Security Guidance — Vulnerability Pattern Detection Hook

Vulnerability Pattern Detection Hook

PreToolUse hook monitoring 9 security vulnerability patterns before file edits. Uses session-scoped state to prevent warning fatigue while ensuring dangerous patterns are always flagged.

9
Patterns
1
Hook
3
Files
Python
Language

🛡️ Security🔍 Pattern Detection🐍 Python Hook⚠️ Context-Aware Warnings

📚 What is this & why does it exist?

Security Guidance is a PreToolUse hook that acts as a real-time security linter for Claude Code. Before any file edit is applied, the Python hook scans the target file path and content for 9 known vulnerability patterns — from command injection in GitHub Actions to eval() and pickle deserialization. It uses session-scoped state tracking to avoid showing the same warning repeatedly, and auto-cleans stale state after 30 days. The goal is to catch security anti-patterns at the moment they’re introduced, not days later in a code review.

💡 Real-world analogy

Think of a spell-checker that only checks for dangerous words — not typos, but things like “rm -rf /” or “eval(user_input)”. Just as a spell-checker underlines problematic words as you type them, the Security Guidance hook underlines dangerous patterns the moment Claude tries to write them into a file. And like a smart spell-checker that stops highlighting a word after you’ve explicitly accepted it, the state tracker remembers which warnings you’ve already seen so it doesn’t nag you about the same pattern twice in one session.

Architecture

The hook intercepts file-edit tool calls before they execute. The Python script parses the tool input, matches file paths and content against 9 security pattern rules, checks session state to avoid duplicate warnings, and returns advisory messages when new patterns are detected.

flowchart LR
    subgraph Trigger["PreToolUse Event"]
        A["Claude requests file edit"] --> B["PreToolUse fires"]
    end
    subgraph Hook["security_reminder_hook.py"]
        B --> C["Parse tool input"]
        C --> D["Match against 9 patterns"]
        D --> E{"New pattern\ndetected?"}
        E -->|"Yes"| F["Load session state"]
        F --> G{"Already\nwarned?"}
        G -->|"No"| H["Show warning + update state"]
        G -->|"Yes"| I["Skip — no warning"]
        E -->|"No"| I
    end
    subgraph Output["Result"]
        H --> J[" Security advisory in response"]
        I --> K["Silent pass-through"]
    end

Security Patterns

The hook monitors for 9 distinct vulnerability patterns, each with specific file path matching and substring detection rules:

