docs: add Philosophy.md — living security-first design philosophy
ober
d01913202bc0a1084c06c66d9a0a2e0950b87330
new file mode 100644 --- /dev/null +++ b/docs/Philosophy.md @@ -0,0 +1,266 @@ +# Jerboa Philosophy + +Living document — last revised 2026-05-24. Honest, no hype, no stubs. + +This file states the long-term design philosophy for the Jerboa stack: the +principles we hold while growing it toward a best-in-breed, defensible +replacement for the npm/Node/JS and C-based stacks. **The organizing goal is +security.** It is meant to be iterated on — by humans and by other LLMs. + +It sits *above* the concrete docs: see [`capability.md`](capability.md) for the +object-capability implementation, [`ai-threat.md`](ai-threat.md) for the +adversarial threat model, and [`security-reference.md`](security-reference.md) +for the hardening reference. This document is the *why*; those are the *how*. + +--- + +## How to iterate on this document + +- **Keep the tone: concrete, no hype, no stubs.** Claims about code must be + verifiable in the tree. Mark aspirational items as such. +- **Separate principle from state.** A principle is durable; "current state" + notes change as the tree changes. Don't let a temporary gap weaken a principle. +- **Never delete a principle to win an argument.** If you disagree, record the + counter-argument under *Open tensions* and leave the principle standing until + it's actually resolved. (This mirrors the stack's "never remove, always fix" + rule, applied to ideas.) +- **Every proposed change carries its security rationale.** "Why is this safer?" + is the question each edit must answer. +- **Date significant revisions** at the top. + +--- + +## North Star + +Replace the npm/Node/JS and C stacks with one that is *both* more capable and +fundamentally more defensible — a single memory-safe language (Jerboa on stock +Chez) orchestrating a small, audited native core (Rust), where the common case +needs no third-party code at all. + +The spine of everything below is one claim: + +> **Memory safety is table stakes. The real game is *authority*.** + +Both stacks we are replacing lost on authority, not just safety. C has no +boundaries at all. npm pairs *ambient authority* (any package can do anything +the process can) with an unsigned, code-executing supply chain. A memory-safe +rewrite that still hands code ambient power has solved the smaller half of the +problem. So every principle here serves: **least authority, proven provenance, +smallest trusted base.** + +--- + +## Principle 1 — Object-capability by default; ambient authority is the bug + +Nothing — a package, a script, a builtin, a piece of AI-generated code — touches +the filesystem, network, processes, or secrets without a capability it was +*handed*. Authority flows by explicit grant and can only be *attenuated*, never +amplified. This is the single principle that makes a large ecosystem survivable: +a malicious or merely buggy dependency can only do what it was granted. + +**Why it's the keystone.** It is the through-line of every concrete failure we +care about: +- An `ssh-agent` socket is a *signing oracle*; whoever can `connect()` signs as + you. "Find a socket and use it" (e.g. `ssh-find-agent`) is a credential + hijack wearing a convenience tool's clothes — possible only because the oracle + sits in an ambient, shared namespace. +- Every unsigned `.el` in a stock Emacs runs with the *full authority of the + editor process* — it can read `~/.authinfo`, exec anything, open sockets. Same + disease at editor scale. +- The fix in both cases is the same shape: the secret/oracle lives behind a + capability boundary (see jsh's encrypted `//embed/` store, unlocked into the + narrowest scope, never ambient), not in a place any same-uid code can reach. + +**Rules out:** ambient authority as the default execution model; "trusted because +it's installed"; extensions/packages that inherit the host's full power. + +**Current state:** seeded and real — `(std capability)` provides unforgeable, +attenuable, revocable tokens ([`capability.md`](capability.md)); jsh carries +OS-level sandbox hooks (landlock/seccomp/pledge/unveil) and the `//embed/` +secret store. **Aspirational:** making capability-scoping the *pervasive default* +for packages and editor extensions, not an opt-in library. + +--- + +## Principle 2 — Depth in the core beats breadth in the ecosystem + +npm won on reach: "there's a package for everything, one import away." We beat +that not by copying it but by inverting it — a large, *first-party, audited* +standard library so the common case needs zero third-party code. Every capability +that would be a sketchy micro-dependency elsewhere becomes a reviewed builtin +operating on **structured data, not text**. + +**Why it's safer.** The thing that makes shell/JS glue dangerous is composing +tools by parsing each other's *text* (`ls`/`ps`/`find` output), `eval`-ing +constructed strings, and `exec`-ing a dozen binaries — each step a footgun. A +broad first-party stdlib of typed operations removes the reason to reach for that +glue, and shrinks the dependency *surface*, not merely the dependency count. + +**Rules out:** deep, unaudited transitive dependency trees; "data as text" as the +default interchange; reaching for a third-party micro-package for something the +core should own. + +**Current state:** 229 `(std *)` modules; jsh's builtins operate on data, not +text. **Aspirational:** the deliberate, ongoing project of promoting common glue +into reviewed builtins faster than an ecosystem of micro-deps would form. + +--- + +## Principle 3 — Provenance is mandatory; installing must never run code + +To have a package ecosystem without npm's disease, invert npm's defaults: +artifacts are **signed** (rooted in hardware — see `jerboa-yubikey`), +**content-addressed**, and **reproducible**; installing a package executes +**no code** — no `postinstall`, no arbitrary build-time scripts. Build steps are +declarative and sandboxed, not a shell escape hatch. + +**Why it's safer.** npm's worst supply-chain incidents are not memory bugs — they +are unsigned, mutable artifacts plus install-time code execution (a compromised +or typosquatted package runs arbitrary code on every `npm install`). Signing +fixes provenance; *no-install-code* removes the most-abused execution surface. +And note the layering: signing answers "who made this," capabilities (Principle 1) +still answer "what it may do" — even a correctly-signed package runs at least +authority. + +**Rules out:** unsigned/mutable packages; install/build-time arbitrary code +execution; flat, typosquat-friendly namespaces with no provenance. + +**Current state:** `jerboa-yubikey` provides the hardware signing root. +**Aspirational:** the package manager and registry that enforce +signing + content-addressing + no-install-code as non-negotiable defaults. + +--- + +## Principle 4 — Safe by construction, fast by delegation, minimal trusted base + +Memory-safe Scheme orchestrates. Drop to Rust only for genuine hot paths and for +parsing hostile bytes fast — and when you do, prefer *pure-Rust* backends, isolate +the most dangerous parsers in a wasm sandbox, and keep the FFI marshalling surface +small and audited. Keep the compiler additive (new primitives, no behavioral +patches) so the trusted base never forks semantically. + +**Why it's safer.** Memory-safety CVEs cluster in untrusted-input parsers, not in +GC/scheduler runtimes — so the high-leverage move is getting *parsers* off C, and +where possible into bounds-checked Scheme (e.g. `jerboa-wafter` dissects packets +in ~1,700 pure-Scheme dissectors, zero `foreign-procedure` calls — safer than even +Rust-with-`unsafe` for that surface, at a throughput cost). But "Rust" is not +automatically "no C": watch for `bundled` C amalgamations (sqlite/duckdb), C++ JIT +engines (mozjs/SpiderMonkey), and C-backed compression. The FFI/marshalling glue +is where *our own* `unsafe` lives — [`ai-threat.md`](ai-threat.md) names it the #1 +attack surface — so it must stay minimal, audited, and ideally generated and +bounds-checked. + +**Rules out:** trading a removed C-CVE for a silently re-added bundled-C-CVE; +sprawling hand-written FFI; behavioral forks of the compiler; growing the trusted +computing base without accounting for it. + +**Current state:** stock-Chez-additive discipline; `libjerboa_native` favors +audited pure-Rust crates (`regex`, `rustls`, the `*-dalek` crypto, `wasmi`); +pure-Scheme parsing where throughput allows. **Aspirational:** an honest, written +TCB accounting and a continuously-audited, minimized FFI boundary. + +--- + +## Principle 5 — Be the safest substrate for AI-written code + +This stack is itself built with AI, and its threat model +([`ai-threat.md`](ai-threat.md)) is adversaries using AI to find bugs. Lean into +that: design so that AI-generated code is *safe by default* and AI is turned +loose to harden us before attackers do. + +**Why it's safer.** Capabilities (Principle 1) cap an autonomous agent's blast +radius — code an agent writes can't exceed the authority it was handed. +Structured-data builtins (Principle 2) remove the `eval`/exec/string-munging +footguns that AI most readily reproduces from training data. And the same AI +that hunts our bugs can generate dissectors, fuzz harnesses, and test suites at +scale (`jerboa-wafter`'s generated dissector corpus; `jerboa-lora`/`jerboa-code`) +— we should find our own CVEs first. + +**Rules out:** giving generated/automated code ambient authority; treating AI +codegen as a convenience while ignoring it as an attack surface. + +**Current state:** `jerboa-lora` (a Jerboa-aware model) and `jerboa-code` (an +agentic coding tool) exist; much of the ecosystem is AI-generated already. +**Aspirational:** capability-bounded execution contexts for agent-written code by +default, and a standing "AI red-team your own tree" practice. + +--- + +## What we explicitly reject + +- **Ambient authority** as the default — the root cause, not a detail. +- **The npm supply-chain model**: unsigned/mutable packages, deep unaudited + transitive trees, install-time code execution, typosquat-friendly namespaces. +- **Secrets in ambient reach**: in process arguments (`ps`-visible), in + shared filesystem namespaces, or in ambient env any same-uid process can read. +- **"Data as text" composition**: `eval`, output-parsing, and exec-sprawl as the + way tools talk to each other. +- **Stubbing or removing features to make a build pass** — a broken safety + guarantee is no guarantee; fix the root cause. +- **Silent C/C++ re-entry** via bundled amalgamations or JIT engines pulled in + without scrutiny. +- **"Memory-safe rewrite" as the finish line.** Safety without authority control + is half a solution. + +--- + +## Open tensions (unresolved — bring arguments) + +These are honest, unsettled trade-offs. Add to them; don't paper over them. + +1. **Capability ergonomics vs. friction.** Ambient authority is popular because + it's frictionless. If granting/threading capabilities is tedious, developers + route around it and the model rots. How do we make *least authority the easy + path* — sane defaults, inference, good tooling — without weakening it? +2. **Ecosystem reach vs. mandatory signing/no-install-code.** npm's strength is + reach. How do we bootstrap breadth while refusing the defaults that gave npm + its reach? Chicken-and-egg adoption is real. +3. **Isolation vs. integration when embedding** (e.g. jsh-in-emacs). In-process + gives one address space and one capability model with secrets never crossing a + boundary, but couples failures and makes tty/job-control hard; a PTY/process + boundary isolates faults but reintroduces the `SSH_AUTH_SOCK`-style bridge. +4. **wasm isolation vs. throughput.** `wasmi` is an interpreter; sandboxing hot + parsers in wasm costs performance. When is the isolation worth the slowdown + versus trusting an in-process pure-Rust parser? +5. **The FFI boundary is our `unsafe`.** Can the marshalling layer be largely + *generated* and bounds-checked so hand-written FFI (the #1 attack surface) + approaches zero? +6. **Honest TCB accounting.** The Chez runtime, additive primitives, every Rust + dep's `unsafe`, and the host kernel are all trusted. What is the real, written + trusted base — and what defends the cases where even the kernel is hostile + (hardware-held keys, remote attestation, "no secrets on the box")? +7. **Confused-deputy and capability leakage.** Ocap prevents forgery, not misuse: + handing a capability to the wrong code still leaks authority. What patterns + keep grants from drifting? + +--- + +## Glossary + +- **Authority** — what a piece of code is *able to do* (read a file, open a + socket, sign with a key), as distinct from whether it's memory-safe. +- **Ambient authority** — authority a program holds simply by running, without + being explicitly granted it (the default in C, shells, Node, and elisp). +- **POLA (Principle of Least Authority)** — every component runs with the minimum + authority it needs, and no more. +- **Object-capability (ocap)** — a model where an unforgeable token *is* the + authority; no token, no access. See [`capability.md`](capability.md). +- **Attenuation** — deriving a *narrower* capability from one you hold (read-only + from read-write); the only permitted transformation. You can never amplify. +- **Signing oracle** — a service that signs/authenticates on a key's behalf + without exposing the key (e.g. `ssh-agent`); access to the oracle *is* access + to the key's power. +- **Provenance** — verifiable evidence of who produced an artifact and that it + hasn't changed (signatures, content-addressing). +- **TCB (Trusted Computing Base)** — the set of components that must be correct + for security to hold; smaller is better. +- **Confused deputy** — a privileged component tricked into misusing its + authority on behalf of a less-privileged caller. + +--- + +## Related docs + +- [`capability.md`](capability.md) — the `(std capability)` object-capability implementation. +- [`ai-threat.md`](ai-threat.md) — adversarial (AI-assisted) threat model; FFI as #1 surface. +- [`security-reference.md`](security-reference.md) — hardening reference.