Most developers know they should "write secure code." But when asked what that means specifically, the answer gets vague fast.
The OWASP Top 10 is the industry standard list of critical web application security risks. But the official documentation is dense and aimed at security professionals. This guide translates each vulnerability into vulnerable code vs. fixed code examples that any developer can understand and act on.
A01: Broken Access Control
The problem: Users can act outside their intended permissions — viewing other users' data, modifying records they don't own, or escalating privileges.
This is the #1 vulnerability on the OWASP list, and it's surprisingly common.
// VULNERABLE: Any authenticated user can view any profile
app.get('/api/users/:id', auth, (req, res) => {
const user = db.getUser(req.params.id);
res.json(user);
});
// FIXED: Verify resource ownership
app.get('/api/users/:id', auth, (req, res) => {
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
const user = db.getUser(req.params.id);
res.json(user);
});
Checklist:
- [ ] Every endpoint checks authorization (not just authentication)
- [ ] Resource ownership is verified before access
- [ ] CORS is configured restrictively (never
*in production) - [ ] CSRF tokens protect state-changing operations
- [ ] No path traversal in file operations (
../)
A02: Cryptographic Failures
The problem: Sensitive data is exposed through weak cryptography, missing encryption, or accidental leakage.
# VULNERABLE: MD5 for password hashing
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
# FIXED: Use bcrypt with auto-generated salt
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
Checklist:
- [ ] Passwords hashed with bcrypt or argon2 (never MD5/SHA)
- [ ] No hardcoded secrets or API keys in source code
- [ ] Sensitive data not logged or in URL parameters
- [ ] HTTPS enforced for all data transmission
- [ ] PII encrypted at rest in the database
A03: Injection
The problem: Untrusted data is sent to an interpreter as part of a command or query. SQL injection is the classic example, but it extends to OS commands, LDAP, and more.
// VULNERABLE: SQL injection via string interpolation
const query = `SELECT * FROM users WHERE email = '${email}'`;
db.query(query);
// Attacker input: ' OR 1=1 --
// Resulting query: SELECT * FROM users WHERE email = '' OR 1=1 --'
// FIXED: Parameterized query
const query = 'SELECT * FROM users WHERE email = $1';
db.query(query, [email]);
// VULNERABLE: Command injection
const { exec } = require('child_process');
exec(`convert ${userFilename} output.png`); // userFilename = "; rm -rf /"
// FIXED: Use array-based API that doesn't invoke shell
const { execFile } = require('child_process');
execFile('convert', [userFilename, 'output.png']);
Checklist:
- [ ] All SQL uses parameterized queries or ORM
- [ ] No
eval()ornew Function()with user input - [ ] No shell execution with user-controlled strings
- [ ] No
innerHTML/dangerouslySetInnerHTMLwith user data - [ ] NoSQL queries don't pass user input to
$whereor$regex
A04: Insecure Design
The problem: Security flaws baked into the architecture itself, not just the implementation. No amount of good code fixes a bad design.
Example: A password reset flow that sends a reset link to the email address provided in the request (instead of the email on file).
Checklist:
- [ ] Rate limiting on authentication endpoints
- [ ] Account lockout after failed login attempts
- [ ] Password reset uses the email on file, not user input
- [ ] Business logic validated server-side (never trust the client)
- [ ] Threat modeling done for sensitive features
A05: Security Misconfiguration
The problem: Insecure default settings, incomplete configurations, or unnecessary features left enabled.
// VULNERABLE: Express app in production
app.use(express.errorHandler()); // Exposes stack traces
// FIXED: Custom error handler for production
app.use((err, req, res, next) => {
console.error(err); // Log internally
res.status(500).json({
error: 'Internal server error' // Don't leak details
});
});
Checklist:
- [ ] Debug mode disabled in production
- [ ] Default credentials changed everywhere
- [ ] Security headers configured (CSP, HSTS, X-Frame-Options)
- [ ] Error messages don't expose stack traces or internals
- [ ] Directory listing disabled
- [ ] Unnecessary features and endpoints removed
A06: Vulnerable and Outdated Components
The problem: Using libraries or frameworks with known security vulnerabilities.
# Check your project right now
npm audit
# or
pip install safety && safety check
Checklist:
- [ ]
npm audit/pip auditruns in CI pipeline - [ ] Dependencies updated regularly
- [ ] No abandoned packages (2+ years without updates)
- [ ] Lock files committed to version control
A07: Identification and Authentication Failures
The problem: Weaknesses in authentication that allow attackers to compromise passwords, sessions, or tokens.
// VULNERABLE: Session not invalidated on logout
app.post('/logout', (req, res) => {
res.clearCookie('session'); // Only clears client-side!
res.redirect('/login');
});
// FIXED: Destroy server-side session
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
res.clearCookie('session');
res.redirect('/login');
});
});
Checklist:
- [ ] Strong password requirements enforced
- [ ] Sessions invalidated server-side on logout
- [ ] Session IDs never exposed in URLs
- [ ] MFA available for sensitive operations
- [ ] Credential stuffing mitigated with rate limiting
A08: Software and Data Integrity Failures
The problem: Code or data that is used without verifying its integrity.
<!-- VULNERABLE: CDN resource without integrity check -->
<script src=\"https://cdn.example.com/lib.js\"></script>
<!-- FIXED: Subresource Integrity (SRI) -->
<script src=\"https://cdn.example.com/lib.js\"
integrity=\"sha384-abc123...\"
crossorigin=\"anonymous\"></script>
Checklist:
- [ ] CDN resources use Subresource Integrity (SRI)
- [ ] No deserialization of untrusted data
- [ ] CI/CD pipeline has integrity checks
- [ ] Auto-updates verify signatures
A09: Security Logging and Monitoring Failures
The problem: Without proper logging, breaches go undetected — the average time to detect a breach is 287 days.
Checklist:
- [ ] Login attempts (success and failure) are logged
- [ ] Access control failures are logged
- [ ] Sensitive data is NEVER in logs (passwords, tokens, PII)
- [ ] Alerts configured for suspicious activity patterns
- [ ] Logs are tamper-protected
A10: Server-Side Request Forgery (SSRF)
The problem: The application fetches a remote resource using a user-supplied URL without validation, allowing attackers to reach internal services.
// VULNERABLE: User controls the fetch URL
app.get('/preview', async (req, res) => {
const response = await fetch(req.query.url);
res.send(await response.text());
});
// Attacker: /preview?url=http://169.254.169.254/metadata
// (AWS metadata endpoint - leaks credentials)
// FIXED: Allowlist validation
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];
app.get('/preview', async (req, res) => {
const url = new URL(req.query.url);
if (!ALLOWED_HOSTS.includes(url.hostname)) {
return res.status(400).json({ error: 'Host not allowed' });
}
const response = await fetch(url.toString());
res.send(await response.text());
});
Checklist:
- [ ] User-supplied URLs validated against an allowlist
- [ ] Internal network addresses blocked (10.x, 172.16.x, 169.254.x)
- [ ] DNS rebinding prevention in place
- [ ] Outbound requests have firewall rules
The Complete Security Checklist
Here's the consolidated quick-reference:
| Category | Key Rule |
|---|---|
| Access Control | Verify ownership, not just authentication |
| Cryptography | bcrypt for passwords, no hardcoded secrets |
| Injection | Parameterize everything, never concatenate |
| Design | Rate limit, validate server-side |
| Configuration | No debug in prod, set security headers |
| Dependencies | Audit regularly, update promptly |
| Authentication | Destroy sessions server-side, enforce MFA |
| Integrity | SRI for CDN, verify signatures |
| Logging | Log auth events, never log secrets |
| SSRF | Allowlist outbound URLs |
Making Security Automatic
Manual security review is important but insufficient. I built Security Audit Pro — a Claude Code skill that automatically scans your codebase against OWASP Top 10 rules, CWE Top 25, dependency vulnerabilities, and secrets detection. It produces a prioritized report with specific line-level findings and fix suggestions.
Whether you use automated tools or this manual checklist, building security into your regular review process is what matters. Don't wait for a breach to start caring about security.
Which of these vulnerabilities have you encountered in your own codebase? I'd bet at least 3 of these 10 apply to most production applications right now.
Top comments (0)