From f5772f40e31a5d19d9434d5bb7f9b0dcaa5a44ef Mon Sep 17 00:00:00 2001 From: truffle Date: Mon, 27 Apr 2026 09:18:01 +0000 Subject: [PATCH] hooks: strip heredoc bodies before dangerous-command scan Closes #100. The dangerous-command blocker regex-matches against the raw `command` string. Bodies of bash heredocs (cat > note.md < { expect(result).toEqual({ continue: true }); }); + + test("allows heredoc body that names a forbidden phrase as data", async () => { + const hook = createDangerousCommandBlocker(); + const callback = hook.hooks[0]; + + const result = await callback( + makeHookInput({ + hook_event_name: "PreToolUse", + tool_name: "Bash", + tool_input: { + command: "cat > note.md < { + const hook = createDangerousCommandBlocker(); + const callback = hook.hooks[0]; + + const result = await callback( + makeHookInput({ + hook_event_name: "PreToolUse", + tool_name: "Bash", + tool_input: { + command: + "node <<'NODEEOF'\n// repro: docker compose down should not be triggered\nconsole.log('ok');\nNODEEOF", + }, + }), + undefined, + { signal: new AbortController().signal }, + ); + + expect(result).toEqual({ continue: true }); + }); + + test("blocks real dangerous command outside the heredoc", async () => { + const hook = createDangerousCommandBlocker(); + const callback = hook.hooks[0]; + + const result = await callback( + makeHookInput({ + hook_event_name: "PreToolUse", + tool_name: "Bash", + tool_input: { + command: "cat > note.md < { + const hook = createDangerousCommandBlocker(); + const callback = hook.hooks[0]; + + const result = await callback( + makeHookInput({ + hook_event_name: "PreToolUse", + tool_name: "Bash", + tool_input: { + command: "cat <<-EOF\n\tharmless body\n\tEOF\nrm -rf /", + }, + }), + undefined, + { signal: new AbortController().signal }, + ); + + expect(result).toHaveProperty("decision", "block"); + }); }); diff --git a/src/agent/hooks.ts b/src/agent/hooks.ts index c39fd3e..127e96a 100644 --- a/src/agent/hooks.ts +++ b/src/agent/hooks.ts @@ -25,6 +25,19 @@ const DANGEROUS_COMMANDS: { pattern: RegExp; label: string }[] = [ { pattern: /kill\s+-9\s+1(\s|$)/, label: "kill init" }, ]; +// Heredoc bodies are data, not commands. Stripping them before the +// dangerous-command scan prevents prose, repros, and journal entries +// that quote a forbidden phrase from tripping the blocker. +// Matches `< string[]; @@ -59,8 +72,9 @@ export function createDangerousCommandBlocker(): HookCallbackMatcher { if (input.hook_event_name !== "PreToolUse") return { continue: true }; const command = (input.tool_input as Record)?.command; if (typeof command === "string") { + const scannable = stripHeredocBodies(command); for (const { pattern, label } of DANGEROUS_COMMANDS) { - if (pattern.test(command)) { + if (pattern.test(scannable)) { return { decision: "block", reason: `Blocked dangerous command: "${label}"`,