(adr-integrity-surfaces)= # 14. Keep the integrity-check surfaces separate and self-contained Date: 2026-06-14 ## Status Accepted ## Context The question "is Claude actually inside the bwrap shadow, and is the sandbox intact?" is answered in three places: - `sandbox-gate.sh` — the `UserPromptSubmit` fail-closed gate; a single `IS_SANDBOX=1` compare, lean by design. - `sandbox-verify.sh` — the `SessionStart` advisory verifier; `IS_SANDBOX` plus nine inline integrity assertions (no token leak, the gitconfig redirect is in effect, `/run/secrets` empty, …). - `.claude/commands/verify-sandbox.md` — the `/verify-sandbox` spec: the full 18-check deterministic battery (of which the verifier's nine are a subset) plus 10 LLM-driven adversarial probes. A natural architecture review flags the overlap as duplication and proposes extracting a shared integrity-check module so the surfaces cannot drift. That would conflict with the project's load-bearing self-containment principle — `claude-shadow` deliberately inlines its argv builder rather than sourcing a library "so the shadow is a single file you can read top-to-bottom" — and it would couple `verify-sandbox.md`, whose value is being a standalone, human-readable summary of the threat model with per-check rationale, to an implementation file. ## Decision Keep the three surfaces as separate, self-contained artifacts. Do not extract a shared integrity-check module, and do not mechanically de-duplicate `verify-sandbox.md` against `sandbox-verify.sh`. Credential isolation is *decided* in `bwrap_argv_build`'s `--clearenv` allow-list, not in the advisory `SessionStart` hook. Coverage for it therefore belongs at the argv-builder layer — negative assertions in `tests/bwrap_argv.sh` that a credential variable present in the environment never appears as a `--setenv` in the built argv — not in a tested copy of the hook. ## Consequences - The verifier's nine assertions stay a hand-maintained subset of the `/verify-sandbox` battery. Drift is accepted: the verifier is a third, advisory line of defence (the gate fail-closes on `IS_SANDBOX`; `/verify-sandbox` is the authoritative live battery), so a stale or buggy assertion costs at most a missed warning, not an open door. With an LLM maintainer, reconciling the subset against the full spec is cheap. - `verify-sandbox.md` stays optimised for human auditability. - A future architecture pass should not re-suggest a shared integrity-check module on DRY grounds alone — that trade-off was considered and declined here. - Follow-up, separate from this decision: add credential-scrub negative assertions to `tests/bwrap_argv.sh`, which today carries no `assert_not_contains` for `GH_TOKEN` / `GITHUB_TOKEN` / `ANTHROPIC_API_KEY` / `SSH_AUTH_SOCK`.