Security is not a feature you add at the end — it’s a set of habits applied throughout development. Most vulnerabilities come from trusting user input or third-party dependencies, not from exotic attack vectors.
Input validation
Validate and sanitize all external input at the boundary:
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).regex(/^[a-zA-Z\s'-]+$/),
age: z.number().int().min(0).max(150).optional(),
});
// Validate before any processing
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.issues });
}
Rules:
- Validate on the server, always — client-side validation is UX, not security
- Use allowlists, not blocklists (“allow letters” not “disallow
- Validate type, length, range, and format separately
- Reject input that doesn’t match the expected schema
SQL injection
Never concatenate user input into SQL queries:
// Bad — vulnerable
db.query(`SELECT * FROM users WHERE id = ${userId}`);
// Good — parameterized
db.query('SELECT * FROM users WHERE id = ?', [userId]);
Use an ORM or query builder that handles parameterization automatically. If you must write raw SQL, always use parameterized queries.
Authentication
Password storage:
import bcrypt from 'bcrypt';
// Hash with cost factor 12+
const hash = await bcrypt.hash(password, 12);
// Verify
const valid = await bcrypt.compare(inputPassword, storedHash);
Never store passwords in plain text, never use MD5/SHA for passwords, never log passwords.
Session management:
- Use
HttpOnly,Secure,SameSite=Strictcookies - Set reasonable expiration (15 min for access tokens, 7 days for refresh tokens)
- Invalidate sessions on password change
- Implement rate limiting on login endpoints
Secrets management
// Bad — hardcoded
const API_KEY = 'sk-abc123...';
// Bad — logged
console.log('Using API key:', process.env.API_KEY);
// Good — environment variables, never logged
const apiKey = process.env.API_KEY;
if (!apiKey) throw new Error('API_KEY environment variable required');
- Never commit secrets to version control
- Use
.envfiles for local development, secret managers for production - Rotate keys periodically
- Use
git-secretsortrufflehogto scan for leaked secrets
Dependency security
# npm
npm audit
# Check for known vulnerabilities before installing
npm audit --omit=dev
- Run
npm auditoryarn auditin CI - Use Dependabot or Renovate for automated updates
- Pin dependency versions in production
- Review changelogs before updating security-critical packages
HTTPS and headers
// Express example
app.use(helmet());
// Sets:
// Strict-Transport-Security: max-age=31536000; includeSubDomains
// X-Content-Type-Options: nosniff
// X-Frame-Options: DENY
// X-XSS-Protection: 0 (modern browsers don't need this)
// Content-Security-Policy: [appropriate policy]
See references/owasp-checklist.md for the full checklist.
When it triggers
- security vulnerability scan
- adding input validation
- handling user authentication
- managing API keys and secrets