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 filePossible 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
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.
- Check if the security-guidance plugin is installed: `claude plugins list`
- Inspect the stderr output from the failed write — look for the rule name (e.g., 'child_process_exec', 'new_function_injection', 'xss_dom_write')
- Examine the hook source to confirm which substring triggered the match: `grep -n 'substrings' ~/.claude/plugins/security-guidance/hooks/security_reminder_hook.py`
- Check your file extension: if it's not .js/.ts/.jsx/.tsx/.mjs/.cjs, the match is a confirmed false positive under the fix
- 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
Workaround: Rewrite code patterns to avoid trigger substrings in non-JS files
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.
- Identify which substring triggered the block from the stderr warning: look for 'child_process_exec' rule and the matched substring
- For `RegExp.prototype.exec()` calls: replace with `String.prototype.match()` — note that `.match()` has different semantics for global regexes (returns all matches, not iterative)
- For `db.exec()` in documentation: rewrite as `db.execute()` with a note that the actual API is `.exec()`
- For `document.cookie`, `document.write`, `eval(`: rewrite with a comment explaining the pattern instead of literal code
- 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 positiveRisks
- 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
Workaround: Temporarily disable the security-guidance plugin until the fix ships
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.
- List installed plugins: `claude plugins list`
- Uninstall the security-guidance plugin: `claude plugins uninstall security-guidance`
- Complete your file writes that were being blocked (the hook no longer fires)
- Reinstall the plugin once done: `claude plugins install security-guidance`
- 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
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"
}