| name | security-and-hardening |
| description | Hardens code against vulnerabilities. Use when handling user input, authentication, data storage, or external integrations. OWASP-aware, language-agnostic principles with TypeScript examples — applies to any feature that accepts untrusted data, manages user sessions, or interacts with third-party services. |
| lastReviewed | "2026-05-26T00:00:00.000Z" |
Security and Hardening
Overview
Security-first development practices for web applications. Treat every external input as hostile, every secret as sacred, and every authorization check as mandatory. Security isn't a phase — it's a constraint on every line of code that touches user data, authentication, or external systems.
Examples below use TypeScript/Node.js syntax for concreteness, but the principles (parameterized queries, hashed passwords, schema validation at boundaries, secrets out of source, OWASP Top 10) apply to any language.
When to Use
- Building anything that accepts user input
- Implementing authentication or authorization
- Storing or transmitting sensitive data
- Integrating with external APIs or services
- Adding file uploads, webhooks, or callbacks
- Handling payment or PII data
The Three-Tier Boundary System
Always Do (No Exceptions)
- Validate all external input at the system boundary (API routes, form handlers)
- Parameterize all database queries — never concatenate user input into SQL
- Encode output to prevent XSS (use framework auto-escaping, don't bypass it)
- Use HTTPS for all external communication
- Hash passwords with bcrypt/scrypt/argon2 (never store plaintext)
- Set security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options)
- Use httpOnly, secure, sameSite cookies for sessions
- Run dependency audits (
npm audit, pip-audit, cargo audit, etc.) before every release
Ask First (Requires Human Approval)
- Adding new authentication flows or changing auth logic
- Storing new categories of sensitive data (PII, payment info)
- Adding new external service integrations
- Changing CORS configuration
- Adding file upload handlers
- Modifying rate limiting or throttling
- Granting elevated permissions or roles
Never Do
- Never commit secrets to version control (API keys, passwords, tokens)
- Never log sensitive data (passwords, tokens, full credit card numbers)
- Never trust client-side validation as a security boundary
- Never disable security headers for convenience
- Never use
eval() or innerHTML with user-provided data
- Never store sessions in client-accessible storage (localStorage for auth tokens)
- Never expose stack traces or internal error details to users
OWASP Top 10 Prevention
1. Injection (SQL, NoSQL, OS Command)
const query = `SELECT * FROM users WHERE id = '${userId}'`;
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
const user = await prisma.user.findUnique({ where: { id: userId } });
2. Broken Authentication
import { hash, compare } from 'bcrypt';
const SALT_ROUNDS = 12;
const hashedPassword = await hash(plaintext, SALT_ROUNDS);
const isValid = await compare(plaintext, hashedPassword);
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 24 * 60 * 60 * 1000,
},
}));
3. Cross-Site Scripting (XSS)
element.innerHTML = userInput;
return <div>{userInput}</div>;
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);
4. Broken Access Control
app.patch('/api/tasks/:id', authenticate, async (req, res) => {
const task = await taskService.findById(req.params.id);
if (task.ownerId !== req.user.id) {
return res.status(403).json({
error: { code: 'FORBIDDEN', message: 'Not authorized to modify this task' }
});
}
const updated = await taskService.update(req.params.id, req.body);
return res.json(updated);
});
5. Security Misconfiguration
import helmet from 'helmet';
app.use(helmet());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
},
}));
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
credentials: true,
}));
6. Sensitive Data Exposure
function sanitizeUser(user: UserRecord): PublicUser {
const { passwordHash, resetToken, ...publicFields } = user;
return publicFields;
}
const API_KEY = process.env.STRIPE_API_KEY;
if (!API_KEY) throw new Error('STRIPE_API_KEY not configured');
Input Validation Patterns
Schema Validation at Boundaries
import { z } from 'zod';
const CreateTaskSchema = z.object({
title: z.string().min(1).max(200).trim(),
description: z.string().max(2000).optional(),
priority: z.enum(['low', 'medium', 'high']).default('medium'),
dueDate: z.string().datetime().optional(),
});
app.post('/api/tasks', async (req, res) => {
const result = CreateTaskSchema.safeParse(req.body);
if (!result.success) {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid input',
details: result.error.flatten(),
},
});
}
const task = await taskService.create(result.data);
return res.status(201).json(task);
});
File Upload Safety
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
const MAX_SIZE = 5 * 1024 * 1024;
function validateUpload(file: UploadedFile) {
if (!ALLOWED_TYPES.includes(file.mimetype)) {
throw new ValidationError('File type not allowed');
}
if (file.size > MAX_SIZE) {
throw new ValidationError('File too large (max 5MB)');
}
}
Triaging Dependency Audit Results
Not all audit findings require immediate action. Use this decision tree:
Dependency audit reports a vulnerability
- Severity: critical or high
- Is the vulnerable code reachable in your app?
- YES --> Fix immediately (update, patch, or replace the dependency)
- NO (dev-only dep, unused code path) --> Fix soon, but not a blocker
- Is a fix available?
- YES --> Update to the patched version
- NO --> Check for workarounds, consider replacing the dependency, or add to allowlist with a review date
- Severity: moderate
- Reachable in production? --> Fix in the next release cycle
- Dev-only? --> Fix when convenient, track in backlog
- Severity: low
- Track and fix during regular dependency updates
Key questions:
- Is the vulnerable function actually called in your code path?
- Is the dependency a runtime dependency or dev-only?
- Is the vulnerability exploitable given your deployment context (e.g., a server-side vulnerability in a client-only app)?
When you defer a fix, document the reason and set a review date.
Rate Limiting
import rateLimit from 'express-rate-limit';
app.use('/api/', rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
}));
app.use('/api/auth/', rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
}));
Secrets Management
.env files:
.env.example → Committed (template with placeholder values)
.env → NOT committed (contains real secrets)
.env.local → NOT committed (local overrides)
.gitignore must include:
.env
.env.local
.env.*.local
*.pem
*.key
Always check before committing:
git diff --cached | grep -i "password\|secret\|api_key\|token"
Security Review Checklist
Authentication
Authorization
Input
Data
Infrastructure
Common Rationalizations
| Rationalization | Reality |
|---|
| "This is an internal tool, security doesn't matter" | Internal tools get compromised. Attackers target the weakest link. |
| "We'll add security later" | Security retrofitting is 10x harder than building it in. Add it now. |
| "No one would try to exploit this" | Automated scanners will find it. Security by obscurity is not security. |
| "The framework handles security" | Frameworks provide tools, not guarantees. You still need to use them correctly. |
| "It's just a prototype" | Prototypes become production. Security habits from day one. |
Red Flags
- User input passed directly to database queries, shell commands, or HTML rendering
- Secrets in source code or commit history
- API endpoints without authentication or authorization checks
- Missing CORS configuration or wildcard (
*) origins
- No rate limiting on authentication endpoints
- Stack traces or internal errors exposed to users
- Dependencies with known critical vulnerabilities
Verification
After implementing security-relevant code:
Related skills
code-review — security review is one dimension of comprehensive code review
deep-review — Skeptic perspective surfaces security issues that single-pass review misses
Would Revise If
Revise by 2026-08-26 (90 days) or sooner if any of the following fires:
- TypeScript-heavy examples confuse heirs working in Python/Rust/Go (the language-agnostic claim breaks down in practice — port snippets or split per-language references)
- The Three-Tier Boundary System (Always Do / Ask First / Never Do) gets bypassed in observed heir security work without surfacing as a problem (the tiers are wrong shape)
- OWASP Top 10 reorganizes such that the numbered subsections (Injection, Broken Authentication, etc.) become misleading
- Dependency audit framing (
npm audit decision tree) becomes stale as package ecosystems evolve
- 90 days pass with zero observed invocations of the skill (it's decorative, not load-bearing)