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.
Updated October 21, 2026: Added the 2026 permission-mode defaults (auto mode for interactive sessions since v2.1.283, Manual for
claude -psince v2.1.302), the Bash sandbox settings,--restricted, andpermissions.blockReadsOutsideWorkingDirectories; clarified that security-auditor is a custom subagent (the built-in check is/security-review); fixed the hook examples to the documented prompt-hook and exit-code contracts; and updated the managed-settings path and models.
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: permission modes and rules, the Bash sandbox, hooks, and the built-in /security-review command, plus a custom security-auditor subagent.
This guide covers how to configure Claude Code for security-conscious development using official features.
Official Security Tools
The security-auditor Agent
The security-auditor is not a built-in agent: it’s a custom subagent you define yourself (Claude Code’s built-in subagents are Explore, Plan, and general-purpose). For an on-demand check with no setup, run the built-in /security-review command, which reviews your branch’s changes for vulnerabilities. To create the subagent, add a file such as:
---
name: security-auditor
description: Reviews code changes for security vulnerabilities. Use for auth, payment, and admin changes.
tools: Read, Glob, Grep
model: sonnet
---
You are a security auditor. Check changes for OWASP Top 10 issues,
hardcoded secrets, injection, XSS, and authentication bypasses, and
report each finding with its severity.
Save it as .claude/agents/security-auditor.md. Claude then delegates to it through the Agent tool (named Task before v2.1.63):
Agent({
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), respond with {\"ok\": false, \"reason\": \"Destructive command: confirm with the user first\"}. Otherwise respond with {\"ok\": true}.",
"continueOnBlock": true
}]
}]
}
}
A prompt hook can only answer {"ok": true} or {"ok": false, "reason": "..."}. On PreToolUse, ok: false denies the call, and continueOnBlock: true hands the reason back to Claude so it can adjust. To have Claude Code ask you before specific commands instead, add ask rules such as "Bash(git push --force *)" under permissions.ask.
Available hook events:
| Event | When | Security Use Case |
|---|---|---|
PreToolUse | Before tool execution | Block dangerous commands |
PostToolUse | After tool execution | Audit file changes |
Stop | Claude finishes responding | Verify security review |
SessionStart | Session begins | Load security context |
Stop Hook - Verify Security Review
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "agent",
"prompt": "Run git diff --name-only. If files in auth/, payment/, or admin/ changed and the transcript shows no security-auditor review, respond with {\"ok\": false, \"reason\": \"Run the security-auditor subagent on the changed files before finishing\"}. Otherwise respond with {\"ok\": true}. $ARGUMENTS"
}]
}]
}
}
A prompt hook sees only the hook input, so this check uses an agent hook, which can run commands and read the transcript before deciding. Agent hooks are experimental. On Stop, ok: false sends Claude back to work with the reason as its next instruction.
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 commands (Bash(npm run:*)is the equivalent older form)Read(.env)- Deny reading .env filesRead(secrets/**)- Deny reading anything in secrets/Bash(*DROP*)- Deny commands containing DROP
Rules are evaluated deny → ask → allow, and deny rules apply in every permission mode, including bypassPermissions. Write file-path rules as Read(...) or Edit(...): Claude Code never checks a Write(path) rule.
Permission Modes and Defaults (2026)
The permission mode decides whether Claude asks before acting, and the defaults changed in 2026:
- Interactive sessions start in auto mode. Since Claude Code v2.1.283, terminal and VS Code sessions with no permission mode configured start in auto mode, where a classifier model reviews each action instead of prompting you. Explicit
askrules still prompt, and auto mode can be turned off for an organization. - Headless runs start in Manual mode. Since v2.1.302,
claude -pand Agent SDK runs with no mode set start in Manual mode (config valuedefault), so any call that needs approval is denied unless an allow rule, hook, or--permission-prompt-toolapproves it. - Protected paths stay guarded. Writes to paths such as
.git,.claude, shell startup files, and.mcp.jsonprompt you in Manual and acceptEdits modes, go to the classifier in auto mode, and are denied in dontAsk; settings allow rules don’t pre-approve them.
For a sensitive repository, pin the starting mode and keep file tools inside the project:
{
"permissions": {
"defaultMode": "default",
"blockReadsOutsideWorkingDirectories": true
}
}
"default" is Manual mode, which asks before edits, shell commands, and network access. A project’s .claude/settings.json can set any starting mode except auto and bypassPermissions. blockReadsOutsideWorkingDirectories (v2.1.257+) makes Read, Grep, Glob, and LSP refuse paths outside your working directories in every mode, including bypassPermissions; a true in any settings file applies, so a repository can turn it on but can’t turn off yours. Administrators can remove auto mode or bypass mode with permissions.disableAutoMode and permissions.disableBypassPermissionsMode set to "disable" in managed settings.
Sandboxing and Restricted Mode
Permission rules govern Claude’s tools; the Bash sandbox adds operating-system enforcement for the shell commands Claude runs (macOS, Linux, and WSL2). It is off by default: run /sandbox, or set it in settings:
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"denyRead": ["~/.aws/credentials"]
},
"network": {
"allowedDomains": ["github.com", "*.npmjs.org"]
}
}
}
failIfUnavailable makes Claude Code exit instead of running commands unsandboxed when the sandbox can’t start, and allowUnsandboxedCommands: false (strict sandbox mode) stops Claude from retrying a blocked command outside the sandbox. The sandbox covers shell commands only; file tools, hooks, and local MCP servers run outside it and follow permission rules.
For evaluation harnesses or shared machines, start Claude Code with --restricted (or CLAUDE_CODE_RESTRICTED=1, v2.1.248+). It removes the built-in tools that run commands or code and WebFetch unless you name them in --tools, keeps file tools inside the working directories, ignores user, project, and local settings, and refuses bypassPermissions.
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 the Agent Tool
// Automatic security review for auth changes
Agent({
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:
Agent({
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": "Edit|Write",
"hooks": [{
"type": "prompt",
"prompt": "If tool_input.file_path is in an auth/, payment/, or admin/ directory, respond with {\"ok\": false, \"reason\": \"Security-sensitive file changed: run the security-auditor subagent on it\"}. Otherwise respond with {\"ok\": true}.",
"continueOnBlock": true
}]
}]
}
}
Pattern 3: Post-Implementation Audit
After implementing:
Agent({
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": "Edit|Write",
"hooks": [{
"type": "prompt",
"prompt": "If the new code builds SQL by concatenating strings with SQL keywords (SELECT, INSERT, UPDATE, DELETE), respond with {\"ok\": false, \"reason\": \"Possible SQL injection: use parameterized queries\"}. Otherwise respond with {\"ok\": true}.",
"continueOnBlock": true
}]
}]
}
}
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": [{
"hooks": [{
"type": "command",
"command": "npm run security-scan >&2 || exit 2"
}]
}]
}
}
Exit code 2 from a Stop hook sends Claude back to work with the hook’s stderr as the reason. The onFailure: "block" field (v2.1.295+) has no effect on Stop hooks, so the command itself must exit 2 when the scan fails.
Automatic Secret Detection
{
"hooks": {
"PreToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "prompt",
"prompt": "Scan the new content for potential secrets: API keys, passwords, tokens, private keys. If found, respond with {\"ok\": false, \"reason\": \"Possible secret on lines ...\"}. Otherwise respond with {\"ok\": true}.",
"continueOnBlock": true
}]
}]
}
}
File Access Control
{
"permissions": {
"deny": [
"Read(.env*)",
"Read(**/secrets/**)",
"Read(**/*credential*)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Edit(.env*)",
"Edit(**/secrets/**)"
]
}
}
Security Testing with test-runner
Like security-auditor, test-runner is a custom subagent you define in .claude/agents/.
Automated Security Test Suite
// Launch parallel security tests
Agent({
subagent_type: "test-runner",
model: "haiku",
prompt: "Run all security-related tests in tests/security/"
})
Agent({
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 = 'test@example.com';
// 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 its settings hierarchy, where managed settings outrank command-line, local, project, and user settings:
Enterprise Layer (Managed Settings)
// /etc/claude-code/managed-settings.json (Linux and WSL; IT managed)
{
"permissions": {
"deny": [
"Bash(*curl*)",
"Bash(*wget*)",
"Read(//etc/**)",
"Edit(//etc/**)"
],
"disableBypassPermissionsMode": "disable"
},
"allowedMcpServers": [
{ "serverName": "filesystem" },
{ "serverName": "memory" }
]
}
On macOS the file lives at /Library/Application Support/ClaudeCode/managed-settings.json, and on Windows at C:\Program Files\ClaudeCode\managed-settings.json. Use // for absolute paths in Read and Edit rules.
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 model configuration docs:
| Task | Recommended Model | Why |
|---|---|---|
| Quick security scan | Haiku 5.5 | Fast pattern matching |
| Code review | Sonnet 5.5 | Balanced analysis |
| Security audit | Opus 5.5 | Thorough review; the docs suggest max effort for hunting vulnerabilities |
| Critical security | Fable 5.1 | Highest intelligence |
Fixing bugs in your own code is routine work for these models, but security-focused requests can hit safeguards. Anthropic’s Opus 5.5 announcement says: “Users will be able to identify and fix bugs in their code as part of the routine software development lifecycle, but most cybersecurity tasks will be re-routed to Opus 4.8.” (source) Requests that a Fable model’s safety classifiers flag, most often in cybersecurity, trigger automatic model fallback, and offensive-security work such as penetration testing triggers it frequently. For that work, Anthropic points organizations to its trusted access programs, such as the Cyber Verification Program.
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
- Run
/security-reviewon your current branch
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, Security, Permission modes, Sandboxing, Hooks Documentation, Claude Code GitHub