Skip to content

docs(design): legibility and the opaque box — why we embed an interpreter - #312

Open
hartsock wants to merge 1 commit into
mainfrom
docs/legibility-and-the-opaque-box
Open

docs(design): legibility and the opaque box — why we embed an interpreter#312
hartsock wants to merge 1 commit into
mainfrom
docs/legibility-and-the-opaque-box

Conversation

@hartsock

@hartsock hartsock commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

Adds docs/design/legibility-and-the-opaque-box.md, a position paper supplying
an answer the docs currently lack.

ADR 0005 demoted L2 from authoritative to convenience. That is correct as a
security claim and this PR does not soften it. But it left no written answer
to the obvious follow-up: then why carry a forked brush engine at all, instead
of shelling out to /bin/bash inside the L3 jail?

The answer is that enforcement and legibility are different products, and only
one of them is obtainable from bash -c
— which returns stdout, stderr, and an
exit code, and nothing whatsoever about what actually ran.

Three contributions, all absent from docs/ today (verified by grep: no hits for
"opaque box", "bash -c", "file descriptor", "preopen", "Capsicum"):

  1. The opaque box. Enumerates what bash -c structurally cannot give a
    harness: resolved argv, sound approval, attribution, atomic admission,
    structured denial, checkpointing, replay. Names approval-by-string-matching as
    a category error of the same shape ADR 0002 already rejects in prompt
    hygiene — eval, $(…), sh -c "$VAR", aliases and PATH shadowing defeat
    it, and no amount of pattern hardening fixes that.

  2. Two axes, not a renumbering. L1/L2/L3 keep their exact ADR 0001/0005
    meanings and answer "where does the guarantee rest?" A separate
    legibility / authority / enforcement triad answers "what is each part
    for?"
    and deliberately does not map one-to-one — authority (Caveats,
    meet, the mint-token ToolContext) is orthogonal to the layers rather than
    being one of them. Stated this way specifically to protect I9 honesty and to
    keep us out of the documented failure mode: Safe.pm, Java SecurityManager,
    Python rexec, Node vm, and Deno's permission flags each shipped a
    legibility layer labelled as an enforcement boundary, and each was bypassed.

  3. fds are already capabilities. Unix does not lack capabilities — a file
    descriptor is unforgeable, transferable and revocable, and fd-passing is ocap
    delegation. The defect is that Unix offers ambient authority alongside them.
    That yields a crisp structural target property: a confined child obtains a
    resource only by inheriting a handle (fd or preopen), never by pathname
    resolution against an ambient root.
    This is Capsicum, is what Landlock
    approximates (ADR 0009/0011), and explains why I6 is load-bearing rather
    than fussy — a held handle has no check-time/use-time resolution gap, which is
    exactly where the dangling-symlink fs_write escape lived.

Also reframes the ADR 0019 fork cost: the brush-ocap-* fork is not overhead we
tolerate for a weak second leash behind Landlock. Brush's real asset is not
bash compatibility — it is that brush-core/brush-parser are libraries
, so
every dispatch point becomes a hook. Compatibility is the ticket, not the prize.

Relates to #311 (quantitative ambient/OpenShell evaluation) — the "what is the
minimum effect surface an agent actually needs?" open question is that issue's
premise stated from the legibility side. Does not close it.

Test plan

Documentation only. No invariant moves, no enforcement claim changes, no code
touched
docs/design/ is the only path in the diff.

  • Every cross-reference audited against the source rather than asserted from
    memory. One error caught and fixed pre-commit: the canonicalization
    invariant is I6 (canonicalize no-follow, then test containment), not I7
    (no in-shell escape from the spawn funnel). I9 (no overclaiming), ADR
    0001/0002/0005/0007/0009/0010/0011/0019 references all verified.
  • Checked against existing docs to confirm the paper is additive and does not
    restate ADR 0001's layer model or ADR 0005's boundary decision.
  • git push pre-push hook green end to end — clippy, tests, package dry-run,
    and the Tier-3 Lean formal gate (157 security theorems replayed, base
    axioms clean).

Note

Low Risk
Documentation-only under docs/design/; explicitly states no invariant or enforcement claim changes.

Overview
Adds docs/design/legibility-and-the-opaque-box.md, a position paper that answers why a forked embedded shell remains worthwhile after ADR 0005 treats L2 as convenience, not the security boundary. It does not weaken L3 or move invariants.

The doc frames bash -c as an opaque box (stdout/stderr/exit only) and lists what that structurally cannot provide—resolved argv, sound string-based approval, attribution, atomic admission, structured denial, checkpointing, replay—and ties string-matching approval to the same category error ADR 0002 rejects for prompt hygiene.

It introduces an orthogonal legibility / authority / enforcement triad (distinct from L1–L3) so legibility and policy are not mislabeled as enforcement, with negative prior art (Safe.pm, Java SecurityManager, etc.). It reframes brush-core / brush-parser as in-process hooks (compatibility as adoption cost, inspectability as the prize) and adds an fd-as-capability / ambient-vs-handle lens for L3 (Capsicum, Landlock, I6, WASI preopens) plus open questions tied to #311.

Reviewed by Cursor Bugbot for commit d4c892f. Configure here.

…eter

WHAT: adds docs/design/legibility-and-the-opaque-box.md, a position paper
arguing the *value* case for the in-process shell engine, plus two framings
absent from the docs: the `bash -c` opaque-box enumeration, and the
observation that POSIX fds are already ocap-grade capabilities.

WHY: ADR 0005 demoted L2 from authoritative to "convenience." That is correct
as a security claim and should not be softened — but it left no written answer
to "then why carry a forked brush engine instead of shelling out to /bin/bash
inside the L3 jail?" This paper supplies that answer: enforcement and
legibility are different products and only one of them is obtainable from
`bash -c`, which returns stdout/stderr/exit code and nothing about what
actually ran.

Three contributions:

- The opaque box. Enumerates what `bash -c` structurally cannot give a
  harness: resolved argv, sound approval, attribution, atomic admission,
  structured denial, checkpointing, replay. Names approval-by-string-matching
  as a category error of the same shape ADR 0002 already rejects in prompt
  hygiene.

- Two axes, not a renumbering. L1/L2/L3 keep their ADR 0001/0005 meanings and
  answer "where does the guarantee rest?" A separate legibility / authority /
  enforcement triad answers "what is each part for?" and deliberately does not
  map one-to-one — authority (Caveats, meet) is orthogonal to the layers.
  Stated this way to protect I9 honesty and to keep us out of the documented
  failure mode: Safe.pm, Java SecurityManager, Python rexec, Node vm, Deno
  permission flags all shipped a legibility layer labelled as a boundary.

- fds are already capabilities. Unix does not lack capabilities; it offers
  ambient authority alongside them. Yields a structural target property — a
  confined child obtains a resource only by inheriting a handle, never by
  pathname resolution against an ambient root — which is Capsicum, is what
  Landlock approximates (ADR 0009/0011), and explains why I6 is load-bearing:
  a handle has no check-time/use-time resolution gap, which is precisely where
  the dangling-symlink fs_write escape lived.

No invariant moves and no enforcement claim changes. Documentation only.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@hartsock hartsock added the risk:low Low-risk change label Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:low Low-risk change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant