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

> Learn how to use security guidance hooks in Claude Code to detect dangerous code patterns. Scan file paths and content to prevent risky edits effectively.

- Repository: [Anthropic/claude-code](https://github.com/anthropics/claude-code)
- Tags: how-to-guide
- Published: 2026-04-02

---

**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`](https://github.com/anthropics/claude-code/blob/main/hooks.json)** | Declares the hook type (`PreToolUse`) and maps it to the executable handler ([`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/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`](https://github.com/anthropics/claude-code/blob/main/plugins/security-guidance/hooks/hooks.json) |
| **[`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/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`](https://github.com/anthropics/claude-code/blob/main/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`](https://github.com/anthropics/claude-code/blob/main//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`](https://github.com/anthropics/claude-code/blob/main/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:

```json
{
  "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`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py) and append a new dictionary to the `SECURITY_PATTERNS` list:

```python
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:

```bash

# 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:

```bash
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`](https://github.com/anthropics/claude-code/blob/main/hooks.json) for registration, [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/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`](https://github.com/anthropics/claude-code/blob/main//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`](https://github.com/anthropics/claude-code/blob/main/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`](https://github.com/anthropics/claude-code/blob/main/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`](https://github.com/anthropics/claude-code/blob/main/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.