updates
ober
14c901db52a43e73c04d9485dc0bd72b3030d74a
--- a/SECURITY.md +++ b/SECURITY.md @@ -7,6 +7,8 @@ library or developer tooling work. Production claims require the gates in [`docs/release-security.md`](docs/release-security.md) and the cross-repository tracker at `~/mine/jerboa-production-readiness.md`. +The security roadmap and prioritized implementation handoff lives in +[`docs/kimi3-security-recommmendations.md`](docs/kimi3-security-recommmendations.md). ## Supported Status --- a/docs/index.md +++ b/docs/index.md @@ -66,8 +66,9 @@ Start with [quickstart.md](quickstart.md), then use - [harden-usage.md](harden-usage.md) - antidebug, seccomp, integrity, and related APIs - [hide.md](hide.md) - reducing information leakage from binaries - [unification.md](unification.md) - security-first system architecture -- [ai-threat.md](ai-threat.md) - AI-assisted adversary assessment -- [uhoh.md](uhoh.md) - cryptographic failure lessons + - [ai-threat.md](ai-threat.md) - AI-assisted adversary assessment + - [kimi3-security-recommmendations.md](kimi3-security-recommmendations.md) - K3-resistant security roadmap and implementation handoff + - [uhoh.md](uhoh.md) - cryptographic failure lessons ## Build, Packaging, And Release new file mode 100644 --- /dev/null +++ b/docs/kimi3-security-recommmendations.md @@ -0,0 +1,974 @@ +# K3-Resistant Security Recommendations — Handoff for Implementing Models + +Status: living handoff document. Created 2026-07-27 from a full review of +`docs/*`, `SECURITY.md`, `lib/std/security/`, `jerboa-native-rs/`, `tests/`, +the `Makefile`, and the `.jerboa-system` manifest. Tone per +[`Philosophy.md`](Philosophy.md): concrete, no hype, no stubs. Every claim +about code was verified in the tree on the date above. + +**Audience:** the next model (or human) asked to implement security work in +this repo. Each work item has an ID, the threat it closes, exact files to +touch, and acceptance criteria. Implement top-down (P0 → P3). Do not skip +P0-01 — the build is currently broken. + +--- + +## Table of Contents + +1. [Why this document exists](#1-why-this-document-exists) +2. [The threat: the 23-minute zero-day era](#2-the-threat-the-23-minute-zero-day-era) +3. [Defense doctrine](#3-defense-doctrine) +4. [Current posture inventory (verified)](#4-current-posture-inventory-verified) +5. [Gap analysis](#5-gap-analysis) +6. [P0 — Containment foundation (do first)](#6-p0--containment-foundation-do-first) +7. [P1 — Harden the boundaries](#7-p1--harden-the-boundaries) +8. [P2 — Supply chain, ecosystem, process](#8-p2--supply-chain-ecosystem-process) +9. [P3 — Research and frontier](#9-p3--research-and-frontier) +10. [Standing practices](#10-standing-practices) +11. [Application containment checklist (for Jerboa users)](#11-application-containment-checklist-for-jerboa-users) +12. [Metrics — how we know the posture improved](#12-metrics--how-we-know-the-posture-improved) +13. [Appendix A: Verified commands](#appendix-a-verified-commands) +14. [Appendix B: Documentation inventory](#appendix-b-documentation-inventory) +15. [Appendix C: Honest limitation ledger](#appendix-c-honest-limitation-ledger) + +--- + +## 1. Why this document exists + +The motivating scenario: a frontier model ("K3-class" — named after the +kimi-k3 incident this document is filed under) was pointed at a mature, +heavily-fuzzed, memory-unsafe C codebase (redis) and produced a working +zero-day in **23 minutes**. The user's requirement is not "make Jerboa +bug-free" (impossible) but: + +> **Have the best possible security posture so that zero-days don't fully +> take over the applications written in this language.** + +That is a *blast-radius containment* requirement. It decomposes into five +engineering goals, which organize every recommendation below: + +| # | Goal | Meaning | +|---|------|---------| +| G1 | **Shrink the target** | Eliminate vulnerability *classes* so there is less to find. | +| G2 | **Cap the blast radius** | When a bug is found, the exploit hits a wall: no ambient authority, kernel-enforced confinement, bounded egress. | +| G3 | **Find it first** | Fuzz, scan, and AI-red-team our own tree continuously, faster than adversaries do. | +| G4 | **Fail closed** | Every degraded security control is a hard error, never a silent fallback. | +| G5 | **Recover fast** | Reproducible, signed, auditable releases so a fix ships in hours, not days. | + +This document maps the existing posture onto G1–G5, identifies the gaps, +and turns each gap into an implementable work item. + +### Rules for the implementing model + +1. **Read [`AGENTS.md`](../AGENTS.md) first.** The `.ss`/`.sls` editing rules + are mandatory (balanced-edit tools only, `jerboa_check_balance` after every + change, no hand paren-counting). +2. **One work item per commit.** Each item below has acceptance criteria; + do not mark done without running them. +3. **Never stub or weaken a security control to make a build pass** — the + repo's standing rule (Philosophy.md, "What we explicitly reject"). A + fail-closed refusal is always acceptable; a fake control never is. +4. **Update the docs in the same session.** If you change behavior described + in `security-reference.md`, `safety-guide.md`, `ai-threat.md`, + `release-security.md`, or this file, fix the doc with the code. +5. **Save discoveries.** Non-trivial patterns → `jerboa_howto_add`; mistakes + → `data/anti-patterns.sexp`; error→fix → `jerboa_error_fix_add`. +6. **Pre-commit gate:** `make binary` on macOS/FreeBSD, `make docker-build` + on Linux, per AGENTS.md. Do not commit with a failing build. + +--- + +## 2. The threat: the 23-minute zero-day era + +### 2.1 What changes + +| Dimension | Pre-AI era | K3-class era | +|---|---|---| +| Time to working exploit for a fresh bug | days–weeks | minutes–hours | +| Cost of auditing 100k LOC for vuln patterns | $50k+ human review | ~free, continuous | +| Fuzzer authoring | specialist weeks | model writes harness in minutes | +| Exploit chaining (bug → RCE → lateral) | rare expertise | on demand | +| Patch window defender can rely on | weeks | **none** | + +Consequences: + +- **"Find and fix every bug" stops working as the primary strategy.** The + adversary re-finds bugs faster than you patch them. Bug-finding speed is + now symmetric; only *structural* asymmetries favor the defender. +- **Heuristic defenses depreciate fastest.** Anything that works by + "recognizing a known-bad pattern" (blocklists, injection regexes, + signature AV) is exactly what a generative adversary is best at + circumventing. [`ai-threat.md`](ai-threat.md) already flags the SQL + injection heuristic this way. +- **Known-unknowns become knowns.** Every "we should audit this someday" + corner of the tree (the 91 `foreign-procedure` files, the 225 exported + Rust FFI symbols, the raw `read` paths) will be systematically enumerated. + There is no security through obscurity left. +- **Supply-chain and tooling surfaces get attacked at machine speed.** + Install scripts, code generators, MCP tools, LSP servers, and CI scripts + are all "applications written in this language" too. + +### 2.2 What does not change + +The mechanisms that survive a K3-class adversary are the ones that were +never pattern-based: + +- **Memory safety of the managed core.** Pure-Scheme Jerboa code has no + buffer overflows, no UAF, no ROP. The redis scenario (memory-corruption + zero-day in C) does not transfer to code that stays off the FFI boundary. + This is Jerboa's single biggest structural asset ([`ai-threat.md`](ai-threat.md) §"What Actually Protects You"). +- **Allowlist (not blocklist) confinement.** `(std security restrict)`'s + `(only (chezscheme) ,@safe-bindings)` environment is *provably closed*: + bindings that aren't listed don't exist, and no prompt engineering or + gadget chain can summon `system` from an empty binding. Verified at + `lib/std/security/restrict.ss:128`. +- **Kernel enforcement.** seccomp BPF and Landlock are evaluated by the + kernel, not by software the attacker can reason around. Irreversible once + installed. +- **Unforgeable capabilities.** Attenuation-only tokens with CSPRNG nonces. + An AI can *misuse* a capability it was handed (confused deputy) but + cannot mint one. +- **Cryptographic provenance.** Ed25519 signatures, content addressing, + reproducible builds. Math, not heuristics. + +### 2.3 The uncomfortable truth + +- **Jerboa cannot fix logic bugs.** Authorization bypass, TOCTOU in + application code, and state-machine errors are language-agnostic + ([`ai-threat.md`](ai-threat.md) §"Where AI Adversaries Will Actually + Win"). What the language *can* do is make the safe pattern the default + and the unsafe pattern unrepresentable — that's most of P0/P1 below. +- **The FFI boundary is our `unsafe`.** 91 files under `lib/std/` mention + `foreign-procedure`; `jerboa-native-rs/src` exports ~225 `#[no_mangle]` + C-ABI symbols and contains 432 matches for `unsafe`. That boundary is + where a K3-class adversary will concentrate, because it is the only place + memory-corruption patterns *exist* in a Jerboa application. + [`Philosophy.md`](Philosophy.md) Principle 4 and open tension #5 both + name this. +- **The process-isolation boundary is currently missing from the tree.** + Every doc says "put adversarial work in a bounded, separately exec'd + worker." No such worker launcher exists in-tree today: + `sandbox-launch` returns status 126 `pre-exec-refused`, + `supervise-available?` returns `#f`, and the raw-fork launchers all fail + closed by design ([`limits.md`](limits.md), [`security-reference.md`](security-reference.md) §5–6). + **This is the single most important gap.** P0-02 builds it. + +--- + +## 3. Defense doctrine + +Five principles, mapped to the goals G1–G5. Every work item below cites the +principles it serves. + +**D1 — Structural beats heuristic (G1).** Prefer defenses that remove a +class (memory safety, allowlists, parameterized queries, no-install-code) +over defenses that recognize an instance (injection regexes, blocklists). +Where a heuristic exists, it must be documented as a speed bump and layered +under a structural defense. + +**D2 — Least authority by default (G2).** Ambient authority is the bug +([`Philosophy.md`](Philosophy.md) Principle 1). Every process that touches +untrusted input runs with: the safe prelude, an explicit capability set, a +kernel sandbox (Landlock/seccomp/Seatbelt/Capsicum as available), a +declared egress policy, and OS resource limits. Defaults must fail closed. + +**D3 — Process boundaries for hostile input (G2, G4).** In-process +restriction (`run-safe-eval`) is for *semi-trusted* expressions only. +Anything adversarial gets a fresh exec'd worker with irreversible controls +installed post-exec. Never run Scheme between fork and exec (the reason +the raw-fork launchers were retired). + +**D4 — Adversarial self-testing (G3).** The same model class that attacks +us defends us: continuous fuzzing with seed corpora and crash regression, +a standing AI-red-team job against our own tree, and a scanner rule base +that grows with every finding. + +**D5 — Auditable everything (G4, G5).** Reproducible builds, signed +artifacts, SBOM, TCB accounting, append-only audit logs. When (not if) a +zero-day lands, the questions "are we affected, which binaries, shipped +when" must be answerable from `dist/release-evidence/` in minutes. + +--- + +## 4. Current posture inventory (verified) + +### 4.1 Defense layers that exist and are real + +| Layer | Module(s) | Verified state | Doc | +|---|---|---|---| +| Safe-by-default prelude (no FFI, no `fork-thread`, no `eval`) | `(jerboa prelude safe)`, `(std safe)` | exists; has a known cosmetic import-conflict warning | [safety-guide.md](safety-guide.md) §1 | +| Allowlist sandbox (closed env, bounded `jerboa-read`) | `(std security restrict)` | exists; `safe-bindings` at `restrict.ss:24` | [security-reference.md](security-reference.md) §2 | +| In-process bounded eval (engine timeout, result cap, fails closed on process controls) | `(std security sandbox)` `run-safe-eval` | exists; no memory limit, no FFI preemption (documented) | security-reference §6 | +| Capability tokens (sealed, CSPRNG nonce, monotone attenuation, revocation) | `(std security capability)` | exists; path check is prefix-string based (limitation documented) | [capability.md](capability.md) | +| Typed capability declarations | `(std security capability-typed)` | exists | security-reference §3 | +| Taint tracking + safe sinks | `(std security taint)` | exists; opt-in only (limitation documented) | security-reference §4 | +| Kernel FS confinement (Linux 5.13+, ABI v1–v3) | `(std security landlock)`, `(std os landlock-native)` | real syscalls | security-reference §5 | +| Kernel syscall filter (BPF, x86_64 + aarch64 numbers) | `(std security seccomp)`, `(std os seccomp)` | real BPF | security-reference §5 | +| macOS / FreeBSD confinement | `(std security seatbelt)`, `(std security capsicum)` | exist | lib listing | +| Self-confinement (pledge/unveil-style `cage!`) | `(std security cage)` | exists; **absent from security-reference.md** — doc gap | `lib/std/security/cage.ss` | +| Privilege-separation pipe channels (no launcher) | `(std security privsep)` | channels only, by design | security-reference §5 | +| Launch policy planner + egress policy objects (no launcher) | `(std os limits sandbox)` | passive policy only, `pre-exec-refused` | [limits.md](limits.md) | +| Native async exec launcher (collect-safe) | `(std os aproc)` | exists — the primitive P0-02 should build on | [aproc.md](aproc.md) | +| Parser hardening (depth/size/backtrack budgets: reader, JSON, XML, YAML, DNS, HTTP/2, WS, zlib, base64, hex, CSV, pregexp, format) | various | phases 1–4 done, 42 tests; **phase 5 (FFI audit) not started** | security-reference §7 | +| Safe deserialization (tagged-JSON envelope, no native FASL on untrusted paths) | `(std safe-fasl)`, `(std fasl)` (trusted-only) | exists; raw-FASL callers still need triage (P0-04) | [safety-guide.md](safety-guide.md) §10 | +| Crypto (Rust `ring`: AEAD, HMAC, PBKDF2, scrypt, ChaCha20, digests; CSPRNG from `/dev/urandom`; timing-safe compare) | `(std crypto native-rust)` et al. | exists; OpenSSL legacy paths still present; no Argon2id | security-reference §10 | +| Secure memory (mlock, guard pages, DONTDUMP, DONTFORK, explicit_bzero) | `(std crypto secure-mem)` | exists; low-level pointer API only | security-reference §9 | +| Lifetime-scoped secrets with auto-wipe | `(std security secret)` | exists; **absent from security-reference.md** — doc gap | `lib/std/security/secret.ss` | +| Env/secret broker for child processes (allow/deny patterns, redaction) | `(std security env)` | exists; **absent from security-reference.md** — doc gap | `lib/std/security/env.ss` | +| Sanitization (HTML/attr/URL/SQL/path/header) | `(std security sanitize)` | exists | security-reference §12 | +| Error classification (opaque client refs) | `(std security errors)` | exists | security-reference §12 | +| Audit log (append-only hash-chained JSONL) | `(std security audit)`, `(std security audit-log)` | exists | security-reference §12 | +| Info-flow lattice (public<internal<secret<top-secret) | `(std security flow)` | exists | security-reference §12 | +| I/O interception (effect-mediated FS/net/proc) | `(std security io-intercept)` | exists | security-reference §12 | +| Build-time import audit (detects direct `(chezscheme)` bypass) | `(std security import-audit)` | exists | security-reference §3 | +| Repo security profiles for scanners | `(std security profile)` | exists | `lib/std/security/profile.ss` | +| Binary hardening (anti-debug, integrity, Ed25519 self-verify) | `(std os antidebug)`, `(std os integrity)` | exists; encrypted-boot and full pipeline are **design-only** ([harden.md](harden.md) §7/§12) | [harden-usage.md](harden-usage.md) | +| Chez runtime hardening (`--enable-harden`: stack protector, FORTIFY, RELRO, CET/BTI/PAC compile-side; SHSTK opt-in at runtime) | vendored Chez fork + launcher hooks | phases 1–2 done, 3.3 done for generated binaries; call/cc-aware SHSTK deferred | [chez-hardening.md](chez-hardening.md) | +| Structured concurrency, timeouts, resource RAII, finalizer net | `(std safe)`, `(std concur deadlock)` | exists | safety-guide §3–6 | +| Lint rules incl. `sql-interpolation`, `unsafe-import` | `(std lint)` | 14 rules | safety-guide §11 | +| WASM sandbox for hostile parsers (wasmi) | `wasm_sm.rs`, `tests/test-wasm-sandbox.ss` | exists, opt-in | [wasm.md](wasm.md), [slang.md](slang.md) | + +### 4.2 Release and CI machinery that exists + +- `make audit` = `security-audit audit-native data-check security-production unification-check` (PR gate). +- `make verify` — one-command local production gate. +- `make sbom`, `make reproducibility-report`, `make reproducibility-compare`, + `make signing-evidence`, `make release-evidence`, `make security-hardware-smoke`. +- Unification toolchain in `tools/`: `check-capability-use.ss`, + `check-native-boundary.ss`, `check-install-policy.ss`, `tcb-report.ss`, + `capability-plan.ss`, `check-audit-events.ss`, `workspace-health.ss`, + `system-report.ss`, `system-provenance.ss`, `system-diff.ss`, + `check-generated-policy.ss`. +- 13 fuzz harnesses in `tests/fuzz/harness/` + `make fuzz` / `fuzz-smoke` / + `fuzz-deep` and 13 per-parser Makefile targets. +- 21 security-related test files (see Appendix A). +- jpkg: content-addressed reproducible artifacts, lockfile-as-boundary, TUF + signed metadata, Ed25519 package signatures, SLSA/in-toto provenance, + transparency log, `jpkg audit` (OSV), declared-capability gating + ([jpkg-guide.md](jpkg-guide.md)). + +### 4.3 Verified numbers + +| Metric | Value (2026-07-27) | Command | +|---|---|---| +| Files under `lib/std/` mentioning `foreign-procedure` | **91** | `grep -rl foreign-procedure lib/std \| wc -l` | +| `#[no_mangle]` C-ABI exports in `jerboa-native-rs/src` | **225** | `grep -c no_mangle jerboa-native-rs/src/*.rs` | +| `unsafe` matches in `jerboa-native-rs/src` | **432** | `grep -c unsafe jerboa-native-rs/src/*.rs` | +| Fuzz harnesses | 13 | `ls tests/fuzz/harness` | +| Checked-in fuzz seed corpora | **0** | `tests/fuzz/` contains only `harness/` | +| Security test files | 21 | Appendix A | +| Vendored C amalgamation in tree | `vendor/jsqlite` | `ls vendor/` | + +### 4.4 Confirmed problems found during this review + +1. **`lib/std/compiler/pattern.ss` is unbalanced *right now*** (confirmed + with `jerboa_check_balance`: unclosed `(` at line 6). This is a build + blocker and violates the pre-commit gate. `git status` shows it clean, + meaning the breakage is committed. **Fix first** (P0-01). The same + breakage was reported for `lib/std/pipeline.ss`, + `tests/test-pipeline.ss`, `tests/test-security2-parsers.ss` in the + checklist appended to `security-reference.md` — re-verify all four. +2. **No exec-based confined worker exists** (see §2.3). All confinement + docs point at a boundary that isn't built. +3. **No fuzz seed corpora and no crash-regression corpus** — fuzzing is + harness-only; nothing preserves found crashes as tests. +4. **`docs/ai-threat.md` comparative table is stale**: it rates supply + chain "Behind (cargo audit)" but jpkg now has TUF/signatures/transparency + /OSV audit. Refresh the table (P2-06). +5. **Three security modules are undocumented** in `security-reference.md`: + `(std security cage)`, `(std security secret)`, `(std security env)`. +6. **Cross-repo findings** recorded in `security-reference.md`'s appended + checklist remain open in sibling repos (out of scope here, tracked in + §8 P2-04 note): `jerboa-code` external-LLM launcher passes + `--dangerously-skip-permissions`/`--yolo` with an allow-by-default + sandbox profile; `jerboa-wafter` has a shell-injection via `system`; + `jerboa-inotify` has an unchecked FFI buffer cast; sibling cross-build + scripts run `git checkout --` against `$jerboa-home/lib`. + +--- + +## 5. Gap analysis + +| Goal | Where we stand | The gap | +|---|---|---| +| G1 shrink target | Managed core is memory-safe; parsers budgeted; safe prelude exists | FFI surface (91 files / 225 symbols / 432 `unsafe`) never systematically audited (phase 5); `vendor/jsqlite` is C in the TCB; safe prelude not the default entry; import conflict undermines "safe symbol wins" confidence | +| G2 cap blast radius | Capabilities, taint, kernel sandbox, egress policy objects all exist | **No worker launcher to assemble them at runtime**; taint opt-in; TOCTOU on paths; distributed actors unauthenticated; no memory limit story; `define-syntax` in sandbox | +| G3 find it first | 13 harnesses, scanner w/ rule DB, lint | No corpora, no crash regression, no scheduled fuzzing, no standing AI-red-team, no exploit-shaped regression suite | +| G4 fail closed | Raw-fork launchers retired correctly; `allow-degraded?` explicit | New controls must keep the invariant; degraded-mode warnings must be test-locked | +| G5 recover fast | SBOM/repro/signing gates exist | TCB accounting manual; doc drift (stale tables, undocumented modules); independent-builder reproducibility not yet routine | + +Priority order follows directly: fix the build (it blocks everything), +build the worker (G2's missing keystone), close the highest-value +structural holes (FFI audit, raw-read triage, actor auth, safe-by-default), +then industrialize find-first and recover-fast. + +--- + +## 6. P0 — Containment foundation (do first) + +### K3-P0-01 — Repair the build; add a balance gate +**Serves:** G4. **Effort:** hours. + +A committed unbalanced file means the safety guarantees in the docs are not +the code that ships. This violates the repo's own pre-commit rule. + +- **Where:** `lib/std/compiler/pattern.ss` (confirmed unclosed `(` at line 6); + re-verify `lib/std/pipeline.ss`, `tests/test-pipeline.ss`, + `tests/test-security2-parsers.ss`. +- **Do:** Recover per AGENTS.md rule 3: `git log -p --follow -- <file>` to + find the breaking commit; prefer fixing forward with + `jerboa_repair_balance` (dry-run, then `apply: true`) — **not** raw + `git checkout --`, which would discard the committed feature work. Then + add `tools/` wiring so a balance check over all tracked `.ss`/`.sls` + files runs inside `make audit` (a `jerboa_project_health_check`-style + pass), failing the gate on any unbalanced file. +- **Accept:** `jerboa_check_balance` clean on all four files; `make binary` + succeeds; `make audit` fails when a synthetic unbalanced file is added. + +### K3-P0-02 — Build the exec-based confined worker (`(std security worker)`) +**Serves:** G2, G3, G4. **Effort:** 1–2 weeks. **This is the keystone.** + +Every security doc routes adversarial work to "a bounded, separately exec'd +worker" that does not exist. Build it as the assembly point for controls +that already exist individually. + +- **Where:** new `lib/std/security/worker.ss`; build on `(std os aproc)` + (collect-safe argv exec, no shell) for launch; reuse policy records from + `(std os limits)`, `(std os limits sandbox)`, `(std security env)`, + `(std security landlock)`, `(std security seccomp)`, `(std security seatbelt)`, + `(std security capsicum)`, `(std security audit-log)`. Optionally a tiny + pre-exec helper in `jerboa-native-rs` (Rust) for rlimit/namespace setup + that must happen before Scheme boots. +- **Design (fail closed at every step):** + - Worker = fresh `jerboa run worker-main.ss` process started via argv + (never shell), with `env-pure:` from an `(std security env)` policy + (allowlist + deny patterns like `*TOKEN*`). + - Supervisor owns: wall-clock deadline, process-group kill + (`kill(neg pid, SIGKILL)` after grace), bounded stdin/stdout/stderr + pipes (byte caps, close-on-limit), and structured result decoding. + - Worker installs, *before* reading any untrusted byte: rlimits + (AS/CPU/NOFILE/NO CORE), Landlock ruleset, seccomp filter (or + Seatbelt/Capsicum equivalent), then enters `(jerboa prelude safe)` and + signals readiness. Any setup failure = nonzero exit, no evaluation. + - Supervisor treats missing readiness, pipe overflow, or deadline as + kill-and-report; results carry `launched?`, refusal axis, and audit + record — the same shapes `(std os limits sandbox)` already defines, so + `sandbox-launch` can be re-pointed at the real backend while keeping + its fail-closed diagnostics contract. + - Wire `make-egress-policy` enforcement via `(std net allow-proxy)` as + the worker's only network route when a deny-default policy is set. +- **Do NOT:** resurrect any Scheme-callback-after-fork API. The retirement + of `run-safe` thunks was correct ([security-reference.md](security-reference.md) §6); the worker + boundary is `exec`. +- **Accept:** new `tests/test-worker.ss` demonstrating: (a) `system`/`eval`/ + FFI unreachable inside worker even if smuggled into input; (b) CPU-spin + killed at deadline with process-group reaped (no orphans — regression + for the `pipeline-timeout` leak noted in security-reference.md's + checklist); (c) memory bomb killed by rlimit AS; (d) FS escape blocked by + Landlock on Linux; (e) oversized stdout truncated at cap; (f) requested + control that can't install ⇒ structured refusal, never silent + degradation; (g) audit-log records start/end. Docs: new section in + `security-reference.md` §5/§6 and rewrite of the "use an exec worker" + passages in `safety-guide.md` §2 and `ai-threat.md` to point at the real + API. + +### K3-P0-03 — Prove the restricted-environment allowlist closed +**Serves:** G1, G4. **Effort:** 2–3 days. + +The allowlist is Jerboa's crown jewel; it currently rests on manual curation. + +- **Where:** new `tools/check-restrict-closure.ss`; `lib/std/security/restrict.ss`. +- **Do:** (a) Mechanically verify every symbol in `safe-bindings` exists in + the pinned Chez version (catches typos that silently *widen* to + nothing or, worse, drift after a Chez upgrade). (b) Forbid-list sweep: + assert none of `system process open-*-file foreign-* load eval compile* + getenv putenv fork-thread import library call/cc call-with-current-continuation + gensym read read-eval` appears. (c) Reachability audit: expand each allowed + binding's definition in the vendored Chez source and flag any that can + reach a forbidden primitive (e.g. a seemingly-safe builtin that calls + `system` internally). Start with a documented manual audit table; automate + what's automatable. (d) Publish the resulting per-binding justification + into `security-reference.md` §2. +- **Accept:** `make audit` runs the checker; removing a binding fails CI if + any doc/test references it; adding a binding requires editing the + justification table. A unit test evaluates escape attempts + (`(eval ...)`, `(system ...)`, `((foreign-procedure ...))`, macro-based + smuggling) inside `restricted-eval` and asserts `&assertion`/`unbound` + failures. + +### K3-P0-04 — Triage and close raw `read` / native FASL paths on untrusted input +**Serves:** G1. **Effort:** 3–5 days. + +`security-reference.md`'s checklist already names exemplars: +`jerbuild.ss:308`, `lib/std/actor/distributed.ss:303`, `lib/std/fasl.ss:27`, +`lib/std/net/grpc.ss:162`. There are more. + +- **Do:** (a) Enumerate every call site of bare `read`, `fasl-read`, and + Chez `load` in `lib/`, `src/`, `tools/`, `mcp/`, `lsp/` (the MCP scanner + already flags these — run it repo-wide, `changed_only: #f`). (b) Classify + each site: *trusted build-time input* (comment + lint suppression with + justification) or *possibly adversarial* (convert to bounded + `jerboa-read` / `safe-fasl` envelope / reject). (c) For `(std fasl)`, + add a loud docstring/contract: trusted provenance only, never network/ + workspace/IPC bytes. (d) Add an `unsafe-deserialize` lint rule so new + raw-`read` sites fail `make audit`. +- **Accept:** scanner reports zero unclassified raw-read/FASL sites; each + remaining site has an inline provenance justification; `jerbuild.ss`, + `distributed.ss`, `grpc.ss` either bounded or documented-trusted. + +### K3-P0-05 — Authenticate distributed actor messages +**Serves:** G2. **Effort:** 3–4 days. + +`security-reference.md` §13: "No message authentication for distributed +actors — messages are still plaintext with no HMAC." A K3-class adversary +that finds any deserialization-shape bug gets a network-reachable +eval-shaped channel; authentication shrinks who can even reach the parser. + +- **Where:** `lib/std/actor/distributed.ss`; reuse `rust-hmac-sha256` / + `rust-timing-safe-equal?` from `(std crypto native-rust)`. +- **Do:** HMAC-SHA256 over the canonical serialized frame with a per-peer + key; include timestamp + monotonic sequence for replay rejection; key + distribution out-of-band (document) or via X25519 handshake + (`x25519.rs` exists in the native backend). Fail closed: unauthenticated + or replayed frames dropped before `deserialize-message` runs. +- **Accept:** tests: tampered frame rejected, replay rejected, wrong key + rejected, valid frame accepted; fuzz harness extended to mutate MAC'd + frames; `security-reference.md` §13 entry removed. + +### K3-P0-06 — Make the safe prelude the default surface +**Serves:** G1, G2. **Effort:** 1 week. + +Today "always use `(jerboa prelude safe)`" is advice in docs. Advice is +what a 23-minute adversary counts on users skipping. + +- **Do:** (a) `jerboa run file.ss` and the script path default to resolving + `(jerboa prelude)` as `(jerboa prelude safe)`; opting into the raw + prelude requires an explicit `(jerboa prelude unsafe)` (rename with + alias kept) or `--unsafe-prelude` flag, which prints a one-line warning + to stderr. (b) Fix the safe prelude import conflict (the "multiple + definitions" warning) so symbol resolution order is deterministic and + tested — a wrong-symbol resolution in a security wrapper is a real bug, + not cosmetics. (c) Extend `(std security import-audit)` so direct + `(chezscheme)` imports in application code fail `make audit` outside an + allowlisted set of stdlib-internal files. (d) Update `quickstart.md`, + `safety-guide.md`, `JERBOA-LANG.md`, `model-coding-guidance.md` and the + MCP cookbook to teach safe-prelude-first. +- **Accept:** `tests/test-safe-prelude.ss` extended: a fresh script using + `(jerboa prelude)` cannot reach `foreign-procedure`, `system`, `eval`, + or `fork-thread`; the escape hatch works and warns; no import-conflict + warning remains at build. + +### K3-P0-07 — Security regression suite v1 (exploit-shaped tests) +**Serves:** G3, G4. **Effort:** 3–4 days. + +Current tests verify features work; almost none verify *attacks fail*. + +- **Where:** new `tests/security/` tree (keeps attack tests separate from + feature tests), wired into `make test-security`. +- **Do:** One test per historical finding so regressions are impossible: + sandbox default-env closure (#1 from the AI-attack table), unbounded + reader budgets (#2/#10), capability intersection attenuation (#3), + empty-host default-deny (#4), symlink canonicalization (#5), bounded + actor deserialization (#6), taint sink enforcement (#8), `read`/`gensym` + absence (#9), import-audit detection (#11), URL/attribute sanitization + (#12), raw-fork fail-closed (#13/#14), `run-safe-eval` degradation + refusal without `allow-degraded?`, plus new P0-02 worker escapes. Each + test names the finding it locks. +- **Accept:** `make test-security` runs the suite; deliberately reverting + any one fix makes exactly its test fail (spot-check three). + +### K3-P0-08 — Fuzzing becomes continuous, seeded, and regressive +**Serves:** G3. **Effort:** 1 week. + +Harnesses without corpora find a bug once and forget it. + +- **Do:** (a) Add `tests/fuzz/corpus/<parser>/` seed corpora (start from + valid samples mutated minimally; include every historical crash input). + (b) Add `tests/fuzz/regression/` — every crash ever found becomes a + checked-in test input executed by `make test` (not just `make fuzz`). + (c) CI: `make fuzz-smoke` on every PR; `make fuzz-deep` nightly with + corpus minimization and new-crash upload as artifacts. (d) Add missing + harnesses: YAML parser, safe-fasl envelope decoder, HMAC'd actor frames + (P0-05), URI/URL sanitizer, `jerboa-read` against the *full* reader + (including block comments/heredocs), `jpkg` archive validator + (path-traversal shaped inputs), MCP/LSP/repl-protocol message parsers + (they are network-adjacent input surfaces for the dev environment — + [`unification.md`](unification.md) §"Developer Environment as a Security + Boundary"). +- **Accept:** `make fuzz-smoke` green in CI; a seeded known-bad input is + caught by regression tests; corpus directories documented in + `testing-and-infrastructure.md`. + +--- + +## 7. P1 — Harden the boundaries + +### K3-P1-01 — FFI boundary audit (parser-hardening phase 5) +**Serves:** G1. **Effort:** 3–4 weeks (the big one). + +The 91 `foreign-procedure` files and 225 native exports are where a +K3-class adversary will shop. `security-reference.md` §13: "FFI audit … is +not started." + +- **Do:** (a) Inventory: generate the authoritative list of every + `foreign-procedure` binding and every `#[no_mangle]` export, with arity, + pointer arguments, and ownership contract (the `.jerboa-system` native + boundary + `tools/check-native-boundary.ss` are the starting point; + `tools/tcb-report.ss` consumes it). (b) Per binding, verify: null-return + checks, length/bounds derivation (no caller-supplied length trusted), + `__collect_safe` on blocking calls, `EINTR` retry (the scanner already + has this C rule — extend to Scheme FFI call sites), integer width + confusion (`unsigned-64` vs `size_t` on all targets), and GC-safety of + bytevector pointers across calls that may collect. (c) Rust side: + `cargo geiger`-style `unsafe` census; each `unsafe` block gets an + invariant comment; deny-by-default `#![deny(unsafe_op_in_unsafe_fn)]`; + panic-free C ABI (unwind across FFI is UB — check `panic.rs` covers all + exports). (d) Shrink: unexport any `#[no_mangle]` symbol without a live + Scheme caller. (e) Generate bindings where possible + ([`ffi.md`](ffi.md)'s `define-ffi-library` + `define-foreign/check` + already encode ownership/destructors — prefer them over hand-written + `foreign-procedure`). (f) Decide `vendor/jsqlite`'s fate: it is a C + amalgamation in the TCB; either move consumers to the Rust sqlite path + (`sqlite.rs`) or document accepted risk + add its CVE feed to release + gates. +- **Accept:** audit report committed (`docs/ffi-audit.md`) with + per-binding verdict; scanner gains null-check/width/bounds rules; zero + un-annotated `unsafe`; export count reduced and justified; findings + fixed or tracked. + +### K3-P1-02 — TOCTOU-safe filesystem capability checks +**Serves:** G2. **Effort:** 1 week. + +`security-reference.md` §13: path checks use `realpath(3)` *before* access, +not `O_NOFOLLOW` + fd-relative verification after open. A symlink race +defeats the capability. + +- **Where:** `(std security capability)` path enforcement; Rust + `secure_fs.rs` already exists — extend it. +- **Do:** open with `O_NOFOLLOW`/`openat2(RESOLVE_NO_SYMLINKS)` where + available, verify the opened fd via `/proc/self/fd/N` against the + canonicalized allowed set, then operate on the fd (not the path). macOS/ + BSD: `open(..., O_NOFOLLOW | O_CLOEXEC)` + `fstat` checks; document the + residual race where the platform can't fully close it. +- **Accept:** race PoC test (symlink swapped between check and open) fails + pre-patch, passes post-patch; `capability.md` limitation removed. + +### K3-P1-03 — Taint enforcement on by default in the safe prelude +**Serves:** G2. **Effort:** 1 week. + +Taint is opt-in; native sinks don't check it. In the safe prelude, the +*default* file/shell/delete operations should be the taint-aware wrappers. + +- **Do:** (a) In `(jerboa prelude safe)`, rebind `open-input-file`, + `open-output-file`, `delete-file`, and any process-spawn to the + `safe-*` taint-checking versions (with the raw versions available only + via explicit `(std unsafe)` import). (b) Taint sources by default: HTTP + request fields, env vars, file contents read in safe mode, network + frames — wrapped at the boundary. (c) Propagation coverage: extend the + tainted-string operation set (currently four functions) to the full + common string/bytevector API so taint doesn't silently launder away. + (d) Performance: measure; if overhead matters, document + `*taint-enforce* #f` as an explicit, warned de-opt — never silent. +- **Accept:** tests: tainted value reaching a sink in safe-prelude code + raises `&taint-violation`; untainted flows unchanged; propagation tests + for each new operation; `safety-guide.md` gains a taint section. + +### K3-P1-04 — Decide `define-syntax` in the sandbox allowlist +**Serves:** G1. **Effort:** 2–3 days. + +Macros in sandboxed code can smuggle references to bindings the allowlist +excluded, depending on expansion environment semantics. + +- **Do:** (a) Write the adversarial test battery first: `syntax-rules` + emitting references to `system`/`eval`/`foreign-procedure`, use of + `datum->syntax` with forged contexts, macro-defined macros. (b) If any + escapes: remove `define-syntax`/`syntax-rules` from `safe-bindings`, or + fence expansion in the restricted environment. (c) If provably closed: + document the proof sketch in `security-reference.md` §13 and lock with + tests. +- **Accept:** battery passes (or feature removed); §13 entry resolved. + +### K3-P1-05 — Crypto modernization: Argon2id, retire OpenSSL legacy paths +**Serves:** G1, G5. **Effort:** 1 week. + +- **Do:** (a) Add Argon2id via the Rust `argon2` crate in + `jerboa-native-rs` (password hashing is the documented gap in + `security-reference.md` §13); make it the recommended `password-hash` + backend, keep PBKDF2 for compatibility. (b) Deprecate the OpenSSL EVP + legacy paths (`(std crypto hmac)`/`aead`/`kdf` legacy): emit a + deprecation warning, route defaults to `ring`, and set a removal + milestone — one fewer C crypto dependency in the TCB. (c) Enforce + [`uhoh.md`](uhoh.md)'s rule table in review: no new pure-Scheme crypto + where secrets flow; digests-only in Scheme. +- **Accept:** Argon2id vectors (RFC 9106) pass; OpenSSL-free build works + (`make native` with openssl disabled); docs updated. + +### K3-P1-06 — Typed secure-memory interface +**Serves:** G2. **Effort:** 3–4 days. + +`secure-mem` requires manual `foreign-ref`/`foreign-set!` pointer +arithmetic — itself an FFI footgun (`security-reference.md` §13). + +- **Do:** Add a high-level API: `secure-bytevector` view type with bounds- + checked get/set, `with-secure-bytevector` scoped form, and integration + with `(std security secret)` so `with-secret` can back secrets by + mlock'd memory. No raw pointer arithmetic exposed. +- **Accept:** tests exercise bounds violations (expect exceptions, not + corruption); guard-page trip test (SIGSEGV on out-of-bounds at the page + level) verified on Linux; §13 entry removed. + +### K3-P1-07 — Memory-limit story for untrusted evaluation +**Serves:** G2. **Effort:** 1 week. + +Chez cannot heap-cap a thread; `run-safe-eval` rightly refuses +`max-memory-size`. The worker (P0-02) is the answer — make it ergonomic. + +- **Do:** (a) Ship `worker-run-eval` convenience: one call that execs a + confined worker with rlimit AS + CPU, runs the restricted eval, returns + bounded output. (b) Document the hierarchy: in-process `run-safe-eval` + (semi-trusted) → confined worker eval (untrusted) → container/VM + (hostile). (c) Investigate Chez `collect-trip` / maximum-heap parameters + as an in-process tripwire (defense-in-depth, not a boundary) — document + findings either way in `chez-limits.md`. +- **Accept:** eval-bomb test killed by worker rlimit; docs updated; + `run-safe-eval` docs cross-link the worker API. + +### K3-P1-08 — Platform sandbox parity +**Serves:** G2. **Effort:** 2 weeks. + +- **Do:** (a) seccomp: extend the BPF generator's syscall tables beyond + x86_64/aarch64 only as far as testing supports; keep the architecture + validation that prevents syscall-number confusion. (b) Ship default + profiles for the common server shapes (`network-server-filter` exists — + add `http-server`, `dns-server`, `worker-eval` presets mapped to the + P0-02 worker). (c) Seatbelt (macOS) and Capsicum (FreeBSD): bring test + coverage to parity with Landlock/seccomp so `make verify` exercises all + three on their native platforms. (d) A `sandbox-report` API that + enumerates which confinements are active in the current process (for + audit logs and the release-evidence bundle). +- **Accept:** per-platform CI smoke for each mechanism; presets used by + P0-02 worker tests. + +### K3-P1-09 — Confused-deputy defenses: capability plans enforced at runtime +**Serves:** G2. **Effort:** 1 week. + +`Philosophy.md` open tension #7: ocap prevents forgery, not misuse. +`tools/capability-plan.ss` and `check-capability-use.ss` already exist — +they are build-time. Close the runtime loop. + +- **Do:** (a) A `.jerboa/capabilities.sexp` per application declaring + intended grants; at startup, the app builds exactly those capabilities + from the root and the runtime *refuses* undeclared grants (fail closed). + (b) `check-capability!/audit` everywhere a capability crosses a module + boundary (audit chain already exists). (c) Lint rule: passing a wider + capability where a narrower derivable one suffices (detect + `attenuate-*` opportunities). (d) Doc: capability-grant style guide in + `capability.md` (grant at the latest moment, narrowest scope, log every + grant). +- **Accept:** undeclared-grant refusal test; audit-chain verification + test; lint rule fires on a synthetic over-grant. + +### K3-P1-10 — Information-leak sweep: errors, logs, and protocol surfaces +**Serves:** G2, G4. **Effort:** 1 week. + +- **Do:** (a) Adopt `(std security errors)` classification in the HTTP + server, router, gRPC, MCP, LSP, and repl-protocol paths: client-visible + errors carry opaque refs; internal detail only in server logs. (b) Add a + scanner rule: `display`/`format` of raw condition messages into HTTP + responses. (c) Ensure `(std security env)` redaction is wired into + `(std security audit-log)` sinks so registered secret values cannot be + logged (the broker already supports value redaction — enforce its use). + (d) Verify release binaries strip build paths (`-ffile-prefix-map`, + `(generate-inspector-information #f)` per `harden.md` §8) — test the + shipped artifact for `$HOME` path leakage. +- **Accept:** tests: crafted failing request yields opaque ref + logged + detail; scanner catches a synthetic leak; artifact grep clean. + +### K3-P1-11 — Parameterized-only SQL in the safe surface +**Serves:** G1. **Effort:** 2–3 days. + +The injection heuristic is a documented speed bump; the structural fix is +making string-built SQL unrepresentable in safe code. + +- **Do:** (a) In `(jerboa prelude safe)`, expose `sqlite-query`/`exec` + *only* in parameterized form (SQL literal + params); the raw-string + variants move behind `(std unsafe)`. (b) Keep the heuristic as + defense-in-depth for the unsafe surface. (c) Strengthen + `sql-interpolation` lint to error in safe-prelude files. +- **Accept:** safe-prelude script cannot call the string-SQL API; lint + errors on interpolation; `safety-guide.md` §4 updated. + +### K3-P1-12 — Eliminate the safe-prelude import conflict +**Serves:** G1, G4. **Effort:** 2 days. (Folded into P0-06 if done there.) + +Tracked separately because "which `sort`/`hash` did I get?" ambiguity in +security wrappers is a latent vulnerability. Make exports disjoint, add a +test asserting no duplicate-export warnings at build, and remove the +"cosmetic" language from `ai-threat.md` once fixed. + +--- + +## 8. P2 — Supply chain, ecosystem, process + +### K3-P2-01 — jpkg secure-by-default templates +**Serves:** G5, G1. **Effort:** 3 days. + +The jpkg hardening exists but is opt-in (`JPKG_REQUIRE_SIGNATURES`, +`JPKG_REQUIRE_PROVENANCE`). Generate new projects with these set, the +lockfile committed, and `jpkg audit` in their CI template. Update +`jpkg-guide.md` quickstart to the secure default path. + +### K3-P2-02 — Dependency-audit parity and freshness +**Serves:** G5. **Effort:** 3 days. + +`ai-threat.md` rates Jerboa behind cargo/npm audit. Close it: wire +`jpkg audit` (OSV) into `make audit` for this repo's own `jpkg.lock`; +keep `cargo audit -D warnings`; add a scheduled weekly job that fails on +new advisories affecting locked versions; document the OSV DB snapshot +provenance. + +### K3-P2-03 — Automated TCB accounting +**Serves:** G5. **Effort:** 1 week. + +`tools/tcb-report.ss` exists — promote it to a release artifact: the +written, versioned list of everything trusted (Chez kernel C, Jerboa C +shims, each Rust crate + its `unsafe` count, vendored C like +`vendor/jsqlite`, the kernel, the container base image digest from +`support/container-dependencies.lock`). Gate: TCB size growth requires a +changelog note. `Philosophy.md` open tension #6 asks for exactly this. + +### K3-P2-04 — Standing AI red-team practice +**Serves:** G3. **Effort:** ongoing; 2 days to bootstrap. + +Institutionalize "we find our own CVEs first" (Philosophy P5): a +documented, repeatable red-team protocol — fresh model instance, repo + +threat model as prompt, findings filed as `data/anti-patterns.sexp` +entries and exploit-shaped tests (P0-07). Quarterly cadence + after every +P1 landing. Also drive the open sibling-repo findings (§4.4 item 6) to +resolution in their own repos — do not patch them from here (repository +boundary rule), but track them in the cross-repo readiness file. + +### K3-P2-05 — Scanner rule growth and pre-commit wiring +**Serves:** G3. **Effort:** continuous; 2 days to wire. + +`data/security-rules.sexp` is the rule DB behind `jerboa_security_scan`. +(a) Add rules for every finding from P1-01 (FFI null/width/bounds), raw +`read`, native FASL, `system` with string concatenation, missing +`with-resource` on `sqlite-open`/`tcp-connect`, hardcoded secrets, unsafe +randomness (`(random N)` for tokens), disabled contract checks in +production (`*safe-mode* 'release` outside tests). (b) Wire +changed-lines-only scanning into a pre-commit hook template under +`support/`; document opt-in in `devex.md`. (c) Every future security +fix must add its scanner rule in the same commit (write it into +`AGENTS.md`-adjacent contributing docs). + +### K3-P2-06 — Documentation consistency pass +**Serves:** G5. **Effort:** 2 days. + +- Refresh `ai-threat.md`'s comparative table (supply chain now: + TUF + signatures + transparency + OSV; update the "Behind" cells and the + "Known Vulnerabilities" table statuses). +- Document `(std security cage)`, `(std security secret)`, + `(std security env)` in `security-reference.md`. +- Add this file to `docs/index.md` (done at creation) and cross-link from + `SECURITY.md`. +- Move the appended "Security Patterns & Threat-Model Checklist" findings + out of `security-reference.md` into a dated `docs/reviews/` file — + reference docs shouldn't carry stale review state. + +### K3-P2-07 — Secret-handling standard adoption +**Serves:** G2. **Effort:** 1 week. + +Standardize on `(std security secret)` (auto-wipe lifetimes) + +`(std security env)` (brokered injection, never parent-env inheritance) + +`(std crypto secure-mem)` (storage). Sweep `lib/`, `mcp/`, `lsp/`, +`tools/` for: secrets in env passed to children unfiltered, secrets in +command-line argv (`ps`-visible — Philosophy rejects this), secrets in +error messages/logs. Scanner rules from P2-05 enforce going forward. + +### K3-P2-08 — SHSTK default decision for daemon binaries +**Serves:** G1. **Effort:** 2 days. + +`JERBOA_ENABLE_SHSTK=try` exists for generated binaries +(`chez-hardening.md` §6.3). For binaries that provably never invoke +`call/cc` (parsers, DNS, static-binary daemons), flip the default to +`try` with a `JERBOA_SECURITY_REPORT=1` attestation line in release +evidence; keep opt-in for REPL/tooling. Record the decision and the +call/cc hazard in `release-security.md`. + +--- + +## 9. P3 — Research and frontier + +Tracked so they're not invented twice (per `chez-hardening.md` §11's +maintenance rule). Do not start before P0/P1 land. + +| ID | Item | Source | Notes | +|---|---|---|---| +| K3-P3-01 | ARM MTE integration in Chez GC | [chez-hardening.md](chez-hardening.md) §7.1 | hardware UAF/overflow detection; needs GC tagging work | +| K3-P3-02 | call/cc-aware shadow stack (SHSTK/GCS) | chez-hardening §6.4 | kernel continuation patch; weeks; needs CET CI runner | +| K3-P3-03 | Intel MPK for JIT page W^X narrowing | chez-hardening §7.3 | most impactful x86_64 item past phase 1 | +| K3-P3-04 | WASM-sandboxed parsers by default for image/media/compression formats | [slang.md](slang.md), Philosophy tension #4 | wasmi fuel sandbox exists; decide throughput trade-off per format | +| K3-P3-05 | Adopt the `slang` secure static subset as the default profile for shipped binaries | [slang.md](slang.md) | attack-surface elimination at the language-subset level; pair with P0-06 | +| K3-P3-06 | Machine-checked proofs of the two smallest, highest-value kernels: the restricted-env allowlist closure (P0-03) and capability attenuation monotonicity | security-reference §13 ("no formal verification") | both are small enough for ACL2/Coq or a model-checking pass; even a property-based test harness over attenuation algebra is progress | +| K3-P3-07 | Covert/side-channel posture | security-reference §13 | GC timing channel documented; add a constant-time coding guide for crypto-adjacent Scheme and a scanner rule for data-dependent branching on secrets | +| K3-P3-08 | Encrypted boot files + full signed-binary pipeline | [harden.md](harden.md) §7/§12 (design-only) | needs the C main entry in the consuming project; coordinates with jerboa-shell | + +--- + +## 10. Standing practices + +These become the permanent operating rhythm once the work items land. + +| Practice | Gate / cadence | Mechanism | +|---|---|---| +| Balance + build health | every commit | `make audit` incl. all-file balance check (P0-01) | +| Scanner with growing rule DB | every commit (changed lines), every night (full tree) | `jerboa_security_scan`, pre-commit template (P2-05) | +| Fuzz smoke / deep | PR / nightly | `make fuzz-smoke` / `fuzz-deep` with corpora (P0-08) | +| Exploit-shaped regression suite | every `make test` | `tests/security/` (P0-07) | +| Dependency advisories | weekly | `jpkg audit` + `cargo audit` (P2-02) | +| AI red-team | quarterly + after P1 landings | protocol in P2-04 | +| TCB report drift | every release | `tools/tcb-report.ss` in release evidence (P2-03) | +| Release gates | every release | `make verify`, `make release-evidence`, `make signing-evidence` with `JERBOA_REQUIRE_RELEASE_SIGNATURES=1` ([release-security.md](release-security.md)) | +| Doc drift check | every security commit | the four security docs + this file updated in-session; `make audit` fails on undocumented new `(std security *)` exports | + +--- + +## 11. Application containment checklist (for Jerboa users) + +This is the answer to "a zero-day was just found in my Jerboa service — +why isn't it game over?" Ship this as a template (consider +`jerboa_project_template` MCP scaffolding) so new services start here. + +**Baseline (every app):** +1. `(import (jerboa prelude safe))` — no raw FFI, no `eval`, no raw threads. (P0-06 makes this the default.) +2. Parameterized queries only; heuristic is a seatbelt, not a wall. (P1-11) +3. `with-resource` on every handle; `with-timeout` on every I/O; socket-level timeouts to cover FFI blocking. +4. Structured conditions + `(std security errors)` for client-facing errors — never leak internals. +5. Secrets via `(std security secret)`/`(std security env)`; never argv, never unfiltered env to children, never logs. (P2-07) + +**Any code path touching untrusted bytes:** +6. Bounded parsers only (they are by default); `jerboa-read` for S-expressions; `safe-fasl` envelope for data-at-rest; native FASL for trusted build artifacts only. (P0-04) +7. Taint sources at the boundary; sinks reject tainted. (P1-03) + +**Anything adversarial (user expressions, uploaded content, plugin code):** +8. Run it in the confined worker: fresh exec, rlimits, Landlock/seccomp (Seatbelt/Capsicum), `env-pure` allowlist, deny-default egress via the allowlist proxy, bounded pipes, supervisor deadline + process-group kill, audit-log start/end. (P0-02, P1-08) +9. Grant the narrowest capabilities, declared in the capability plan, intersected per request. (P1-09) + +**Servers:** +10. Drop privileges after startup: `cage!`/Landlock + seccomp profile, then serve. (`safety-guide.md` §"Server That Drops Privileges"; `(std security cage)`). +11. Admission limits (global/per-source) from `(std net thread-httpd)`; deadlines on header/body. +12. Append-only audit log with hash chain; alert on verify failure. + +**Release:** +13. Signed artifacts, SBOM, reproducibility report, pinned `jpkg.lock`, `jpkg audit` clean. When the CVE drops, `dist/release-evidence/` answers "affected? which build? shipped when?" in minutes. (G5) + +--- + +## 12. Metrics — how we know the posture improved + +Track these in `docs/status.md` per release: + +| Metric | Baseline (2026-07-27) | Target | +|---|---|---| +| Build balance clean | **failing** (pattern.ss) | always clean | +| Confined worker exists | no | yes, tested (P0-02) | +| Unclassified raw `read`/FASL sites | unknown (4+ named) | 0 | +| FFI bindings audited | 0 / 91 files | 100% with verdicts | +| Un-annotated Rust `unsafe` blocks | unknown / 432 matches | 0 | +| Fuzz corpora / crash regressions | 0 / 0 | per-parser corpora + every crash a test | +| Exploit-shaped regression tests | ~0 | ≥ 1 per historical finding | +| Sandboxed-by-default app entry | no | yes (P0-06) | +| Supply-chain gates in `make audit` | partial | jpkg audit + cargo audit + signatures required | +| Security-doc drift items | ≥ 5 (§4.4) | 0 | +| Time from advisory → release evidence answering "affected?" | unmeasured | < 1 hour drill, quarterly | + +--- + +## Appendix A: Verified commands + +```bash +# Posture facts used in this document (re-run to refresh): +grep -rl 'foreign-procedure' lib/std | wc -l # 91 +grep -c 'no_mangle' jerboa-native-rs/src/*.rs | awk -F: '{s+=$2} END {print s}' # 225 +grep -c 'unsafe' jerboa-native-rs/src/*.rs | awk -F: '{s+=$2} END {print s}' # 432 +ls tests/fuzz/harness # 13 harnesses +ls vendor/ # ChezScheme, ChezScheme-patches, jsqlite + +# Security test files (21): +# test-capability test-fasl-safety test-phase3-security test-phase4-safety +# test-region-safety test-restrict-hardened test-safe-prelude +# test-sandbox-native-timeout test-sandbox-script-failclosed +# test-sandbox-thread-failclosed test-sandbox test-secure-fs +# test-secure-link-argv test-security-capability test-security-profile +# test-security-taint test-security2-parsers test-taint test-typed-fuzz +# test-wasm-sandbox (+ tests/fuzz/) + +# Gates: +make audit # PR gate: security-audit audit-native data-check security-production unification-check +make test-security # security feature tests (extend with P0-07) +make fuzz-smoke # CI fuzz pass (extend with corpora, P0-08) +make verify # local production gate +make sbom reproducibility-report signing-evidence release-evidence +``` + +## Appendix B: Documentation inventory + +Security-relevant docs as of 2026-07-27, with their role in this plan: + +| Doc | Role | +|---|---| +| [`Philosophy.md`](Philosophy.md) | The *why*: authority-centric doctrine; principles every item above cites | +| [`ai-threat.md`](ai-threat.md) | Existing AI-adversary threat model; needs table refresh (P2-06) | +| [`security-reference.md`](security-reference.md) | Feature reference + limitation ledger (§13); add worker/cage/secret/env sections | +| [`safety-guide.md`](safety-guide.md) | User-facing secure-app guide; update §2 to the real worker API | +| [`release-security.md`](release-security.md) | Release gates and threat model for the distribution | +| [`capability.md`](capability.md) | ocap implementation + documented limitations (TOCTOU fixed by P1-02) | +| [`chez-hardening.md`](chez-hardening.md) | CPU/runtime mitigations; P2-08 and P3-01..03 source | +| [`harden.md`](harden.md), [`harden-usage.md`](harden-usage.md) | Binary hardening; §7/§12 design-only → P3-08 | +| [`limits.md`](limits.md) | Fail-closed launcher policy the worker (P0-02) must satisfy | +| [`aproc.md`](aproc.md) | The exec primitive the worker builds on | +| [`slang.md`](slang.md) | Secure static subset; P3-05 | +| [`unification.md`](unification.md) | Security invariants for the whole stack; §"AI code is untrusted code" | +| [`ffi.md`](ffi.md) | FFI DSL with ownership/destructors; P1-01 prefers generated bindings | +| [`jpkg-guide.md`](jpkg-guide.md) | Supply-chain security model; P2-01/02 | +| [`uhoh.md`](uhoh.md) | Crypto discipline rule table; enforced by P1-05 | +| [`finalizer-safety-net.md`](finalizer-safety-net.md) | Resource-leak net proposal; verify guardian coverage when adopting | +| [`chez-limits.md`](chez-limits.md) | Target for P1-07 heap-cap findings | +| [`testing-and-infrastructure.md`](testing-and-infrastructure.md) | Update with corpus/regression layout (P0-08) | +| [`SECURITY.md`](../SECURITY.md) | Policy root; cross-link this file | + +## Appendix C: Honest limitation ledger + +Carried forward from `security-reference.md` §13, updated, plus additions +from this review. Keep this list current — a stale limitation list is how +fake confidence happens. + +- No formal verification of security modules (P3-06 targets the two + smallest kernels). +- No FIPS 140-3 / Common Criteria evaluation. +- No covert-channel analysis; Chez GC is a timing side channel (P3-07). +- Seccomp tables cover x86_64/aarch64 only; Landlock needs Linux 5.13+; + Seatbelt/Capsicum have thinner test coverage (P1-08). +- Taint is opt-in until P1-03; native Chez sinks never check it. +- No message authentication for distributed actors until P0-05. +- Path checks are TOCTOU-raceable until P1-02. +- `define-syntax` remains in the sandbox allowlist pending P1-04's proof. +- `run-safe-eval` output cap is on the formatted result only; no + stdout/stderr capture; engine timeouts can't preempt blocked FFI + (by design — the worker, P0-02, is the boundary). +- No Argon2id until P1-05; OpenSSL legacy paths present until then. +- Secure memory API is pointer-level until P1-06. +- FFI audit (phase 5) unstarted until P1-01; `vendor/jsqlite` is C in the + TCB pending its decision. +- No independent red-team evaluation yet (P2-04 starts the practice). +- The confined exec worker does not exist yet (P0-02); until it lands, + every doc sentence saying "use an exec worker" describes an aspiration, + not an API. +- `lib/std/compiler/pattern.ss` is unbalanced as of this writing (P0-01). + +--- + +*Maintenance rule (per `chez-hardening.md` §11): drift between this +document and the tree is itself a smell — fix the tree or fix the doc, in +the same commit.*