Skip to content

Instantly share code, notes, and snippets.

@jstockdi
Created January 30, 2026 16:05
Show Gist options
  • Select an option

  • Save jstockdi/aeaae7f5d01bd3f18d2e40f63633a930 to your computer and use it in GitHub Desktop.

Select an option

Save jstockdi/aeaae7f5d01bd3f18d2e40f63633a930 to your computer and use it in GitHub Desktop.
Implementing PII Validation Hooks in Claude Code
# Implementing PII Validation Hooks in Claude Code
Hooks are shell commands that run at specific points in Claude Code's workflow. For PII protection, you'll use PreToolUse hooks to inspect and block operations before they execute.
## Step 1: Create the PII Validation Script
Create a file at `.claude/hooks/pii_validator.py`:
```python
#!/usr/bin/env python3
"""
PII Validator Hook for Claude Code
Blocks file writes and commands that contain potential PII.
"""
import json
import sys
import re
# PII patterns to detect
PII_PATTERNS = {
'ssn': r'\b\d{3}-\d{2}-\d{4}\b',
'credit_card': r'\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b',
'email': r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
'phone': r'\b(\+1[- ]?)?\(?\d{3}\)?[- ]?\d{3}[- ]?\d{4}\b',
'api_key': r'\b(sk-[a-zA-Z0-9]{20,}|api[_-]?key[=:]\s*["\']?[a-zA-Z0-9]{20,})\b',
'aws_key': r'\b(AKIA[0-9A-Z]{16})\b',
'password': r'(password|passwd|pwd)\s*[=:]\s*["\']?[^\s"\']+',
}
def detect_pii(text: str) -> list[tuple[str, str]]:
"""Scan text for PII patterns. Returns list of (pii_type, matched_value)."""
findings = []
for pii_type, pattern in PII_PATTERNS.items():
matches = re.findall(pattern, text, re.IGNORECASE)
for match in matches:
# Redact the actual value for safety
redacted = match[:4] + '***' if len(match) > 4 else '***'
findings.append((pii_type, redacted))
return findings
def main():
# Read hook input from stdin
input_data = json.load(sys.stdin)
tool_name = input_data.get('tool_name', '')
tool_input = input_data.get('tool_input', {})
content_to_check = ""
context = ""
# Extract content based on tool type
if tool_name in ('Write', 'Edit', 'MultiEdit'):
content_to_check = tool_input.get('content', '')
context = f"file: {tool_input.get('file_path', 'unknown')}"
elif tool_name == 'Bash':
content_to_check = tool_input.get('command', '')
context = "bash command"
elif tool_name == 'WebFetch':
# Check if URL contains sensitive data
content_to_check = tool_input.get('url', '')
context = "web request URL"
# Scan for PII
findings = detect_pii(content_to_check)
if findings:
# Block the operation and provide feedback
pii_types = ', '.join(set(f[0] for f in findings))
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"userFacingText": f"🛑 PII BLOCKED: Detected {pii_types} in {context}",
"assistantFacingText": (
f"SECURITY BLOCK: Your {tool_name} operation was blocked because it "
f"contains potential PII ({pii_types}). Please remove or redact "
f"sensitive information before proceeding. Detected patterns: "
f"{[f[1] for f in findings]}"
)
}
}
print(json.dumps(output))
sys.exit(2) # Exit code 2 = block with feedback
# Allow the operation
sys.exit(0)
if __name__ == "__main__":
main()
```
## Step 2: Make it Executable
```bash
chmod +x .claude/hooks/pii_validator.py
```
## Step 3: Configure the Hook in Settings
Add to your `.claude/settings.json`:
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit|Bash|WebFetch",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/pii_validator.py\"",
"timeout": 10
}
]
}
]
}
}
```
## Step 4: Register the Hook
In Claude Code, run:
```
/hooks
```
Then review and approve the new hook configuration.
## How It Works
| Exit Code | Behavior |
|-----------|----------|
| 0 | Allow the operation to proceed |
| 2 | Block and provide feedback to Claude |
| Non-zero (other) | Block silently |
The hook receives JSON on stdin with the tool details:
```json
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content here..."
}
}
```
## Extended Example: Adding Custom Patterns
For your KYC/fintech context, you might want to add patterns like:
```python
PII_PATTERNS = {
# ... existing patterns ...
'sin': r'\b\d{3}[- ]?\d{3}[- ]?\d{3}\b', # Canadian SIN
'passport': r'\b[A-Z]{2}\d{6}\b',
'account_number': r'\baccount[_\s]?(num|number|#)?[:\s]*\d{8,12}\b',
'routing_number': r'\brouting[_\s]?(num|number|#)?[:\s]*\d{9}\b',
}
```
---
Would you like me to expand this with additional validation logic, like checking for PII in file paths or adding logging/alerting capabilities?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment