Security-First Development with Claude Code
How to ensure your Claude Code workflow produces secure code. From security-auditor agent to PreToolUse hooks and automated auditing.
AI-assisted development raises an important question: How do you ensure the code Claude writes is secure?
The answer isn’t to avoid AI assistance—it’s to build security into your workflow using Claude Code’s official tools: the security-auditor agent, PreToolUse hooks, and permission settings.
This guide covers how to configure Claude Code for security-conscious development using official features.
Official Security Tools
The security-auditor Agent
According to the Claude Code documentation, the security-auditor is a specialized agent for vulnerability detection:
Task({
subagent_type: "security-auditor",
model: "sonnet", // or "opus" for critical code
prompt: `
Audit the authentication module for security vulnerabilities.
Check for:
- OWASP Top 10 vulnerabilities
- Hardcoded secrets
- SQL injection patterns
- XSS vulnerabilities
- Authentication bypass risks
`
})
When to use security-auditor:
| Scenario | Model | Why |
|---|---|---|
| Auth/payment changes | Sonnet/Opus | Critical code |
| New API endpoints | Sonnet | Input validation |
| File upload handling | Sonnet | Sanitization |
| External API integration | Sonnet | Data exposure |
Hooks for Automatic Security Enforcement
From the official hooks documentation, hooks automatically enforce security policies without manual intervention.
PreToolUse Hook - Block Dangerous Operations
Configure in .claude/settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "prompt",
"prompt": "If the command contains destructive operations (rm -rf, DROP TABLE, git push --force), return 'ask' for user confirmation. Otherwise return 'approve'."
}]
}]
}
}
Available hook events:
| Event | When | Security Use Case |
|---|---|---|
PreToolUse | Before tool execution | Block dangerous commands |
PostToolUse | After tool execution | Audit file changes |
Stop | Agent considers stopping | Verify security review |
SessionStart | Session begins | Load security context |
Stop Hook - Verify Security Review
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "If code in auth/, payment/, or admin/ was modified, verify security-auditor was run. If not, block and explain."
}]
}]
}
}
Permission Settings
From the Claude Code documentation, use settings.json for access control:
{
"permissions": {
"allow": [
"Read",
"Write",
"Edit",
"Bash(npm run:*)",
"Bash(git:*)"
],
"deny": [
"Read(.env)",
"Read(.env.*)",
"Read(secrets/**)",
"Read(**/*credentials*)",
"Bash(*rm -rf*)",
"Bash(*DROP*)"
]
}
}
Permission pattern syntax:
Bash(npm run:*)- Allow all npm run commandsRead(.env)- Deny reading .env filesRead(secrets/**)- Deny reading anything in secrets/Bash(*DROP*)- Deny commands containing DROP
The Security Challenge
When Claude writes code, several risks exist:
- Insecure patterns copied from training data
- Missing input validation
- Improper error handling that leaks information
- Hardcoded credentials or secrets
- SQL injection, XSS, and other vulnerabilities
The solution: codify your security requirements using agents and hooks so Claude follows them automatically.
Building a Security-First CLAUDE.md
Security Section Template
# Security Requirements
## Non-Negotiables
These rules cannot be overridden for any reason:
1. **No Secrets in Code**
- Never hardcode credentials, API keys, or tokens
- Use environment variables for all secrets
- Reference secrets through secure config management
2. **Input Validation**
- All user inputs must be validated
- Use allowlist validation over blocklist
- Validate on both client and server
3. **Output Encoding**
- Encode all outputs for the context (HTML, URL, JS)
- Use framework-provided escaping mechanisms
- Never concatenate raw user input into responses
4. **Authentication & Authorization**
- All endpoints must verify authentication
- Check authorization before every action
- Use principle of least privilege
5. **Data Protection**
- Encrypt sensitive data at rest and in transit
- Minimize data collection and retention
- Log access to sensitive data
## Security Review Requirements
These types of changes require explicit security review:
- Authentication/authorization logic
- Payment processing
- Personal data handling
- API endpoint creation
- Database queries
- File uploads
- Email sending
- External API calls
Input Validation Standards
# Input Validation Standards
## Validation Library
Use Zod for all runtime validation:
\`\`\`typescript
import { z } from 'zod';
// Define schema
const UserSchema = z.object({
email: z.string().email(),
age: z.number().min(0).max(150),
name: z.string().min(1).max(100),
});
// Validate
const result = UserSchema.safeParse(input);
if (!result.success) {
throw new ValidationError(result.error);
}
\`\`\`
## Validation Rules
- String inputs: Define max length
- Numeric inputs: Define valid range
- Email: Use proper email validation
- URLs: Validate protocol and domain
- IDs: Validate format (UUID, numeric, etc.)
- Arrays: Validate length and item types
- Objects: Validate required fields
Secure Coding Patterns
# Secure Coding Patterns
## Database Queries
ALWAYS use parameterized queries:
\`\`\`typescript
// NEVER do this
const query = \`SELECT * FROM users WHERE email = '\${email}'\`;
// ALWAYS do this
const user = await db.user.findUnique({
where: { email }, // Prisma handles parameterization
});
\`\`\`
## Error Handling
Never expose internal details:
\`\`\`typescript
// NEVER do this
catch (error) {
return res.status(500).json({ error: error.message });
}
// ALWAYS do this
catch (error) {
console.error('Internal error:', error); // Log full error
return res.status(500).json({ error: 'An error occurred' }); // Generic message
}
\`\`\`
## Authentication Checks
Always verify auth at the start:
\`\`\`typescript
export async function handler(req, res) {
// First: Authenticate
const user = await getAuthenticatedUser(req);
if (!user) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Second: Authorize
if (!user.canAccessResource(resourceId)) {
return res.status(403).json({ error: 'Forbidden' });
}
// Then: Proceed with logic
// ...
}
\`\`\`
Agent Delegation for Security
Configure Claude to automatically involve security review for sensitive changes:
# Agent Delegation Rules
## Automatic Security Review
These changes automatically trigger security-auditor agent:
- Files in: auth/, payment/, admin/
- Functions containing: password, token, secret, key, credential
- Operations: user creation, permission changes, data deletion
- External calls: API integrations, webhooks, email
## Security Agent Configuration
When security-auditor runs, it checks for:
- OWASP Top 10 vulnerabilities
- Hardcoded secrets
- Missing authentication
- SQL injection patterns
- XSS vulnerabilities
- Insecure direct object references
- Missing rate limiting
- Insufficient logging
Implementing with Task Tool
// Automatic security review for auth changes
Task({
subagent_type: "security-auditor",
model: "sonnet",
prompt: `
Review changes to auth/ directory.
Check for OWASP Top 10 vulnerabilities:
1. Injection
2. Broken authentication
3. Sensitive data exposure
4. XML external entities
5. Broken access control
6. Security misconfiguration
7. Cross-site scripting
8. Insecure deserialization
9. Using components with known vulnerabilities
10. Insufficient logging
Report findings with severity levels.
`
})
Security Workflow Patterns
Pattern 1: Pre-Implementation Review
Before writing security-sensitive code:
Task({
subagent_type: "security-auditor",
model: "sonnet",
prompt: `
Planning to implement password reset functionality.
Review security considerations:
1. What vulnerabilities should I address?
2. What OWASP vulnerabilities are relevant?
3. What's the secure implementation pattern?
4. What tests should I write?
`
})
Pattern 2: Implementation with Guard Rails
While implementing with automatic hooks:
{
"hooks": {
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "If the written file is in auth/, payment/, or admin/ directories, trigger a security scan and report any issues found."
}]
}]
}
}
Pattern 3: Post-Implementation Audit
After implementing:
Task({
subagent_type: "security-auditor",
model: "opus", // Use Opus for critical security review
prompt: `
Comprehensive security audit of password reset feature.
Check for:
- Token generation entropy
- Timing attacks in token comparison
- Rate limiting bypass
- User enumeration
- Session fixation after reset
- Proper invalidation of old sessions
Provide findings categorized by severity:
- Critical
- High
- Medium
- Low
`
})
Common Vulnerabilities and Prevention
SQL Injection
Vulnerable:
const query = `SELECT * FROM users WHERE id = ${userId}`;
Secure:
const user = await prisma.user.findUnique({ where: { id: userId } });
Hook protection:
{
"hooks": {
"PreToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "If the code contains string concatenation with SQL keywords (SELECT, INSERT, UPDATE, DELETE), return 'block' with warning about SQL injection. Otherwise 'approve'."
}]
}]
}
}
Cross-Site Scripting (XSS)
Vulnerable:
<div dangerouslySetInnerHTML={{ __html: userContent }} />
Secure:
<div>{userContent}</div> // React auto-escapes
CLAUDE.md rule:
## XSS Prevention
- Never use dangerouslySetInnerHTML without sanitization
- Use DOMPurify for HTML content that must be rendered
- Escape all dynamic content in non-React contexts
Authentication Bypass
Vulnerable:
if (user.role === 'admin') {
// Allow action
}
Secure:
if (await authService.hasPermission(user.id, 'admin:action')) {
// Allow action
}
Sensitive Data Exposure
Vulnerable:
return res.json({ user }); // Includes password hash!
Secure:
return res.json({ user: sanitizeUser(user) });
Hooks for Continuous Security
Pre-Commit Security Check
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "npm run security-scan",
"onFailure": "block"
}]
}]
}
}
Automatic Secret Detection
{
"hooks": {
"PreToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "Scan the code for potential secrets: API keys, passwords, tokens, private keys. If found, return 'block' with the line numbers. Otherwise 'approve'."
}]
}]
}
}
File Access Control
{
"permissions": {
"deny": [
"Read(.env*)",
"Read(**/secrets/**)",
"Read(**/*credential*)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Write(.env*)",
"Edit(.env*)"
]
}
}
Security Testing with test-runner
Automated Security Test Suite
// Launch parallel security tests
Task({
subagent_type: "test-runner",
model: "haiku",
prompt: "Run all security-related tests in tests/security/"
})
Task({
subagent_type: "security-auditor",
model: "sonnet",
prompt: "Verify test coverage for authentication edge cases"
})
Security Test Example
describe('Password Reset Security', () => {
it('rejects invalid tokens', async () => {
const response = await request(app)
.post('/api/auth/reset-password')
.send({ token: 'invalid', password: 'newpassword' });
expect(response.status).toBe(400);
expect(response.body).not.toContain('token');
});
it('prevents timing attacks on token validation', async () => {
// Use constant-time comparison
const times = [];
for (let i = 0; i < 100; i++) {
const start = performance.now();
await validateToken(`token${i}`);
times.push(performance.now() - start);
}
// Variance should be minimal
expect(standardDeviation(times)).toBeLessThan(5);
});
it('rate limits reset requests', async () => {
const email = '[email protected]';
// First 3 requests succeed
for (let i = 0; i < 3; i++) {
const response = await request(app)
.post('/api/auth/forgot-password')
.send({ email });
expect(response.status).toBe(200);
}
// 4th request is rate limited
const response = await request(app)
.post('/api/auth/forgot-password')
.send({ email });
expect(response.status).toBe(429);
});
});
Security Audit Checklist
Before any deployment:
## Pre-Deployment Security Checklist
### Authentication
- [ ] All endpoints require authentication (except public ones)
- [ ] Tokens expire appropriately
- [ ] Password requirements enforced
- [ ] Account lockout after failed attempts
### Authorization
- [ ] Permission checks on all protected actions
- [ ] No privilege escalation paths
- [ ] Admin functions properly protected
### Data Protection
- [ ] Sensitive data encrypted at rest
- [ ] HTTPS enforced
- [ ] No secrets in code or logs
- [ ] PII handling compliant
### Input/Output
- [ ] All inputs validated
- [ ] All outputs properly encoded
- [ ] File uploads validated and sanitized
- [ ] No SQL injection vulnerabilities
### Logging & Monitoring
- [ ] Security events logged
- [ ] Logs don't contain sensitive data
- [ ] Alerting configured for anomalies
Enterprise Security Features
Claude Code supports enterprise security requirements through the four-layer configuration hierarchy:
Enterprise Layer (Managed Settings)
// /etc/claude-code/settings.json (IT managed)
{
"permissions": {
"deny": [
"Bash(*curl*)",
"Bash(*wget*)",
"Read(/etc/**)",
"Write(/etc/**)"
]
},
"mcpServers": {
"allowlist": ["filesystem", "memory"]
}
}
Audit Logging Configuration
## Audit Requirements
Log these events to secure audit system:
- Authentication attempts (success/failure)
- Authorization decisions
- Data access (especially sensitive data)
- Configuration changes
- API calls to external services
Compliance Integration
## Compliance Requirements
- PCI DSS: No card data in logs, encrypt at rest
- GDPR: Data minimization, right to deletion
- HIPAA: PHI access controls and audit trails
- SOC 2: Change management documentation
Model Selection for Security
From the Claude Code CHANGELOG:
| Task | Recommended Model | Why |
|---|---|---|
| Quick security scan | Haiku 4.5 | Fast pattern matching |
| Code review | Sonnet 4.5 | Balanced analysis |
| Security audit | Sonnet 4.5 | Thorough review |
| Critical security | Opus 4.5 | Highest intelligence |
Quick model switching (v2.0.65+): Press Option+P (macOS) or Alt+P (Windows/Linux) during prompting.
Getting Started
Today:
- Add security-auditor delegation rules to CLAUDE.md
- Configure permission denials for sensitive files
- Add one PreToolUse hook for dangerous commands
This week:
- Implement Stop hook for security verification
- Add security test suite
- Run security-auditor on critical modules
This month:
- Deploy pre-commit security hooks
- Integrate security scanning in CI/CD
- Train team on security-first development
Security is a journey, not a destination. With Claude Code’s agents and hooks, you can automate security best practices.
Sources: Claude Code Documentation, Claude Code GitHub, Hooks Documentation