claude-code - ✅(Solved) Fix [BUG] security-guidance hook blocks markdown/doc writes containing "exec(" substring [1 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#46720Fetched 2026-04-12 13:34:49
View on GitHub
Comments
2
Participants
2
Timeline
6
Reactions
0
Author
Timeline (top)
labeled ×4commented ×2

The security-guidance plugin's security_reminder_hook.py has a child_process_exec rule whose substring list includes bare "exec(". The hook runs plain substring matching over the entire file content with no awareness of file extension, so any Markdown (or other non-code) file containing the text exec( — for example a code sample like db.exec(schema) in documentation about better-sqlite3 — causes the PreToolUse hook to print the command-injection warning to stderr and exit with code 2, blocking the Write tool call.

Root Cause

The security-guidance plugin's security_reminder_hook.py has a child_process_exec rule whose substring list includes bare "exec(". The hook runs plain substring matching over the entire file content with no awareness of file extension, so any Markdown (or other non-code) file containing the text exec( — for example a code sample like db.exec(schema) in documentation about better-sqlite3 — causes the PreToolUse hook to print the command-injection warning to stderr and exit with code 2, blocking the Write tool call.

Fix Action

Fix / Workaround

Workarounds currently available

PR fix notes

PR #47514: fix(security-guidance): skip doc files for substring checks

Description (problem / solution / changelog)

Summary

  • skip substring-based security heuristics for documentation and plaintext file extensions
  • keep path-based workflow checks and source-file warnings intact
  • add focused tests for docs false positives, source-file warnings, and workflow path checks

Testing

  • python3 -m unittest plugins/security-guidance/hooks/test_security_reminder_hook.py

Closes #46720

Changed files

  • plugins/security-guidance/hooks/security_reminder_hook.py (modified, +17/-1)
  • plugins/security-guidance/hooks/test_security_reminder_hook.py (added, +45/-0)
RAW_BUFFERClick to expand / collapse

Summary

The security-guidance plugin's security_reminder_hook.py has a child_process_exec rule whose substring list includes bare "exec(". The hook runs plain substring matching over the entire file content with no awareness of file extension, so any Markdown (or other non-code) file containing the text exec( — for example a code sample like db.exec(schema) in documentation about better-sqlite3 — causes the PreToolUse hook to print the command-injection warning to stderr and exit with code 2, blocking the Write tool call.

Location

plugins/security-guidance/hooks/security_reminder_hook.py

  • Lines 70–90: child_process_exec rule definition.
  • Line 71: "substrings": ["child_process.exec", "exec(", "execSync("] — bare "exec(" is the offender.
  • Lines 183–199: check_patterns() performs if substring in content over the raw content with no file-extension gate.
  • Line 196: the offending naive match.
  • Lines 204–214: extract_content_from_input() — for Write, returns the full content field, so the entire Markdown body is subjected to the substring match.
  • Lines 272–273: on match, prints the reminder to stderr and sys.exit(2), which blocks the tool call.

plugins/security-guidance/hooks/hooks.json wires the hook with "matcher": "Edit|Write|MultiEdit", so it runs on every file write regardless of extension.

Reproduction

  1. Install the security-guidance plugin.
  2. Ask Claude Code to write any .md file whose body contains the text exec( — for example a documentation file that describes SQLite usage with a snippet like db.exec(schema), or prose that quotes child.exec(...) in an example block.
  3. The hook matches the child_process_exec rule, prints the command-injection reminder to stderr, and exits 2.
  4. The Write is blocked.

Workarounds currently available

  • Write a placeholder body first, then use small Edit calls whose individual new_string values don't contain exec( (the Edit path of extract_content_from_input only scans new_string, not the whole file).
  • Disable the hook globally via ENABLE_SECURITY_REMINDER=0 (nuclear — loses all other rules too).
  • Retry the same file path in the same session: the (file_path, rule_name) state key is recorded on first match, so the second attempt passes. Confusing and inconsistent.

Impact

  • Blocks legitimate documentation writes that mention exec( in any context — prose, code fences, references to SQLite's db.exec(), shell-scripting docs, hook-internals docs, and so on.
  • Surfaces as an opaque block: the user sees a command-injection warning unrelated to what they are writing.
  • The react_dangerously_set_html, document_write_xss, innerHTML_xss, eval( and pickle rules share the same latent issue: any documentation file that quotes those identifiers in code samples will be blocked.

Suggested fixes

  1. Scope by extension (preferred). Add a path_check to the child_process_exec rule (and the other content-based code rules) that skips common documentation/plaintext extensions: .md, .mdx, .rst, .txt, .adoc, and similar. Keeps the rule in force for real source files.
  2. Tighten the substring list. Drop bare "exec(" and keep only "child_process.exec" and "execSync(". Loses coverage of aliased imports (const { exec } = require('child_process')) but kills the false positive.
  3. Global extension gate in check_patterns(). Short-circuit all content-based rules when the file path has a documentation extension. Substring matching over prose is inherently lossy; a path-level gate fixes the whole class at once.

Option 1 is the cleanest localised fix. Option 3 is the cleanest systemic fix.

Notes

  • Issues #40172 and #46449 concern a different failure mode in the same hook (hardcoded python3 on Windows). This report is unrelated to platform and reproduces on macOS.

Attribution

This false positive was detected by a human while using Claude Code. The hook source investigation, root-cause analysis, and the filing of this issue were performed by Claude Opus 4.6 (1M context) under human direction.

extent analysis

TL;DR

The most likely fix is to modify the child_process_exec rule to scope by file extension, skipping common documentation extensions like .md and .txt, to prevent false positives.

Guidance

  • Identify the child_process_exec rule in security_reminder_hook.py and consider adding a path_check to filter out documentation files.
  • Alternatively, tighten the substring list by removing bare "exec(" to reduce false positives, although this may lose coverage of certain use cases.
  • Consider implementing a global extension gate in check_patterns() to short-circuit content-based rules for documentation files.
  • Review the hooks.json file to understand how the hook is wired and consider adjusting the matcher to exclude certain file types.

Example

# Example of how to add a path_check to the child_process_exec rule
if file_path.endswith(('.md', '.mdx', '.rst', '.txt', '.adoc')):
    return  # skip documentation files

Notes

The suggested fixes aim to address the false positive issue without introducing new problems. However, it's essential to test and verify the changes to ensure they don't break existing functionality.

Recommendation

Apply the workaround by scoping the child_process_exec rule by file extension, as it is the cleanest localized fix. This approach allows for a targeted solution that minimizes the impact on other rules and functionality.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING