feat(hooks): memorable-user-prompt-hook.ts — consent gate, deny veto, HIGH-tier pre-scan, fail-closed receipt, trust envelope; runExternal in spawn-bin

The PR's hook exec'd the vendor binary with the full environment and
passed its stdout to Claude verbatim. It is now the house pattern: a
fail-open bash shim over a .ts twin that (1) gates on the memorable_recall
consent key, (2) skips repos whose trust policy is deny or read-only,
(3) scans the prompt (raw bytes and decoded string leaves) and refuses to
hand over a HIGH-tier credential shape, (4) writes a fail-closed egress
receipt naming the local executable it ran, (5) spawns the vendor in its
own process group with an allowlisted environment and group-kills it on
timeout, (6) accepts only a string additionalContext back, caps it at
8 KiB on a UTF-8 boundary and wraps it in the trust envelope, and (7)
records an `output-written` outcome after the stdout write completes.
One deadline clock (4.5 s) undercuts Claude Code's 5 s kill and bounds
both ledger writes through the new lockBudgetMs option on
writeReceipt/writeOutcome (default unchanged).

spawn-bin gains runExternal for external executables (detached group,
stderr drained, stdin EPIPE handled, stdout capped, win32 refused).
The wiring test pins the sink fail-closed and sweeps hosts/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-09-08 17:46:52 +00:00
co-authored by Claude Fable 5.1
parent 3034769813
commit d4dbeb6d42
9 changed files with 933 additions and 45 deletions
+1
View File
@@ -23,6 +23,7 @@ describe('claude hooks: Windows path + bin-spawn invariants', () => {
expect(src).toContain('export function repoRoot');
expect(src).toContain('export function binPath');
expect(src).toContain('export function runBin');
expect(src).toContain('export function runExternal');
expect(src).toContain('fileURLToPath');
});