Blocking Email Addresses in Prompts with Claude Code Hooks
What You'll Learn
Create a hook that automatically detects and blocks email addresses in prompts before they reach Claude, providing a privacy safety net that works on every single prompt submission.
Prerequisites
- Claude Code installed and working
- UV installed (Python package manager)
- Basic understanding of JSON configuration
- A terminal/command prompt open
Steps
Step 1: Create Project Directory Structure
Create the project directory and subdirectories:
mkdir blocking-emails-in-prompts
cd blocking-emails-in-prompts
mkdir -p .claude/hooks
mkdir -p logs
Verify the structure:
ls -la
Step 2: Configure the Hook
Create .claude/settings.json
with the UserPromptSubmit hook configuration:
cat > .claude/settings.json << 'EOF'
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "uv run .claude/hooks/prompt_email_blocker.py"
}
]
}
]
}
}
EOF
Step 3: Create the Email Detection Hook
Create .claude/hooks/prompt_email_blocker.py
with the email blocking logic:
cat > .claude/hooks/prompt_email_blocker.py << 'EOF'
#!/usr/bin/env python3
"""
# /// script
# requires-python = ">=3.8"
# dependencies = []
# ///
Claude Code UserPromptSubmit Hook: Email Address Detection and Blocking
This hook runs before Claude processes any user prompt and blocks prompts
that contain email addresses to prevent accidental PII exposure.
Privacy Guarantee: Prompts with emails never reach Claude.
"""
import json
import sys
import re
from datetime import datetime
from pathlib import Path
def detect_emails(content):
"""
Detect email addresses in text content.
Returns:
dict: {
'has_emails': bool,
'count': int,
'redacted_emails': list of redacted email examples
}
"""
# Email regex pattern
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
# Find all email matches
emails = re.findall(email_pattern, content, re.IGNORECASE)
# Redact emails for safe logging (show only domain)
redacted_emails = []
for email in emails[:3]: # Show max 3 examples
if '@' in email:
username, domain = email.split('@', 1)
redacted = f"***@{domain}"
redacted_emails.append(redacted)
return {
'has_emails': len(emails) > 0,
'count': len(emails),
'redacted_emails': redacted_emails
}
def log_hook_execution(event_type, data):
"""Log hook execution to JSON file."""
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
log_entry = {
"timestamp": datetime.now().isoformat(),
"event_type": event_type,
"data": data
}
log_file = log_dir / "prompt_blocker.json"
# Append to existing log file
logs = []
if log_file.exists():
try:
with open(log_file, 'r') as f:
logs = json.load(f)
except:
logs = []
logs.append(log_entry)
# Keep only last 50 entries
logs = logs[-50:]
with open(log_file, 'w') as f:
json.dump(logs, f, indent=2)
def main():
"""Main hook execution logic."""
try:
# Read hook input from stdin
hook_input = json.load(sys.stdin)
# Extract the user's prompt
prompt = hook_input.get("prompt", "")
session_id = hook_input.get("session_id", "")
if not prompt:
log_hook_execution("no_prompt", {
"session_id": session_id,
"reason": "no prompt content found"
})
sys.exit(0)
# Run email detection on the prompt
email_result = detect_emails(prompt)
if email_result['has_emails']:
# Emails detected in prompt - block it
error_message = f"""
š« EMAIL ADDRESSES DETECTED IN PROMPT - BLOCKED
Found {email_result['count']} email address(es) in your prompt:
Examples: {', '.join(email_result['redacted_emails'])}
Your prompt contains email addresses and has been blocked to protect privacy.
To proceed:
1. Remove or redact the email addresses from your prompt
2. Use placeholder emails like 'user@example.com' instead
3. Replace emails with descriptions like '[team email]' or '[customer email]'
Privacy Protection: Your prompt was not sent to Claude.
Original prompt length: {len(prompt)} characters
"""
log_hook_execution("prompt_blocked", {
"session_id": session_id,
"email_count": email_result['count'],
"redacted_emails": email_result['redacted_emails'],
"prompt_length": len(prompt)
})
print(error_message.strip(), file=sys.stderr)
sys.exit(2) # Exit code 2 blocks the prompt from reaching Claude
else:
# No emails detected - allow prompt
log_hook_execution("prompt_allowed", {
"session_id": session_id,
"prompt_length": len(prompt),
"reason": "no emails detected"
})
# Optional: Show confirmation for transparency
# print(f"ā
Prompt cleared (no emails detected)", file=sys.stderr)
sys.exit(0)
except Exception as e:
# Log unexpected errors
log_hook_execution("hook_error", {
"error": str(e),
"error_type": type(e).__name__
})
# Don't block on hook errors - let Claude proceed
print(f"Hook error (allowing prompt): {e}", file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()
EOF
Make the hook executable:
chmod +x .claude/hooks/prompt_email_blocker.py
Step 4: Test the Hook
Test with a safe prompt (should work normally):
claude "Help me write a Python function to calculate the factorial of a number"
Test with an email in the prompt (should be blocked):
claude "Please email the project update to john.doe@company.com and let me know when it's sent"
Step 5: Examine the Hook Logs
Check what the hook has recorded:
cat logs/prompt_blocker.json
Example Usage
Here's a complete example showing the hook in action:
# Navigate to your project
cd blocking-emails-in-prompts
# Test with a prompt containing multiple emails
claude "Send meeting invites to alice@company.com, bob@partner.org, and carol@client.net for tomorrow's review"
Expected Output
When an email is detected, you'll see:
š« EMAIL ADDRESSES DETECTED IN PROMPT - BLOCKED
Found 3 email address(es) in your prompt:
Examples: ***@company.com, ***@partner.org, ***@client.net
Your prompt contains email addresses and has been blocked to protect privacy.
To proceed:
1. Remove or redact the email addresses from your prompt
2. Use placeholder emails like 'user@example.com' instead
3. Replace emails with descriptions like '[team email]' or '[customer email]'
Privacy Protection: Your prompt was not sent to Claude.
Original prompt length: 91 characters
Tips & Variations
- Whitelist specific domains: Modify the hook to allow certain domains like @example.com
- Extend to other PII: Add detection for phone numbers, SSNs, or credit card numbers
- Team deployment: Share the
.claude/
directory with your team via version control - Custom messages: Tailor the error message to your organization's privacy policies
Troubleshooting
-
Issue: Hook not executing Solution: Ensure the hook file is executable (
chmod +x
) and UV is installed -
Issue: Legitimate technical discussions about emails are blocked Solution: Use placeholders like "user@example.com" or "[email]" in your prompts
-
Issue: Logs directory not created Solution: The hook creates it automatically, but ensure you have write permissions
-
Issue: Hook allows emails through Solution: Check that
.claude/settings.json
is properly formatted and in the right location
Technical Deep Dive
š Detailed Code Documentation
For a comprehensive breakdown of the code implementation, including:
- Email detection logic and regex patterns
- Logging system implementation
- Error handling strategies
- Alternative implementation approaches
Real-World Applications
Enterprise Development
- Prevent developers from including real customer emails in prompts
- Block internal email addresses from being sent to external AI services
- Protect employee contact information during troubleshooting
Compliance Requirements
- GDPR compliance for EU email addresses
- Corporate data protection policies
- Audit trail for compliance reporting
Development Best Practices
- Train developers to use placeholder emails
- Prevent PII from entering AI training data
- Secure prompt engineering practices