KBCodeKB
Unverified

Fix Claude Code Plugin False Positive: security-guidance Hook Blocks File Writes on 'exec(' Substring in Markdown, TypeScript, and Non-JS Files — Resolve security_reminder_hook.py Exit Code 2

The Claude Code security-guidance plugin's `child_process_exec` rule performs plain substring matching on the bare string `exec(` across ALL file types with no file-extension awareness. This causes false-positive blocks on legitimate code patterns in Markdown documentation, TypeScript files using `RegExp.prototype.exec()`, and any non-JavaScript file containing the text `exec(`. When the hook detects a match, it prints a command-injection warning to stderr and exits with code 2, blocking the `Write`/`Edit`/`MultiEdit` tool call entirely. The fix (merged in anthropics/claude-plugins-official#2074 on May 29, 2026) gates XSS and browser-DOM substring rules to JS-family file extensions only. Affected patterns include `exec(`, `document.cookie`, `document.write`, `eval(`, `window.eval`, `localStorage`, and `sessionStorage`. Until the updated plugin ships, workarounds include temporarily disabling the security-guidance plugin or rewriting affected code patterns to avoid the trigger substrings.

Symptoms

  • Write/Edit/MultiEdit tool calls fail with 'command injection' warning on stderr and exit code 2 when the file content contains 'exec('
  • False positive trigger on legitimate `RegExp.prototype.exec()` calls in TypeScript/JavaScript files
  • False positive trigger on code documentation containing `db.exec(schema)` or similar patterns in Markdown files
  • Hook blocks writes to non-code files (Markdown, text, configuration) despite no security risk
  • Error message references 'child_process_exec' rule in security_reminder_hook.py but the blocked content is not using child_process
  • Also affects other substring triggers: `document.cookie`, `document.write`, `eval(`, `window.eval`, `localStorage`, `sessionStorage` in non-JS files

Error signatures

command injection warning on stderr from security_reminder_hook.py
child_process_exec rule matched
Hook exit code 2 — tool call blocked
false positive on 'exec(' substring in non-JS file

Possible causes

  • The security-guidance plugin's `child_process_exec` rule (in `plugins/security-guidance/hooks/security_reminder_hook.py`, lines 70–90) includes bare `exec(` in its substring list alongside `child_process.exec` and `execSync(`. The `check_patterns()` function (lines 183–199) performs naive `if substring in content` matching on the raw file content with no file-extension gating. For `Write` tool calls, `extract_content_from_input()` (lines 204–214) returns the full `content` field, subjecting the entire file body to the substring match. On match, the hook prints the warning and calls `sys.exit(2)`, which blocks the tool call.
  • The `hooks.json` configuration wires the hook with `"matcher": "Edit|Write|MultiEdit"`, so it runs on every file write regardless of file extension. The fix gates XSS and browser-DOM rules to JS-family extensions (`.js`, `.ts`, `.jsx`, `.tsx`, `.mjs`, `.cjs`) so they no longer fire on Markdown, text, or config files.
  • Other substring-based rules in the same plugin have identical false-positive behavior on non-JS files, including `document.cookie`, `document.write`, `eval(`, `window.eval`, `localStorage`, and `sessionStorage` triggers.

Solutions

Diagnose: Verify the security-guidance hook is the blocker and identify which specific rule and substring fired

risk: lowagentpublished

Confirm that the security-guidance plugin's `security_reminder_hook.py` is the cause of the blocked write by inspecting the stderr output, checking the hook source code for the rule definition, and verifying your file type matches the false-positive pattern. This diagnostic step helps distinguish this bug from genuine security warnings or other hook-related issues.

  1. Check if the security-guidance plugin is installed: `claude plugins list`
  2. Inspect the stderr output from the failed write — look for the rule name (e.g., 'child_process_exec', 'new_function_injection', 'xss_dom_write')
  3. Examine the hook source to confirm which substring triggered the match: `grep -n 'substrings' ~/.claude/plugins/security-guidance/hooks/security_reminder_hook.py`
  4. Check your file extension: if it's not .js/.ts/.jsx/.tsx/.mjs/.cjs, the match is a confirmed false positive under the fix
  5. If the hook fired on a JS-family file with a legitimate use (e.g., `RegExp.prototype.exec()`), this is also a known false positive addressed by PR #2074

Commands

claude plugins list 2>&1 | grep -i security
grep -n -A5 'child_process_exec\|substrings' ~/.claude/plugins/security-guidance/hooks/security_reminder_hook.py 2>/dev/null
file /path/to/blocked/file
echo 'test exec(' > /tmp/test_hook.md && claude -p 'write /tmp/test_hook.md with content: test exec(' 2>&1 | grep -i 'security\|hook\|blocked\|exit'

Risks

  • Inspecting hook source files requires filesystem access to the Claude Code plugins directory (~/.claude/plugins/)
  • If the security-guidance plugin is not the blocker, a different hook, Claude Code's built-in safety filters, or the Anthropic API content filter may be responsible
  • The grep pattern on hook source may fail if the plugin was installed to a non-standard location — check `claude config get pluginsPath` for the custom path