#PatternFile Path MatchSubstring / IndicatorRisk
1Command Injection (GitHub Actions).github/workflows/${{ in run: blocksArbitrary command execution via PR titles/branch names
2child_process.exec*.js, *.tschild_process.execShell injection via unsanitized arguments
3new Function()*.js, *.tsnew Function(Dynamic code execution, equivalent to eval
4eval()*.js, *.ts, *.pyeval(Arbitrary code execution from string input
5dangerouslySetInnerHTML*.jsx, *.tsxdangerouslySetInnerHTMLXSS via unsanitized HTML injection in React
6document.write()*.js, *.ts, *.htmldocument.write(DOM-based XSS and document clobbering
7.innerHTML*.js, *.ts.innerHTMLXSS via unsanitized content assignment
8pickle*.pypickle.load, pickle.loadsArbitrary code execution during deserialization
9os.system*.pyos.system(Shell injection via unsanitized command strings

State Management

Session-Scoped Warning Tracking

To prevent warning fatigue, the hook maintains a state file that tracks which pattern–file combinations have already triggered warnings in the current session. The state file is stored alongside the hook script and is automatically cleaned up after 30 days of inactivity.

📋 State File Format

JSON dictionary mapping pattern keys to timestamps of when the warning was last shown.

  • Key format: {pattern_id}:{file_path}
  • Value: ISO 8601 timestamp
  • Location: .security-state.json next to hook

🗑️ Auto-Cleanup

Stale entries are removed on each hook invocation to keep the state file compact.

  • Entries older than 30 days are pruned
  • Cleanup runs before pattern matching
  • Zero-entry state file is deleted entirely
{
  "eval:src/utils/parser.js": "2026-03-25T14:30:00Z",
  "innerHTML:src/components/Render.tsx": "2026-03-25T14:32:00Z",
  "pickle:ml/model_loader.py": "2026-03-24T09:15:00Z"
}

Data Flow

sequenceDiagram
    participant Claude as Claude LLM
    participant CC as Claude Code CLI
    participant Hook as security_reminder_hook.py
    participant State as .security-state.json
    participant Dev as Developer

    Claude->>CC: Request: edit file src/api/handler.js
    CC->>Hook: PreToolUse(tool="file_edit", input={path, content})
    Hook->>Hook: Match path *.js against 9 patterns
    Hook->>Hook: Found: child_process.exec in content

    Hook->>State: Load existing warnings
    State-->>Hook: No prior warning for exec:src/api/handler.js

    Hook->>State: Write new entry with timestamp
    Hook-->>CC:  "child_process.exec detected — consider execFile() or spawn() with array args"

    CC-->>Dev: Show security advisory
    CC->>Claude: Proceed with file edit + advisory context

    Note over Claude,Dev: Later in same session...

    Claude->>CC: Request: edit same file again
    CC->>Hook: PreToolUse(tool="file_edit", input={path, content})
    Hook->>State: Load existing warnings
    State-->>Hook: Already warned for exec:src/api/handler.js
    Hook-->>CC: Silent pass-through (no duplicate warning)

Key Components

ComponentFileRole
security_reminder_hook.pyhooks-handlers/security_reminder_hook.pyPython PreToolUse hook — pattern matching engine with state tracking
plugin.json.claude-plugin/plugin.jsonPlugin manifest declaring the PreToolUse hook with file_edit matcher
README.mdREADME.mdPlugin documentation, pattern catalog, and configuration guide

Hook Implementation

PreToolUse Hook Configuration

PropertyValue
EventPreToolUse
Typecommand
Handlerpython3 hooks-handlers/security_reminder_hook.py
Matcherfile_edit|write_to_file
OutputJSON with optional message field for advisory text
#!/usr/bin/env python3
"""PreToolUse hook: scan file edits for 9 security vulnerability patterns."""

import json, sys, os, datetime

PATTERNS = [
    {"id": "gh-actions-injection", "path": ".github/workflows/", "substr": "${{",
     "msg": "GitHub Actions command injection — avoid interpolating untrusted input in run: blocks"},
    {"id": "child-process-exec", "path_ext": [".js", ".ts"], "substr": "child_process.exec",
     "msg": "child_process.exec is vulnerable to shell injection — use execFile() or spawn()"},
    {"id": "new-function", "path_ext": [".js", ".ts"], "substr": "new Function(",
     "msg": "new Function() enables dynamic code execution — equivalent to eval()"},
    {"id": "eval", "path_ext": [".js", ".ts", ".py"], "substr": "eval(",
     "msg": "eval() executes arbitrary code — use JSON.parse(), ast.literal_eval(), or a safe parser"},
    {"id": "dangerouslySetInnerHTML", "path_ext": [".jsx", ".tsx"], "substr": "dangerouslySetInnerHTML",
     "msg": "dangerouslySetInnerHTML enables XSS — sanitize with DOMPurify first"},
    {"id": "document-write", "path_ext": [".js", ".ts", ".html"], "substr": "document.write(",
     "msg": "document.write() enables DOM-based XSS — use textContent or createElement"},
    {"id": "innerHTML", "path_ext": [".js", ".ts"], "substr": ".innerHTML",
     "msg": ".innerHTML enables XSS — use textContent for text or sanitize HTML first"},
    {"id": "pickle", "path_ext": [".py"], "substr": "pickle.load",
     "msg": "pickle deserialization executes arbitrary code — use json or a safe format"},
    {"id": "os-system", "path_ext": [".py"], "substr": "os.system(",
     "msg": "os.system() is vulnerable to shell injection — use subprocess.run() with a list"},
]

STATE_FILE = os.path.join(os.path.dirname(__file__), ".security-state.json")

Interactions

ModuleInterfaceData Exchanged
Claude Code CLIHook registrationPreToolUse event with file_edit matcher triggers Python script
Claude LLMAdvisory injectionWarning messages added to tool response context
File systemState persistence.security-state.json tracks shown warnings with timestamps
DeveloperSecurity feedback⚠️ inline advisories when dangerous patterns are detected

Configuration Reference

ParameterDefaultDescription
hooks.PreToolUse[0].typecommandExecution type — shell command invoking Python
hooks.PreToolUse[0].commandpython3 hooks-handlers/security_reminder_hook.pyPath to the security scanner hook
hooks.PreToolUse[0].matcherfile_edit|write_to_fileRegex matching tool names that trigger the hook
State file location.security-state.jsonRelative to hook script directory
State TTL30 daysEntries older than 30 days are auto-pruned
Pattern count9Number of vulnerability patterns monitored

Directory Map

Source tree for Security Guidance
security-guidance/
├── .claude-plugin/plugin.json          ← Role: Plugin manifest with PreToolUse hook
├── README.md                           ← Role: Pattern catalog and usage docs
└── hooks-handlers/
    ├── security_reminder_hook.py        ← Role: Pattern detection + state engine
    └── .security-state.json             ← Role: Session warning state (auto-generated)

Warning Examples

Example Security Advisory

When Claude attempts to edit a file containing a detected pattern:

⚠️ Security Advisory — child_process.exec
The file src/api/handler.js uses child_process.exec() which passes arguments through a shell, making it vulnerable to command injection.

Recommended: Use child_process.execFile() or child_process.spawn() with an argument array to avoid shell interpretation of special characters.

GitHub Actions Pattern

# ⚠️ DANGEROUS — PR title is attacker-controlled
- name: Greet
  run: echo "Hello ${{ github.event.pull_request.title }}"

# ✅ SAFE — use an environment variable
- name: Greet
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: echo "Hello $PR_TITLE"