How to Use Security Guidance Hooks for Detecting Dangerous Code Patterns in Claude Code

Security guidance hooks for detecting dangerous code patterns intercept PreToolUse operations in Claude Code by scanning file paths and content against configurable rules, emitting warnings to stderr and exiting with code 2 to block risky edits until acknowledged.

The security guidance plugin ships with Claude Code as a built-in safeguard that runs before file-modifying tools execute. Located in the anthropics/claude-code repository, the hook system evaluates edits against a JSON-defined ruleset without requiring separate linting steps or manual code review.

Architecture of the Security Guidance System

The plugin consists of four tightly integrated components that handle registration, pattern matching, state persistence, and maintenance.

Component Role Source Path
hooks.json Declares the hook type (PreToolUse) and maps it to the executable handler (security_reminder_hook.py). When Claude Code dispatches an edit request, it reads this registry to determine which hooks to invoke. plugins/security-guidance/hooks/hooks.json
security_reminder_hook.py The Python implementation that parses the JSON payload from stdin, extracts the session_id, tool name, target file path, and proposed content. It iterates over hardcoded SECURITY_PATTERNS—checking path_check lambdas first, then substrings matches—and prints warnings to stderr before exiting with code 2 to abort the operation. plugins/security-guidance/hooks/security_reminder_hook.py
State management (load_state, save_state) Persists a set of previously shown warnings to ~/.claude/security_warnings_state_<session_id>.json. This deduplication prevents the same security reminder from spamming the user repeatedly during a single Claude Code session. Same file as above
Debug logging (debug_log) Writes timestamped diagnostic messages to /tmp/security-warnings-log.txt for plugin developers troubleshooting hook execution. Same file as above
Cleanup routine (cleanup_old_state_files) Removes state files older than 30 days (10% chance per run) to prevent stale data buildup. Same file as above
Environment toggle (ENABLE_SECURITY_REMINDER) When set to "0", the hook short-circuits immediately and returns exit code 0, effectively disabling all security checks for that session. Same file as above

How the Hook Intercepts Edits

When Claude Code receives a request such as Edit file X, the execution flow follows this pipeline:

  1. Hook Discovery: Claude Code reads plugins/security-guidance/hooks/hooks.json to discover the PreToolUse hook type and locate the handler command.
  2. Payload Delivery: It executes python3 ${CLAUDE_PLUGIN_ROOT}/hooks/security_reminder_hook.py with a JSON payload written to stdin containing the session_id, tool_name, tool_input.file_path, and tool_input.content (or tool_input.new_string for Write/MultiEdit operations).
  3. Pattern Evaluation: The hook loads SECURITY_PATTERNS—an array of rule objects where each entry contains a ruleName, optional path_check (lambda function), substrings (list of dangerous tokens), and a reminder string.
  4. Match Detection: For each rule, it first evaluates the path_check against the file path, then scans the content for any substrings entry. If the rule specifies path_check: ".github/workflows/" and the content includes ${{ github.event.issue.title }}, the match triggers.
  5. Deduplication Check: The hook constructs a unique key (<file_path>-<rule_name>), loads the session-specific warning set from ~/.claude/security_warnings_state_<session_id>.json, and skips printing if the key exists.
  6. Blocking Output: On a new match, it prints the reminder text to stderr, saves the updated state, and exits with code 2. This non-zero exit code aborts the tool operation, forcing the user to address the warning before proceeding.

Configuration and Custom Rules

The hooks.json Registration File

The hook registers itself through a declarative JSON file that Claude Code parses at startup:

{
  "hooks": [
    {
      "hook": "PreToolUse",
      "commands": [
        "python3",
        "${CLAUDE_PLUGIN_ROOT}/hooks/security_reminder_hook.py"
      ]
    }
  ]
}

This registration ensures the hook runs before any Write, Edit, or MultiEdit tool executes, providing real-time security guidance without manual linting steps.

Modifying SECURITY_PATTERNS

To extend detection capabilities, edit security_reminder_hook.py and append a new dictionary to the SECURITY_PATTERNS list:

SECURITY_PATTERNS = [
    # ... existing rules ...

    {
        "ruleName": "hardcoded_secret",
        "substrings": ["API_KEY=", "SECRET="],
        "reminder": "⚠️ Hardcoded secret detected. Move secrets to a secret manager or environment variable."
    }
]

Rules support two detection mechanisms:

  • Path-based filtering: Use the path_check key with a lambda string (e.g., ".github/workflows/") to restrict the rule to specific directories or file extensions.
  • Content scanning: The substrings array lists literal tokens that, when found in the proposed edit content, trigger the warning.

State Management and Deduplication