Verification

  • Step 1: Run `claude plugins list` → expect: 'security-guidance' in installed plugins output
  • Step 2: Run `grep -c 'exec(' ~/.claude/plugins/security-guidance/hooks/security_reminder_hook.py 2>/dev/null` → expect: ≥1 (confirms 'exec(' is in the substring list)
  • Step 3: Check the blocked file's extension: `file /path/to/blocked/file` → expect: non-JS-family extension confirms false positive (e.g., 'ASCII text', 'Markdown document')
  • Step 4: After applying the workaround (disable plugin or rewrite code), the Write operation should succeed with exit code 0 and no stderr from the hook
0 verified0 failed

Workaround: Rewrite code patterns to avoid trigger substrings in non-JS files

risk: lowgithubpublished

If you cannot disable the plugin (e.g., in a shared environment), rewrite the triggering code patterns to use alternative APIs that don't contain the blocked substrings. Key substitutions: `RegExp.prototype.exec()` → `String.prototype.match()`, and in documentation, replace literal API calls with descriptive alternatives. This is a temporary measure — the PR #2074 fix gates these rules to JS files only.

  1. Identify which substring triggered the block from the stderr warning: look for 'child_process_exec' rule and the matched substring
  2. For `RegExp.prototype.exec()` calls: replace with `String.prototype.match()` — note that `.match()` has different semantics for global regexes (returns all matches, not iterative)
  3. For `db.exec()` in documentation: rewrite as `db.execute()` with a note that the actual API is `.exec()`
  4. For `document.cookie`, `document.write`, `eval(`: rewrite with a comment explaining the pattern instead of literal code
  5. For `localStorage` and `sessionStorage`: use bracket notation or `window['localStorage']` syntax as a workaround

Config examples

# Before (BLOCKED — RegExp.prototype.exec triggers 'exec(' rule):
const match = /^Regenerate (\w+):/.exec(changeDesc)?.[1];

# After (works — String.prototype.match avoids the substring):
const match = changeDesc?.match(/^Regenerate (\w+):/)?.[1];
# Before (BLOCKED in Markdown documentation):
const db = new Database('app.db');
db.exec(schema);

# After (rewritten for docs — note actual API in comment):
const db = new Database('app.db');
db.execute(schema); // Actual API is db.exec() — temporary rename to avoid plugin false positive

Risks

  • Rewriting code in documentation reduces accuracy — the rewritten code no longer matches the actual API, which could mislead readers
  • `String.prototype.match()` returns different data structures than `RegExp.prototype.exec()` — for global regexes, `.match()` returns an array of all matches instead of iterative results with `lastIndex`
  • Bracket notation workarounds (e.g., `window['localStorage']`) may trigger linter warnings or style violations
  • This is purely a temporary workaround — the fix gates rules to JS files only, so documentation accuracy is fully restored with the update

Verification

  • Step 1: Identify the trigger substring from stderr output → expect: rule name (e.g., 'child_process_exec') and matched substring (e.g., 'exec(')
  • Step 2: Rewrite the triggering pattern using the alternative API → expect: rewritten code contains no trigger substrings (verify with grep: `echo "$content" | grep -o 'exec('` returns nothing)
  • Step 3: Retry the Write/Edit operation → expect: file written successfully with no hook stderr output and exit code 0
0 verified0 failed

Workaround: Temporarily disable the security-guidance plugin until the fix ships

risk: lowgithubpublished

The simplest workaround is to temporarily uninstall or disable the security-guidance plugin while writing files that contain trigger substrings. The fix (PR #2074, merged May 29, 2026) gates XSS and browser-DOM substring rules to JS-family files only, so false positives on Markdown, text, config, and other non-JS files will stop once the updated plugin ships.

  1. List installed plugins: `claude plugins list`
  2. Uninstall the security-guidance plugin: `claude plugins uninstall security-guidance`
  3. Complete your file writes that were being blocked (the hook no longer fires)
  4. Reinstall the plugin once done: `claude plugins install security-guidance`
  5. Check if the updated plugin with the fix is available: `claude plugins update security-guidance`

Commands

claude plugins list
claude plugins uninstall security-guidance
claude plugins install security-guidance
claude plugins update security-guidance

Risks

  • Disabling the security-guidance plugin removes legitimate security warnings for all file types — re-enable it immediately after completing the blocked writes
  • If you frequently write documentation with code samples containing trigger substrings, you may need to keep the plugin disabled until the fix is available via `claude plugins update`

Verification

  • Step 1: Run `claude plugins list` → expect: 'security-guidance' appears in the installed plugins list
  • Step 2: Run `claude plugins uninstall security-guidance` → expect: confirmation message indicating plugin was uninstalled
  • Step 3: Attempt the previously-blocked Write operation (e.g., write a Markdown file containing 'db.exec(schema)') → expect: file written successfully with no hook exit code 2 or stderr warning
  • Step 4: Reinstall and verify fix: `claude plugins install security-guidance && claude plugins update security-guidance` → expect: plugin reinstalled and updated to latest version
0 verified0 failed

Agent JSON

Canonical machine-readable representation of this issue:

{
  "issue_id": "3a6471f6-d611-4c78-a416-6c00af355979",
  "slug": "fix-claude-code-plugin-false-positive-security-guidance-hook-blocks-file-writes-on-exec-substring-in-markdown-typescript-ozv5bu",
  "verification_status": "unverified",
  "canonical_json": "https://codekb.dev/v1/issues/fix-claude-code-plugin-false-positive-security-guidance-hook-blocks-file-writes-on-exec-substring-in-markdown-typescript-ozv5bu"
}
← Back to all issuesPowered by CodeKB