Claude Code Security Best Practices
Using Claude Code makes you faster, but speed creates a new risk: accepting code without fully reading it. Claude-generated code can contain real security vulnerabilities — not because Claude intends harm, but because it lacks the full context of your production environment, your users, and your threat model. Understanding how to use Claude Code securely protects both your application and your users.
The Core Security Rule
Treat every line Claude writes the same way you would treat code submitted by a new developer on your team — read it, understand it, and question anything that touches sensitive data or user input before you ship it.
Developer Safety Habit:
──────────────────────────────────────────
Claude writes code
↓
You READ the code (not just scan it)
↓
You ASK about anything you do not understand
↓
You TEST it before it goes to production
↓
Ship with confidence
Never Share Secrets With Claude
This is the most critical rule. Never paste the following into a Claude conversation:
- API keys (AWS, Stripe, OpenAI, Twilio, etc.)
- Database connection strings with real credentials
- Private keys or certificates
- Passwords — yours or any user's
- JWT secrets or session signing keys
- OAuth client secrets
What to Do Instead
WRONG — sharing a real secret: "Here is my database URL: postgresql://admin:MyP@ssw0rd@prod-db.example.com:5432/users Why is this connection failing?" RIGHT — using a placeholder: "Here is my database URL format: postgresql://[USER]:[PASSWORD]@[HOST]:5432/[DB_NAME] I'm getting a connection timeout error. What should I check?"
Replace real values with obvious placeholders. Claude understands the structure and can help without seeing your actual credentials.
Watch for SQL Injection in Generated Code
SQL injection is one of the most common and damaging web security vulnerabilities. It happens when user input is inserted directly into a SQL query string instead of being handled safely through parameterized queries.
Spotting the Problem
VULNERABLE — direct string insertion: const query = "SELECT * FROM users WHERE email = '" + email + "'"; An attacker sends: email = "' OR '1'='1" The query becomes: SELECT * FROM users WHERE email = '' OR '1'='1' → Returns ALL users in the database
The Safe Version
SAFE — parameterized query: const query = "SELECT * FROM users WHERE email = $1"; const result = await db.query(query, [email]); The database treats email as data, never as code. No matter what the attacker sends, it cannot change the query.
When Claude writes database queries for you, check every one. If you see user input concatenated into a SQL string, ask Claude to rewrite it with parameterized queries before using it.
Input Validation and Sanitization
Every piece of data that comes from a user — form fields, URL parameters, file uploads, headers — must be validated before your code processes or stores it. Claude sometimes skips validation in generated code to keep examples short.
Ask Claude to Add Validation Explicitly
Prompt: "Write a route handler that accepts user registration data. Include validation for: - email must be a valid email format - password must be at least 8 characters - username must be alphanumeric only, 3–20 characters - reject any extra fields not in the schema Return clear error messages for each failed check."
Validation Layers
User Input
↓
[Format check] — Is this even the right type? (string, number)
↓
[Range check] — Is it within allowed limits? (length, value)
↓
[Content check] — Does it contain dangerous characters?
↓
[Business check] — Does it follow your app's rules?
↓
Safe to use in your application
Authentication and Authorization Checks
Authentication proves who you are. Authorization proves what you are allowed to do. Claude-generated API routes sometimes include the main logic but skip the auth middleware. Always verify that every protected route requires authentication.
Missing auth check — dangerous:
app.get('/admin/users', async (req, res) => {
const users = await db.getAllUsers();
res.json(users);
});
// Anyone on the internet can call this
With auth middleware — correct:
app.get('/admin/users', requireAuth, requireAdmin, async (req, res) => {
const users = await db.getAllUsers();
res.json(users);
});
// Only authenticated admins reach the handler
Checklist for Every Route Claude Generates
□ Does this route need authentication? (most do) □ Is the auth middleware applied before the handler? □ Does it check the user's ROLE, not just login status? □ Does it verify the user can only access THEIR data? (user A cannot read user B's profile via /users/:id)
Dependency Security
When Claude suggests adding a new npm package or Python library, research it before installing. Third-party packages are a common entry point for supply chain attacks — a malicious package masquerades as a popular one with a similar name.
Before Installing Any Package Claude Suggests
1. Search the package name exactly on npmjs.com or PyPI 2. Check: weekly downloads (popular = more scrutiny from community) 3. Check: last updated date (abandoned packages get no security fixes) 4. Check: number of open issues, GitHub stars 5. Run: npm audit (after install) to check for known vulnerabilities
Typosquatting example — real attacks: Legitimate: express Fake attack: expres (one 's' missing) — looks identical when rushed
Reviewing Claude's Code for Hardcoded Values
Claude sometimes hardcodes values in example code that should never be hardcoded in real applications. Before using generated code, scan it for:
- Any string that looks like a key, token, or password
- Hardcoded usernames or admin credentials
- Fixed URLs that should come from environment variables
- Magic numbers that represent security thresholds (like token expiry times)
WRONG — hardcoded values in generated code: const JWT_SECRET = "mySuperSecretKey123"; const ADMIN_EMAIL = "admin@company.com"; const API_URL = "https://api.production.company.com"; RIGHT — from environment variables: const JWT_SECRET = process.env.JWT_SECRET; const ADMIN_EMAIL = process.env.ADMIN_EMAIL; const API_URL = process.env.API_URL;
Cross-Site Scripting (XSS) in Generated Frontend Code
XSS happens when user-supplied content gets inserted into a web page as raw HTML. An attacker submits a script tag, and your app runs it in other users' browsers.
VULNERABLE — raw HTML insertion:
document.getElementById('greeting').innerHTML = 'Hello ' + userName;
// If userName = "<script>stealCookies()</script>" — it runs
SAFE — text content only:
document.getElementById('greeting').textContent = 'Hello ' + userName;
// Treated as text, not HTML — the script tag never executes
When Claude generates frontend code that displays user data, check whether it uses innerHTML (risky) or textContent/innerText (safe). If you need to display formatted HTML, ask Claude to use a sanitization library like DOMPurify.
Asking Claude to Audit Its Own Code for Security
After Claude writes a function that handles user data or authentication, ask it to review the code specifically for security issues before you use it.
Prompt: "You just wrote this authentication handler. Now put on a security engineer hat and audit it. List every potential security vulnerability and how an attacker could exploit each one."
This two-step approach — write, then audit — catches most common vulnerabilities before the code ever reaches your repository.
Environment Variables and .env Files
Store all secrets in a .env file and load them at runtime. Make sure .env is in your .gitignore file so it never gets committed to version control.
.env file (never commit this): ────────────────────────────── DATABASE_URL=postgresql://user:pass@host:5432/db JWT_SECRET=your-real-secret-here STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxxx .env.example file (safe to commit — no real values): ────────────────────────────── DATABASE_URL=postgresql://USER:PASS@HOST:5432/DB JWT_SECRET=your-jwt-secret-here STRIPE_SECRET_KEY=sk_live_your-stripe-key
Commit .env.example to show teammates which variables they need. Never commit .env.
Key Points
- Read every line Claude writes before using it — especially code that touches user data, authentication, or databases
- Never paste real API keys, passwords, or connection strings into Claude — use obvious placeholders instead
- Check every database query for parameterized inputs — user data must never be concatenated into SQL strings
- Verify that every protected API route has authentication and authorization middleware applied
- Research packages Claude suggests before installing — check download count, update date, and run npm audit
- Scan generated code for hardcoded secrets and move them to environment variables
- After Claude writes security-sensitive code, ask it to audit its own output as a security engineer
