# How to Create Security Hooks in Claude Code to Prevent Dangerous Operations

> Learn to create security hooks in Claude Code to prevent dangerous operations. Use the PreToolUse hook framework to inspect and abort file modifications.

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

---

**Claude Code provides a PreToolUse hook framework that intercepts file modification tools, inspects incoming paths and content via JSON stdin, and aborts operations by exiting with code 2 when dangerous patterns are detected.**

Claude Code, Anthropic's AI coding assistant, ships with a robust plugin architecture in the `anthropics/claude-code` repository that enables developers to implement security hooks blocking risky operations before execution. The built-in security-guidance plugin demonstrates this pattern by intercepting Edit, Write, and MultiEdit commands to scan for unsafe code patterns. By creating custom hooks that leverage the PreToolUse event, you can enforce organizational security policies and prevent potentially destructive file changes.

## Architecture of the Claude Code Hook System

The hook framework relies on three coordinated components that process tool execution requests before they reach the filesystem.

### Core Components

**Hook Definition ([`hooks.json`](https://github.com/anthropics/claude-code/blob/main/hooks.json))** — Located at [`plugins/security-guidance/hooks/hooks.json`](https://github.com/anthropics/claude-code/blob/main/plugins/security-guidance/hooks/hooks.json), this file declares which Claude Code events trigger your validation logic and specifies the executable command to run.

**Plugin Manifest ([`plugin.json`](https://github.com/anthropics/claude-code/blob/main/plugin.json))** — Found at [`plugins/security-guidance/.claude-plugin/plugin.json`](https://github.com/anthropics/claude-code/blob/main/plugins/security-guidance/.claude-plugin/plugin.json), this JSON file makes the hook discoverable by Claude Code, registering it as a valid plugin within the ecosystem.

**Hook Script ([`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py))** — Implemented in [`plugins/security-guidance/hooks/security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/plugins/security-guidance/hooks/security_reminder_hook.py), this Python script contains the actual validation logic. It loads configurable **SECURITY_PATTERNS**, inspects incoming file paths and content, maintains per-session state to avoid repetitive warnings, and exits with code `2` to block operations or `0` to permit them.

### Execution Flow

When Claude Code receives a tool request, the hook framework executes the following sequence:

1. Claude Code receives a tool request (e.g., `Edit` on a TypeScript file).
2. The `PreToolUse` entry in [`hooks.json`](https://github.com/anthropics/claude-code/blob/main/hooks.json) matches the tool name against the pattern `Edit|Write|MultiEdit`.
3. Claude Code launches the command defined in the hook configuration: `python3 ${CLAUDE_PLUGIN_ROOT}/hooks/security_reminder_hook.py`.
4. The hook script reads the JSON payload from **stdin**, containing `session_id`, `tool_name`, and `tool_input`.
5. The script extracts the **file path** and **content** fields (which vary by tool type).
6. The `check_patterns()` function iterates over `SECURITY_PATTERNS`, executing path-check lambdas or searching for forbidden substrings in the content.
7. If a pattern matches, the hook checks a per-session **state file** at `~/.claude/security_warnings_state_<session_id>.json` to determine if this warning was already shown.
8. On first encounter, the script prints the `reminder` text to **stderr** and exits with code `2`, signaling Claude Code to abort the tool.
9. If no patterns match, the script exits with code `0` and the original tool proceeds.

The framework also performs automatic cleanup of state files older than 30 days to prevent unbounded storage growth.

## Implementing a Custom Security Hook

Creating a security hook requires three files: the executable script, the hook definition, and the plugin manifest.

### The Hook Script

Your hook script must read JSON from stdin and exit with specific codes. Here is a minimal Python implementation that blocks usage of `eval()`:

```python
#!/usr/bin/env python3
import json, sys

def main():
    payload = json.load(sys.stdin)
    tool = payload.get("tool_name")
    
    if tool not in ["Edit", "Write", "MultiEdit"]:
        sys.exit(0)

    # Extract content based on tool type

    content = ""
    if tool == "Edit":
        content = payload["tool_input"].get("new_string", "")
    elif tool == "Write":
        content = payload["tool_input"].get("content", "")
    elif tool == "MultiEdit":
        content = " ".join(e.get("new_string", "") for e in payload["tool_input"].get("edits", []))

    # Security check

    if "eval(" in content:
        print("⚠️ Avoid using eval() – it can lead to code injection.", file=sys.stderr)
        sys.exit(2)  # Abort the tool

    
    sys.exit(0)  # Allow the tool

if __name__ == "__main__":
    main()

```

Save this as [`my_security_hook.py`](https://github.com/anthropics/claude-code/blob/main/my_security_hook.py) in your plugin's `hooks/` directory.

### Configuring hooks.json

Register your hook by creating [`hooks.json`](https://github.com/anthropics/claude-code/blob/main/hooks.json) with a PreToolUse matcher:

```json
{
  "description": "Blocks unsafe eval() usage",
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/my_security_hook.py"
          }
        ],
        "matcher": "Edit|Write|MultiEdit"
      }
    ]
  }
}

```

The `${CLAUDE_PLUGIN_ROOT}` variable resolves to your plugin directory at runtime.

### Registering the Plugin Manifest

Create [`.claude-plugin/plugin.json`](https://github.com/anthropics/claude-code/blob/main/.claude-plugin/plugin.json) to make Claude Code aware of your hook:

```json
{
  "name": "my-security-plugin",
  "version": "0.1.0",
  "description": "Example security plugin that blocks eval() usage",
  "author": { "name": "Your Name", "email": "you@example.com" }
}

```

Place this file at the root of your plugin directory. Claude Code automatically discovers plugins containing this manifest file.

## Extending Built-in Security Patterns

The reference implementation in [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py) uses a `SECURITY_PATTERNS` list defining detection rules. Each pattern entry contains:

- **ruleName**: Unique identifier for the rule
- **pathCheck** (optional): Lambda function evaluating the file path
- **substrings**: List of forbidden strings to search for in content
- **reminder**: Warning message displayed to the user

To add a new pattern blocking `process.env` access in shell scripts, modify the `SECURITY_PATTERNS` list:

```python
{
    "ruleName": "process_env_leak",
    "substrings": ["process.env"],
    "reminder": "⚠️ Direct use of `process.env` can expose secrets. Prefer passing needed values explicitly."
}

```

The hook evaluates all patterns on every file modification, ensuring comprehensive coverage without manual intervention.

## Summary

- **Claude Code hooks** intercept tool execution via the `PreToolUse` event before files are modified.
- Hooks receive JSON input on stdin containing `session_id`, `tool_name`, and `tool_input`, and must exit with code `0` (allow) or `2` (abort).
- The three required files are: the hook script (logic), [`hooks.json`](https://github.com/anthropics/claude-code/blob/main/hooks.json) (registration), and [`plugin.json`](https://github.com/anthropics/claude-code/blob/main/plugin.json) (manifest).
- The reference implementation at [`plugins/security-guidance/hooks/security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/plugins/security-guidance/hooks/security_reminder_hook.py) demonstrates pattern matching, session state management, and warning deduplication.
- The framework is **language-agnostic**—any executable respecting the stdin/exit code contract works.
- State files track shown warnings per session to avoid repetitive alerts, with automatic cleanup after 30 days.

## Frequently Asked Questions

### What exit codes should a Claude Code security hook use?

Exit code `0` allows the tool to proceed normally, while exit code `2` aborts the operation and displays the stderr output to the user as a warning. Any non-zero exit code blocks execution, but code `2` is the conventional signal for policy violations in the Claude Code hook framework.

### Which tools support PreToolUse hooks in Claude Code?

The `PreToolUse` event supports file modification tools including `Edit`, `Write`, and `MultiEdit`. You can target specific tools using regex matchers in [`hooks.json`](https://github.com/anthropics/claude-code/blob/main/hooks.json) (e.g., `Edit|Write|MultiEdit` or individual tool names) to apply security checks only where relevant.

### How does Claude Code prevent repetitive security warnings?

The [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py) implementation creates session-specific state files at `~/.claude/security_warnings_state_<session_id>.json` to track which warnings have already been displayed. Before emitting a warning, the hook checks this state file and only blocks the operation if it's the first occurrence in that session, preventing alert fatigue while maintaining security.

### Can I write security hooks in languages other than Python?

Yes, the Claude Code hook framework is **language-agnostic**. Any executable binary or script that can read JSON from stdin, perform logic, and exit with the appropriate status codes (0 or 2) will function correctly. The reference implementation uses Python, but Bash, Node.js, Go, or compiled binaries work equally well provided they follow the input/output contract.