From ec7b43b94b35d763c2d0b27fa899508ba91043f7 Mon Sep 17 00:00:00 2001 From: elhoim Date: Sun, 26 Jul 2026 17:05:41 +0000 Subject: [PATCH 1/2] fix(security): treat procfs secret reads as credential paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CREDENTIAL_PATHS guards ~/.ssh keys, ~/.aws/credentials, GPG keyrings and .env files, but procfs reaches the same secrets by another route: a process's environ holds its API keys and tokens, and mem/maps expose its address space. Because `cat` is in SEARCH_TOOLS, classifyCommand() took the read-only-command allow branch for `cat /proc/self/environ` and returned `allow` — printing every secret in the environment with no prompt. Add a procfs entry to CREDENTIAL_PATHS so those reads return `neutral` and defer to the native permission prompt. Covers literal pids, `self` and the shell forms ($$, $PID, ${PID}) that reach the same file. Ordinary procfs reads (cpuinfo, meminfo, loadavg, /proc/sys/*) are unaffected and still auto-allow. (cherry picked from commit d1f7df7dae934a808dbc2b2d2df3e2e61ba09cda) --- LifeOS/install/hooks/lib/safety-classifier.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/LifeOS/install/hooks/lib/safety-classifier.ts b/LifeOS/install/hooks/lib/safety-classifier.ts index 0fba1d8515..dd5028e88c 100755 --- a/LifeOS/install/hooks/lib/safety-classifier.ts +++ b/LifeOS/install/hooks/lib/safety-classifier.ts @@ -90,6 +90,13 @@ export const CREDENTIAL_PATHS: readonly RegExp[] = [ /\.aws\/credentials\b/, /\.gnupg\/(private-keys|secring)/, /(^|\s|=|@|"|')([~\$\{\}\/A-Za-z0-9._-]+\/)?\.env(\.[a-zA-Z0-9_-]+)?(\s|$|"|')/, + // procfs reaches the same secrets the entries above guard: `environ` is the + // process environment (API keys, tokens), `mem`/`maps` are its address space. + // `cat` is in SEARCH_TOOLS, so without this `cat /proc/self/environ` takes the + // read-only-command allow at classifyCommand() and prints every secret in the + // environment without a prompt. Covers literal pids, `self`, and the shell + // forms that reach the same file (`$$`, `$PID`, `${PID}`). + /\/proc\/(?:self|\d+|\$\$|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)\/(?:environ|mem|maps|cmdline)\b/, ]; export const READ_ONLY_COMMAND_PATTERNS: readonly RegExp[] = [ From 90470fce3d76d23cae055c3dcd4c8e610e2dcace Mon Sep 17 00:00:00 2001 From: elhoim Date: Sun, 26 Jul 2026 17:12:31 +0000 Subject: [PATCH 2/2] fix(security): route MCP and ToolSearch results through the injection scan Safety.hook.ts documents the intended wiring at isAttackerWritableSource: "the settings.json PostToolUse matcher only routes WebFetch/WebSearch + the qualifying mcp__ names here", with the in-code gate as the defensive backstop. The matcher only ever carried the WebFetch/WebSearch half. The ToolSearch and mcp mail/drive/calendar branches of that predicate were unreachable, so third-party-authored text arriving over those tools was never given the data-not-instructions framing or the injection-shape scan. Merge the two matcher entries into one alternation covering all four classes. The in-code gate still does the precise filtering, so unrelated MCP servers cost one neutral passthrough and nothing more. Also add dropbox to the gate: file content pulled from a sync service is attacker-writable for the same reason drive already is (shared folders, file requests). (cherry picked from commit da744721e0e668d25a935b1e2cde30b0f3f2ebaf) --- LifeOS/install/hooks/Safety.hook.ts | 5 ++++- LifeOS/install/hooks/hooks.json | 12 +----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/LifeOS/install/hooks/Safety.hook.ts b/LifeOS/install/hooks/Safety.hook.ts index d040f57859..0061a7ecd1 100755 --- a/LifeOS/install/hooks/Safety.hook.ts +++ b/LifeOS/install/hooks/Safety.hook.ts @@ -296,7 +296,10 @@ function isAttackerWritableSource(toolName: string): boolean { // ToolSchemaLoaded hook event) is an upstream Claude Code feature request. if (toolName === "ToolSearch") return true; if (!toolName.startsWith("mcp__")) return false; - return /gmail|mail|drive|calendar|inbox/i.test(toolName); + // dropbox joins the list for the same reason drive is on it: file content + // pulled from a sync service is third-party-authored text that a stranger + // can put there (shared folders, file requests). + return /gmail|mail|drive|calendar|inbox|dropbox/i.test(toolName); } function annotate(input: { tool_name?: string; tool_response?: unknown }): void { diff --git a/LifeOS/install/hooks/hooks.json b/LifeOS/install/hooks/hooks.json index ff8b570a12..38fdc175e1 100644 --- a/LifeOS/install/hooks/hooks.json +++ b/LifeOS/install/hooks/hooks.json @@ -62,17 +62,7 @@ ] }, { - "matcher": "WebFetch", - "hooks": [ - { - "type": "command", - "command": "$HOME/.claude/hooks/Safety.hook.ts", - "timeout": 5 - } - ] - }, - { - "matcher": "WebSearch", + "matcher": "WebFetch|WebSearch|ToolSearch|mcp__.*([Mm]ail|[Dd]rive|[Cc]alendar|[Ii]nbox|[Dd]ropbox)", "hooks": [ { "type": "command",