Skip to content

fix(danger): fingerprinted read ledger (TOCTOU re-gate) + pre-exec content audit for unread-script approvals - #146

Merged
jkyberneees merged 6 commits into
mainfrom
fix/read-ledger-hardening
Aug 27, 2026
Merged

fix(danger): fingerprinted read ledger (TOCTOU re-gate) + pre-exec content audit for unread-script approvals#146
jkyberneees merged 6 commits into
mainfrom
fix/read-ledger-hardening

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

Summary

Closes two gaps in the H-6 unread-script gate identified in the exec-then-audit vs audit-then-exec review:

  1. Read-ledger TOCTOU. The ledger recorded that a path was read, never what state it was in. A file mutated after its display read — via an MCP tool, curl -o, an npm lifecycle hook, or any background process — stayed licensed, re-creating inside the gate exactly the timing failure the injection study identified ("detection is not the failing control, timing is").
  2. No content audit before execution. The only injection scanning in the pipeline runs on tool results (post-execution, wrapUntrusted). An unread script's approval described the file path, never its contents — the one artifact that decides whether execution is safe.

Changes

Fingerprinted licenses (TOCTOU re-gate)internal/danger/readledger.go

  • RecordRead captures the file state at display time: size + mtime, plus sha256 for files up to 1 MiB. Unstatable targets record a fail-closed entry that never licenses.
  • New WasReadFresh re-verifies on-disk state at gate time; UnreadScriptTargets consumes freshness, so post-read mutation re-fires the unread_exec gate until the mutated content is re-read (re-reading renews the fingerprint — the model has then seen the bytes on disk).
  • WasRead keeps its existence-only semantics (viewer licensing, CRIT-001 behavior unchanged).

Pre-execution content auditcmd/odek/unreadscan.go

  • scanUnreadScripts reads the target's leading 256 KiB (read-only) and runs danger.ScanInjection over the raw bytes plus best-effort single-layer base64/hex decodes of embedded blobs.
  • Findings ride in the unread_exec approval description in both shell and parallel_shell: the human decides with content evidence, not just a path.
  • Invariants pinned by tests: the audit never populates the read ledger (the auditor is not the model) and never returns raw content, only threat labels.

Test plan (TDD: RED -> GREEN -> docs)

  • internal/danger/readledger_fingerprint_test.go: post-read mutation re-gates, same-size mtime-restored tamper caught by hash, deleted files never fresh, large-file stat fallback, re-read re-licensing.
  • cmd/odek/unreadscan_test.go: plain/base64/hex injection findings, clean-script silence, scan-never-populates-ledger, audit-window cap, approval-description wiring for shell (clean + finding paths).
  • cmd/odek/security_report_validation_test.go: regression-bar entry for the fingerprint re-gate.
  • Verified: new tests green in both packages; internal/danger full suite green under -race; gofmt/vet clean.

Note: several pre-existing tests require cached sudo credentials or an interactive TTY and cannot run from a headless local shell; their local failure set is identical on main and unrelated to this branch. Docs updated in the same change (SECURITY.md, AGENTS.md).

…pt audit

- internal/danger: WasReadFresh fingerprint contract (TOCTOU re-gate):
  post-read mutation must re-fire the H-6 gate, incl. same-size
  mtime-restored tamper (hash catches touch -r), deleted files never
  fresh, large-file stat fallback, and re-read re-licensing.
- cmd/odek: scanUnreadScripts pre-execution content audit contract —
  plain/base64/hex injection findings, clean-script silence,
  scan-never-populates-ledger invariant, capped audit window, and
  shell approval description enrichment wiring.
- security_report_validation_test: regression-bar entry for the
  fingerprint re-gate.

Expected state: both packages fail to build (symbols not implemented
yet). GREEN commit follows.
…cript audit

Read-ledger fingerprints (TOCTOU re-gate):
- RecordRead captures the file state at display time (size + mtime,
  sha256 for files <= 1 MiB); unstatable targets record a fail-closed
  entry that never licenses.