The hook maintains session-specific state to avoid overwhelming users with repetitive warnings. When a rule matches:

  1. The hook computes a stable key from the file path and rule name.
  2. It loads existing keys from ~/.claude/security_warnings_state_<session_id>.json.
  3. If the key exists, the hook exits silently with code 0, allowing the edit to proceed.
  4. If the key is new, the hook persists it to disk and prints the warning, ensuring one reminder per unique pattern per session.

The cleanup_old_state_files function periodically purges state files older than 30 days, preventing unbounded disk usage across long-running Claude Code installations.

Enabling and Disabling the Hook

The security guidance hook is enabled by default. Control it via environment variable:


# Explicitly enable (default behavior)

export ENABLE_SECURITY_REMINDER=1

# Disable for the current session (allows dangerous edits without warnings)

export ENABLE_SECURITY_REMINDER=0

When ENABLE_SECURITY_REMINDER is set to "0", the hook exits immediately with code 0, bypassing all pattern checks. Use this only in isolated environments where you deliberately accept the risk.

Debugging Hook Execution

To inspect hook behavior manually, pipe a test payload into the script:

cat <<EOF | python3 plugins/security-guidance/hooks/security_reminder_hook.py
{
  "session_id": "debug-session-001",
  "tool_name": "Write",
  "tool_input": {
    "file_path": ".github/workflows/deploy.yml",
    "content": "run: echo ${{ github.event.issue.title }}"
  }
}
EOF

This outputs the matching rule's reminder to stderr and exits with code 2, simulating the blocking behavior Claude Code enforces during actual edits.

Summary

  • Security guidance hooks for detecting dangerous code patterns operate as PreToolUse interceptors in Claude Code, evaluating file paths and content against configurable SECURITY_PATTERNS before edits apply.
  • The system comprises hooks.json for registration, security_reminder_hook.py for pattern matching, and session-based state files in ~/.claude/ to deduplicate warnings.
  • Rules support both path_check lambdas (directory filtering) and substrings arrays (content scanning), with matches printing to stderr and exiting code 2 to block operations.
  • Enable or disable the hook via the ENABLE_SECURITY_REMINDER environment variable, and extend detection by appending dictionaries to SECURITY_PATTERNS in the source file.
  • Debug output writes to /tmp/security-warnings-log.txt, while automatic cleanup removes state files older than 30 days.

Frequently Asked Questions

How do security guidance hooks integrate with Claude Code's plugin system?

According to the anthropics/claude-code source, the plugin system discovers hooks by reading plugins/security-guidance/hooks/hooks.json at startup. This file declares the PreToolUse hook type and specifies the Python executable path. When Claude Code prepares to invoke a tool like Write or Edit, it serializes the tool input to JSON and pipes it through the registered hook command. If the hook exits with a non-zero status (specifically code 2), Claude Code surfaces the stderr output in the chat interface and aborts the tool invocation, effectively embedding security checks directly into the edit pipeline without requiring external CI/CD steps.

What types of dangerous patterns can the hook detect?

The default SECURITY_PATTERNS configuration in security_reminder_hook.py targets high-risk code constructs including GitHub Actions workflow injections (e.g., ${{ github.event.issue.title }} in run: commands), unsafe Node.js patterns (child_process.exec, eval, new Function), and React's dangerouslySetInnerHTML. Each pattern defines either a path_check constraint (such as .github/workflows/) or content substrings (such as exec( or rm -rf), allowing granular control over when warnings trigger. You can extend this list to cover organization-specific risks—like hardcoded API keys or deprecated cryptographic functions—by appending new rule dictionaries to the SECURITY_PATTERNS array.

Why does the hook exit with code 2 specifically?

Exit code 2 serves as a semantic signal to Claude Code that the operation should be blocked due to a security concern rather than a system error. The security_reminder_hook.py implementation explicitly calls sys.exit(2) after printing the warning to stderr, distinguishing security failures from other plugin crashes or environment issues. This convention allows Claude Code to differentiate between a failed hook (which might retry) and a security intervention (which requires user acknowledgment). The session state tracking in ~/.claude/security_warnings_state_<session_id>.json ensures that even if the user reissues the same edit command, the hook recognizes the previously shown warning and permits the operation, preventing permanent lockouts while maintaining initial friction against risky changes.

Can I run security guidance hooks in CI/CD pipelines or pre-commit checks?

While designed for interactive Claude Code sessions, the hook's architecture supports headless execution because it reads JSON from stdin and returns standard exit codes. You can invoke python3 plugins/security-guidance/hooks/security_reminder_hook.py programmatically by constructing the expected JSON payload containing session_id, tool_name, and tool_input. However, the built-in state management assumes a single-session context tied to ~/.claude/, so CI implementations should either clear state files between runs or modify the load_state function to use ephemeral storage. For pre-commit workflows, consider adapting the SECURITY_PATTERNS logic into a standalone linter that reads staged files rather than JSON payloads, though this replicates functionality already available in dedicated security scanners.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →