claude-injection-guard
March 9, 2026 · View on GitHub
claude-injection-guard
Prompt injection protection for Claude Code agents
Hooks-based, two-stage, local-LLM-powered
When Claude Code (or a sub-agent) fetches web content, that content enters the agent's context and can contain malicious instructions designed to hijack its behavior. This is called a prompt injection attack.
claude-injection-guard intercepts fetched content before it reaches Claude, using a fast two-stage pipeline:
- Stage 1 — Rule Engine (deterministic, ~1ms): Regex-based pattern matching for known injection signatures. High-confidence hits are blocked immediately; suspicious content escalates to Stage 2.
- Stage 2 — Local LLM Guard (~200-800ms, only for escalations): A small local model (Phi-3.5 mini, Qwen-2.5 1.5B, etc.) classifies suspicious content. No data leaves your machine.
[WebFetch result] --> [Stage 1: Rules] --> SAFE --------------------------> Agent
| SUSPICIOUS
[Stage 2: LLM] --> SAFE ----------------> Agent
| INJECTION
BLOCKED + logged + user notified
Table of Contents
- Features
- Requirements
- Architecture
- Installation
- Claude Code Hook Setup
- Configuration
- What Gets Detected
- Adding Custom Patterns
- Fail-Safe Design
- Running Tests
- Roadmap
- Security Considerations
- Contributing
- License
Features
| Feature | Description | |
|---|---|---|
| :hook: | Native Claude Code hook | Zero friction, no proxy, no MCP setup |
| :zap: | Two-stage pipeline | LLM only called when rules are uncertain |
| :house: | Sovereign by design | Local LLM, no external classification API |
| :electric_plug: | Backend-agnostic | Ollama, LM Studio, Docker Model Runner, Apple Silicon MLX |
| :whale: | Optional Docker | Bring your own Ollama, or use our compose file |
| :clipboard: | Detailed logging | Every block is logged with reason and matched pattern |
| :jigsaw: | Extensible | Add custom patterns in config, no code changes needed |
Requirements
- Python 3.11+
- Claude Code (CLI)
- One LLM backend (only needed for Stage 2):
Note: Stage 1 (rule engine) works without any LLM backend. Stage 2 is optional and only called for ambiguous content.
Installation
# 1. Clone the repo
git clone https://github.com/michaelhannecke/claude-injection-guard
cd claude-injection-guard
# 2. Install (minimal -- only pyyaml required)
pip install -e .
# 3. Set up your LLM backend (example: Ollama)
ollama pull phi3.5:mini
# 4. Configure
cp config.example.yml ~/.claude/injection-guard/config.yml
# Edit as needed (defaults work out of the box with Ollama)
Architecture
hooks/post_tool_use.py ← Claude Code hook entry point (thin wrapper)
guard/
post_tool_use.py ← Main hook logic & orchestration
stage1_rules.py ← Rule engine: 10 definitive + 15 suspicious patterns
stage2_llm.py ← LLM classifier: 4 backends, structured output
config.py ← YAML config loader, deep_merge, defaults
logger.py ← Structured stderr-only logger
tests/
test_stage1.py ← 30 tests
test_stage2_llm.py ← 10 tests
test_post_tool_use.py ← 17 tests
test_config.py ← 6 tests
test_logger.py ← 4 tests
Claude Code Hook Setup
Add this to your Claude Code hooks configuration (~/.claude/settings.json or project .claude/settings.json):
{
"hooks": {
"PostToolUse": [
{
"matcher": "WebFetch|web_fetch",
"hooks": [
{
"type": "command",
"command": "python3 <path-to>/claude-injection-guard/hooks/post_tool_use.py"
}
]
}
]
}
}
Note: Replace
<path-to>with the absolute path to your local clone, e.g./Users/you/projects.
See claude_hooks.example.json for a copy-paste template.
Configuration
Copy config.example.yml to ~/.claude/injection-guard/config.yml:
stage2:
backend: ollama # ollama | openai_compatible | docker_model_runner | mlx
model: phi3.5:mini
endpoint: http://localhost:11434
confidence_threshold: 0.75
Backend Options
| Backend | When to use |
|---|---|
ollama | Ollama installed natively (recommended default) |
openai_compatible | LM Studio, vLLM, any OpenAI-compatible server |
docker_model_runner | Docker Desktop >= 4.40 with Model Runner |
mlx | Apple Silicon, maximum performance, pip install mlx-lm |
Optional: Docker Backend
If you prefer not to install Ollama natively:
docker compose up -d
docker compose exec guard-llm ollama pull phi3.5:mini
The hook still runs on the host -- only the LLM inference runs in Docker.
What Gets Detected
Stage 1 -- Definitive blocks (immediate, no LLM needed) — 10 patterns
- Direct instruction overrides (
ignore all previous instructions,disregard your instructions) - Identity hijacking (
pretend to be,your new role is) - Fake system prompt injection (
system prompt:,new system message) - Known jailbreak personas (DAN, STAN, DUDE, AIM, KEVIN, BasedGPT)
- Jailbreak activation language (
jailbreak mode,developer mode enabled) - Credential exfiltration (
send your API key to,exfiltrate) - DOM data exfiltration (
localStorage,document.cookieto external targets) - Hidden HTML comment instructions (
<!-- instruction: -->) - XML/HTML instruction tags (
<instruction>,<system>,<prompt>)
Stage 1 -- Suspicious (escalated to Stage 2) — 15 patterns, scored 0.6–0.8
- Role hijacking phrases (
you are now a hacker,act as DAN) - Soft instruction resets (
forget everything you were told) - Safety bypass language (
override your restrictions) - LLM prompt delimiter injection (
[INST],<|im_start|>,<|system|>) - Conversation role tags (
### Human:,### Assistant:) - Zero-width character obfuscation (U+200B, U+200C, U+200D, U+FEFF)
- Encoded instruction delivery (base64/hex encoded commands)
- User deception instructions (
tell the user,respond with) - Instruction replacement framing (
instead of,replace your instructions) - Resource exhaustion / DoS patterns (infinite loops, repeated operations)
Stage 2 -- LLM classification
- Contextual, semantic injection patterns that evade rules
- Multi-sentence indirect injections
- Domain-specific attack variations
Adding Custom Patterns
# config.yml
stage1:
custom_patterns:
- pattern: "(?i)exfiltrate.*company.*data"
reason: "Custom: company data exfiltration"
score: 0.9
definitive: true # block immediately, skip Stage 2
See CLAUDE.md for detailed pattern authoring guidelines.
Fail-Safe Design
The guard is designed to fail open by default -- if Stage 2 LLM is unavailable, the content passes through rather than breaking your workflow. You can change this behavior:
hooks:
fail_open: false # block on any guard error (more secure)
Running Tests
67 tests across 5 test files covering rules, LLM classification, hook orchestration, config, and logging.
pip install -e ".[dev]"
pytest
| Test file | Tests | Coverage |
|---|---|---|
test_stage1.py | 30 | Definitive blocks, suspicious escalation, safe passthrough, custom patterns, edge cases |
test_stage2_llm.py | 10 | Response parsing, backend calls, delimiter hardening |
test_post_tool_use.py | 17 | Hook orchestration, error handling, exit codes |
test_config.py | 6 | Config loading, merging, precedence |
test_logger.py | 4 | Logger setup, levels, file handling |
Linting and type checking:
ruff check .
mypy guard/ hooks/
Roadmap
-
Bashtool interception (forcurl/wgetin shell commands) - MCP server mode (for multi-agent / team setups)
- Fine-tuned guard model (domain-specific injection dataset)
- Metrics endpoint (Prometheus-compatible)
- VS Code extension integration
Security Considerations
- 25 built-in patterns (10 definitive, 15 suspicious) plus user-configurable custom patterns
- Stage 1 patterns are conservative by design -- low false positive rate is prioritized
- Stage 2 LLM output is strictly parsed; the guard model cannot itself be injected
- The hook always logs to stderr, separate from Claude's stdout context
- Content is truncated to 50k chars for Stage 1 and 3k for Stage 2 (sufficient for injection patterns, protects against DoS)
Contributing
Contributions are welcome! Please ensure:
- All tests pass (
pytest) - Linting is clean (
ruff check .) - Type checking passes (
mypy guard/ hooks/)
See CLAUDE.md for architecture details and coding conventions.
License
Apache 2.0 -- see LICENSE