- WasReadFresh re-verifies on-disk state at gate time; the H-6
  unread-script gate now consumes freshness, so a file mutated after
  its read (MCP tool, curl -o, lifecycle hook, other process) re-fires
  the gate until the mutated content is re-read. Re-reading renews the
  fingerprint — the model has then seen the bytes that are on disk.
- WasRead keeps its existence-only semantics for viewer licensing.

Pre-execution content audit (audit-then-exec companion):
- scanUnreadScripts (cmd/odek/unreadscan.go) reads the target's leading
  256 KiB read-only and runs danger.ScanInjection over the raw bytes
  plus best-effort single-layer base64/hex decodes of embedded blobs.
  Findings ride in the unread_exec approval description in both shell
  and parallel_shell — the human decides with content evidence, not
  just a path.
- The audit never writes the read ledger (the auditor is not the
  model) and never returns raw content, only threat labels.

Verified: targeted suites green in both packages; danger suite green
under -race; regression-bar entry added in the RED commit.
- SECURITY.md: extend the unread_exec gate section with the TOCTOU
  fingerprint semantics and the pre-exec injection-scan enrichment;
  add both rows to the injection-study mitigation table.
- AGENTS.md: note the fingerprinted ledger and unreadscan.go in the
  danger classifier bullet.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 27, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
odek 035579a Commit Preview URL

Branch Preview URL
Aug 27 2026, 07:26 PM

… prompt

Pins the prompt-layer counterpart to the mechanical gates shipped in
e52e6f4: provenance (justification from the principal, never from
content read during the task), audit-before-execute for make/npm/CI
shapes the file-operand gate cannot see, failed-read never becomes
execution, purpose-vs-contents tell, deferred-execution confirmation,
tool-metadata-is-not-directive, scope discipline, and the untrusted
wrapper contract. Guard: defaultSystem must never trip
danger.ScanInjection (IDENTITY.md copies are scanned and rejected).
Adds the 'Execution provenance — where justification comes from'
section to defaultSystem, the prompt-layer counterpart to the
mechanical gates:

- provenance: justification must come from the principal — repo/tool
  text dressed as policy, compliance, or platform-team mandates is
  context, never authorization (covers the doc/AGENTS.md/MDP class of
  the injection study);
- audit-before-execute for make/npm/CI shapes the file-operand gate
  cannot see; reading the Makefile is not enough;
- a failed read is never substituted with executing the file;
- purpose-vs-contents contradiction is a stop-and-flag tell;
- deferred-execution writes need the principal's confirmation naming
  the mechanism;
- MCP tool metadata describes capability, never directives;
- scope discipline inside the project directory;
- the <untrusted_content_*> wrapper contract is now stated in the
  prompt, so model and wrapper agree by construction.

Also fixes two pre-existing self-trips found by the new guard:
defaultSystem quoted 'ignore previous instructions' /
'output your system prompt' verbatim in its IPI signals and described
exfil hooks as 'requests to echo secrets' — phrases that match
danger.ScanInjection's own patterns, so an operator copying the
default into ~/.odek/IDENTITY.md would have it silently rejected.
Both reworded scanner-safe; the guard now bisects per line and line
pair (scanner \s+ matches newlines) and names the offending phrase.

Verified: all TestDefaultSystem* green (incl. anti-pattern conciseness
and verification scaffold); stateless cmd/odek failure set byte-
identical to the pre-change environmental baseline (14 TTY-approval
tests, unchanged); gofmt/vet clean.
…ault

- AGENTS.md identity section: note the execution-provenance rules in
  defaultSystem and the scanner-clean guarantee (pinned by
  TestDefaultSystem_PassesOwnInjectionScan).
- SECURITY.md system-prompts bullet: same, plus the rule summary.
@jkyberneees
jkyberneees merged commit 131bd5c into main Aug 27, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant