Skip to main content
This page condenses the project’s threat model. The full version, including the out-of-scope list and the private reporting channel, is SECURITY.md in the repository.

Reading is defensive

  • Source files are opened read-only, with O_NOFOLLOW, and re-checked for change while being read.
  • Symlinks get one of three outcomes: resolved inside a declared root, recorded as crossing between roots, or refused and listed. Never a silent skip.
  • Never-export rules keep whole classes of data out of any artifact: account and billing identity, machine fingerprints, trust decisions, OAuth locks, transcripts, history.

Redaction runs as five layers

The results are unioned, so a value caught by any layer is redacted:
  1. Declared secret positions, per surface.
  2. Key names.
  3. Clean-room provider patterns.
  4. Entropy at declared sinks.
  5. A deep walk of every parsed leaf, with one bounded decode.
A false positive costs a span, never a file. Recall is measured rather than claimed: the test suite seeds 212 fake credentials across every surface the tool reads and asserts the per-tier percentage on every build. Before any bundle reaches disk, the export gate re-scans the serialized bytes with the full detector. A high-confidence hit aborts the write with exit code 5 and nothing is written. There is no --include-secrets and no second “complete” file.
The honest limit — shapeless high-entropy secrets with nothing naming them are caught only by position. Detection is defence in depth, not a guarantee; read a bundle before you share it. The redaction report tells you exactly what was found, the class assigned to each value, and the confidence.

Imported bundles are hostile input

Entry names are canonicalized and re-checked on read. Traversal, case-fold collisions, duplicate entries, count and size bombs and tampered digests are refused with distinct exit codes rather than repaired. The import path is built so that consent is specific and revocable:
  • Nothing is applied without a recorded consent against a specific rendered diff.
  • Permission rules are additions; importing rule 3 resolves to an append and can never overwrite yours.
  • Everything executable lands written but inert, in the runtime’s own disabled idiom: "disabled": true, enabled = false, a hooks_disabled block, no execute bit. enable is a separate act that re-verifies the content hash recorded at import.
  • Writes are staged at 0700, snapshotted, fingerprint-re-verified immediately before each write, made through O_NOFOLLOW | O_EXCL temp files plus rename, and rolled back in full on any failure.

What the tests hold down

The named release gates each pin one property, and a failure names the property that broke: gate:purity (no I/O modules in the core), gate:secrets (the seeded-credential recall floor), gate:network (the stubbed-network cycle), gate:adversarial (hostile bundles), gate:noop (diff leaves the target home byte-identical), gate:compat, and gate:cutover. Every test runs against a committed fake home; nothing in the suite reads a real ~/.claude, ~/.codex or ~/.claude.json.