Security Hardening
This skill enforces structured security analysis before code reaches production. It covers the most common vulnerabilities found in backend systems and provides concrete mitigation patterns per category.
When to Use
- Any backend feature that handles user input, file uploads, or external data
- Any task involving authentication, authorization, sessions, or tokens
- Code review with a security focus (
/review + security scope)
- Before deploying a new API endpoint, service, or integration to production
- When the codebase is new and no security baseline has been established
When NOT to Use
- Pure frontend UI tasks with no backend interaction
- Infrastructure configuration reviewed separately under a dedicated infra checklist
- Internal tooling with no external network exposure and no user data
Core Principle
Assume breach at every boundary. Every input is hostile, every token is potentially stolen, every dependency is potentially compromised. Security is not a final step — it is a constraint on every design decision.
Security Hardening Process
Use the checklist below and track progress:
Security hardening progress:
- [ ] Step 1: Inventory trust boundaries and data flows
- [ ] Step 2: Audit authentication and authorization
- [ ] Step 3: Validate input handling and injection surface
- [ ] Step 4: Review secrets and credential management
- [ ] Step 5: Check security headers and transport
- [ ] Step 6: Verify dependency and supply chain hygiene
- [ ] Step 7: Produce Security Review output
Step 1: Inventory trust boundaries and data flows
Map every point where untrusted data enters the system:
- HTTP request bodies, query params, headers, cookies
- File upload content and filenames
- Webhook payloads from third parties
- Data read from external APIs or message queues
- Data read from the database that originated from user input
For each entry point, verify: who validates it, where, and how failures are handled.
Step 2: Audit authentication and authorization
Authentication (who are you):
- Token signatures verified cryptographically, not just decoded
- Token expiry (
exp) checked explicitly
- Refresh token rotation implemented if long-lived sessions exist
- No credentials stored in localStorage — use httpOnly cookies or server-side sessions
Authorization (what are you allowed to do):
- Every route/handler has an explicit authorization check — no implicit "logged in = allowed"
- Object-level authorization: user A cannot access user B's resources by changing an ID
- Function-level authorization: low-privilege users cannot call admin endpoints by knowing the URL
- Authorization checked server-side, never trusted from client payload
Step 3: Validate input handling and injection surface
Injection (OWASP A03):
- SQL: use parameterized queries or ORM exclusively — no string concatenation into SQL
- Command injection: never pass user input to
exec, shell, spawn without strict allowlist
- Path traversal: normalize and validate file paths, reject
../ sequences, use path.resolve + boundary check
- SSRF: if accepting URLs from users, validate against an allowlist of allowed hosts/schemes
Input validation:
- Validate shape, type, and length at the boundary — before any business logic
- Reject unexpected fields explicitly (strip unknown properties, do not silently pass them)
- Validate file uploads: MIME type (from content inspection, not filename), size limit, extension allowlist
XSS (OWASP A03 — frontend impact):
- Never render user content as raw HTML
- Set
Content-Type: application/json explicitly on API responses
- Use
Content-Security-Policy header (see Step 5)
Step 4: Review secrets and credential management
- No secrets in source code — no hardcoded API keys, passwords, tokens, or private keys
- No secrets in environment variable names that reveal their value (avoid
DB_PASSWORD=prod123)
- Secrets loaded from environment or a secrets manager (Vault, AWS Secrets Manager, Doppler)
.env files in .gitignore — verified, not assumed
- CI/CD secrets injected at runtime, not stored in repo or build artifacts
- JWT signing keys rotatable without service downtime
- Database passwords rotatable — connection pools handle graceful reconnection
Audit pattern: search for password, secret, key, token, api_key literals in source and committed config files.
Step 5: Check security headers and transport
Every HTTP response must include:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
CORS:
Access-Control-Allow-Origin must not be * for authenticated endpoints
- Allowed origins must be an explicit allowlist, not a regex match on user-controlled values
- Credentials (
withCredentials: true) require explicit Access-Control-Allow-Credentials: true and a non-wildcard origin
Rate limiting:
- Authentication endpoints (login, register, password reset) must have rate limits
- Expensive compute or data endpoints must have rate limits
- Rate limits applied per IP and per user ID when authenticated
Step 6: Verify dependency and supply chain hygiene
- Run
npm audit, pip audit, or cargo audit — zero high/critical CVEs before deployment
- Lockfile committed and pinned — no floating
^ ranges in production without lockfile
- Dependency count minimized — remove unused packages
- Docker base images pinned to digest or minor version, not
latest
- No
eval, Function(), or dynamic code execution from user-controlled strings
Step 7: Produce Security Review output
## Security Review — [service/feature] — [date]
### Trust Boundaries
[Enumerated entry points and data flows]
### Findings
| Severity | Category | Location | Issue | Mitigation |
|---|---|---|---|---|
| CRITICAL | Injection | auth/login.ts:42 | Raw SQL concatenation | Use parameterized query |
| HIGH | AuthZ | api/documents.ts | Missing object-level auth | Add ownership check |
| MEDIUM | Secrets | .env.example | Real credentials committed | Rotate + remove |
| LOW | Headers | middleware/cors.ts | Wildcard CORS on auth routes | Restrict to allowlist |
### Verified Clean
[Areas reviewed and confirmed free of issues]
### Deferred
[Known issues accepted with documented reason and owner]
Severity definitions:
- CRITICAL: exploitable without authentication, leads to data breach or full compromise
- HIGH: exploitable by authenticated users, significant data exposure or privilege escalation
- MEDIUM: requires specific conditions, limited impact
- LOW: defense-in-depth improvement, no direct exploitability
Security Review Checklist
Authentication:
- [ ] Tokens verified cryptographically (signature + expiry)
- [ ] No credentials in localStorage
- [ ] Refresh token rotation implemented
Authorization:
- [ ] Every endpoint has explicit authZ check
- [ ] Object-level: user cannot access other users' resources
- [ ] Function-level: privilege escalation by URL is impossible
Input:
- [ ] All external input validated at entry boundary
- [ ] SQL uses parameterized queries exclusively
- [ ] File uploads validated by content, not filename
- [ ] No path traversal possible
Secrets:
- [ ] No secrets in source code or committed config
- [ ] .env in .gitignore — confirmed
- [ ] CI secrets injected at runtime
Transport:
- [ ] Security headers present on all responses
- [ ] CORS allowlist is explicit, not wildcard on authenticated routes
- [ ] Rate limiting on auth endpoints
Dependencies:
- [ ] npm/pip/cargo audit passes with no high/critical CVEs
- [ ] Lockfile committed and up to date
OWASP Top 10 Quick Reference
| OWASP ID |
Category |
Primary Mitigation |
| A01 |
Broken Access Control |
Object-level + function-level authZ on every endpoint |
| A02 |
Cryptographic Failures |
TLS everywhere, no secrets in code, verified token signatures |
| A03 |
Injection |
Parameterized queries, input validation at boundary, no dynamic eval |
| A04 |
Insecure Design |
Threat modeling at design phase, principle of least privilege |
| A05 |
Security Misconfiguration |
Security headers, no default credentials, no debug endpoints in prod |
| A06 |
Vulnerable Components |
Regular dependency audits, pinned versions |
| A07 |
Authentication Failures |
Rate limiting, secure token storage, rotation support |
| A08 |
Software Integrity |
Lockfiles, signed artifacts, no untrusted CDN scripts |
| A09 |
Logging Failures |
Log auth events, never log credentials or tokens |
| A10 |
SSRF |
URL allowlists, block internal ranges from user-supplied URLs |
Anti-Patterns to Avoid
| Anti-Pattern |
Instead Do |
| Security review as final step before deploy |
Security constraints baked into design and review |
SELECT * FROM users WHERE id = '${userId}' |
Parameterized query or ORM |
Trusting user-supplied role or isAdmin from JWT payload |
Verify against database at request time |
Access-Control-Allow-Origin: * on authenticated API |
Explicit origin allowlist |
| Plain-text or reversibly encrypted passwords |
One-way password hashing (argon2 or bcrypt) with strong parameters |
Hardcoded SECRET_KEY = "dev-secret" in source |
Environment variable, fail fast if missing |
Logging Authorization: Bearer <token> for debugging |
Log presence/absence only, never token value |
File path: path.join(uploadDir, userFilename) |
path.resolve + check result starts with uploadDir |
npm install without lockfile in CI |
Commit lockfile, use npm ci |
Connected Skills
code-review — run security-hardening as part of every backend code review
api-contract — API design decisions (auth scheme, input validation contract) are security decisions
technical-context-discovery — discover existing security patterns before introducing new ones
nestjs — NestJS-specific guards, interceptors, and validation pipe patterns
python-fastapi — FastAPI-specific dependency injection for auth and exception handlers
sql-and-database — parameterized queries, least-privilege DB users, migration safety
1---2name: security-hardening3description: Security review and hardening patterns for production backend systems. Covers OWASP Top 10, authentication and authorization patterns, secrets management, input validation, security headers, and API security. Use before any backend code ships to production, during security-focused code review, or when a task involves auth, user data, file handling, or external API integration.4metadata:5 source: https://skillmd.com/skills/jakubmikolajek/security-hardening6---78# Security Hardening910This skill enforces structured security analysis before code reaches production. It covers the most common vulnerabilities found in backend systems and provides concrete mitigation patterns per category.1112## When to Use1314- Any backend feature that handles user input, file uploads, or external data15- Any task involving authentication, authorization, sessions, or tokens16- Code review with a security focus (`/review` + security scope)17- Before deploying a new API endpoint, service, or integration to production18- When the codebase is new and no security baseline has been established1920## When NOT to Use2122- Pure frontend UI tasks with no backend interaction23- Infrastructure configuration reviewed separately under a dedicated infra checklist24- Internal tooling with no external network exposure and no user data2526## Core Principle2728**Assume breach at every boundary.** Every input is hostile, every token is potentially stolen, every dependency is potentially compromised. Security is not a final step — it is a constraint on every design decision.2930## Security Hardening Process3132Use the checklist below and track progress:3334```35Security hardening progress:36- [ ] Step 1: Inventory trust boundaries and data flows37- [ ] Step 2: Audit authentication and authorization38- [ ] Step 3: Validate input handling and injection surface39- [ ] Step 4: Review secrets and credential management40- [ ] Step 5: Check security headers and transport41- [ ] Step 6: Verify dependency and supply chain hygiene42- [ ] Step 7: Produce Security Review output43```4445**Step 1: Inventory trust boundaries and data flows**4647Map every point where untrusted data enters the system:48- HTTP request bodies, query params, headers, cookies49- File upload content and filenames50- Webhook payloads from third parties51- Data read from external APIs or message queues52- Data read from the database that originated from user input5354For each entry point, verify: who validates it, where, and how failures are handled.5556**Step 2: Audit authentication and authorization**5758Authentication (who are you):59- Token signatures verified cryptographically, not just decoded60- Token expiry (`exp`) checked explicitly61- Refresh token rotation implemented if long-lived sessions exist62- No credentials stored in localStorage — use httpOnly cookies or server-side sessions6364Authorization (what are you allowed to do):65- Every route/handler has an explicit authorization check — no implicit "logged in = allowed"66- Object-level authorization: user A cannot access user B's resources by changing an ID67- Function-level authorization: low-privilege users cannot call admin endpoints by knowing the URL68- Authorization checked server-side, never trusted from client payload6970**Step 3: Validate input handling and injection surface**7172Injection (OWASP A03):73- SQL: use parameterized queries or ORM exclusively — no string concatenation into SQL74- Command injection: never pass user input to `exec`, `shell`, `spawn` without strict allowlist75- Path traversal: normalize and validate file paths, reject `../` sequences, use `path.resolve` + boundary check76- SSRF: if accepting URLs from users, validate against an allowlist of allowed hosts/schemes7778Input validation:79- Validate shape, type, and length at the boundary — before any business logic80- Reject unexpected fields explicitly (strip unknown properties, do not silently pass them)81- Validate file uploads: MIME type (from content inspection, not filename), size limit, extension allowlist8283XSS (OWASP A03 — frontend impact):84- Never render user content as raw HTML85- Set `Content-Type: application/json` explicitly on API responses86- Use `Content-Security-Policy` header (see Step 5)8788**Step 4: Review secrets and credential management**8990- No secrets in source code — no hardcoded API keys, passwords, tokens, or private keys91- No secrets in environment variable names that reveal their value (avoid `DB_PASSWORD=prod123`)92- Secrets loaded from environment or a secrets manager (Vault, AWS Secrets Manager, Doppler)93- `.env` files in `.gitignore` — verified, not assumed94- CI/CD secrets injected at runtime, not stored in repo or build artifacts95- JWT signing keys rotatable without service downtime96- Database passwords rotatable — connection pools handle graceful reconnection9798Audit pattern: search for `password`, `secret`, `key`, `token`, `api_key` literals in source and committed config files.99100**Step 5: Check security headers and transport**101102Every HTTP response must include:103104```105Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'106X-Content-Type-Options: nosniff107X-Frame-Options: DENY108Strict-Transport-Security: max-age=63072000; includeSubDomains; preload109Referrer-Policy: strict-origin-when-cross-origin110Permissions-Policy: geolocation=(), microphone=(), camera=()111```112113CORS:114- `Access-Control-Allow-Origin` must not be `*` for authenticated endpoints115- Allowed origins must be an explicit allowlist, not a regex match on user-controlled values116- Credentials (`withCredentials: true`) require explicit `Access-Control-Allow-Credentials: true` and a non-wildcard origin117118Rate limiting:119- Authentication endpoints (login, register, password reset) must have rate limits120- Expensive compute or data endpoints must have rate limits121- Rate limits applied per IP and per user ID when authenticated122123**Step 6: Verify dependency and supply chain hygiene**124125- Run `npm audit`, `pip audit`, or `cargo audit` — zero high/critical CVEs before deployment126- Lockfile committed and pinned — no floating `^` ranges in production without lockfile127- Dependency count minimized — remove unused packages128- Docker base images pinned to digest or minor version, not `latest`129- No `eval`, `Function()`, or dynamic code execution from user-controlled strings130131**Step 7: Produce Security Review output**132133```markdown134## Security Review — [service/feature] — [date]135136### Trust Boundaries137[Enumerated entry points and data flows]138139### Findings140| Severity | Category | Location | Issue | Mitigation |141|---|---|---|---|---|142| CRITICAL | Injection | auth/login.ts:42 | Raw SQL concatenation | Use parameterized query |143| HIGH | AuthZ | api/documents.ts | Missing object-level auth | Add ownership check |144| MEDIUM | Secrets | .env.example | Real credentials committed | Rotate + remove |145| LOW | Headers | middleware/cors.ts | Wildcard CORS on auth routes | Restrict to allowlist |146147### Verified Clean148[Areas reviewed and confirmed free of issues]149150### Deferred151[Known issues accepted with documented reason and owner]152```153154Severity definitions:155- **CRITICAL**: exploitable without authentication, leads to data breach or full compromise156- **HIGH**: exploitable by authenticated users, significant data exposure or privilege escalation157- **MEDIUM**: requires specific conditions, limited impact158- **LOW**: defense-in-depth improvement, no direct exploitability159160## Security Review Checklist161162```163Authentication:164- [ ] Tokens verified cryptographically (signature + expiry)165- [ ] No credentials in localStorage166- [ ] Refresh token rotation implemented167168Authorization:169- [ ] Every endpoint has explicit authZ check170- [ ] Object-level: user cannot access other users' resources171- [ ] Function-level: privilege escalation by URL is impossible172173Input:174- [ ] All external input validated at entry boundary175- [ ] SQL uses parameterized queries exclusively176- [ ] File uploads validated by content, not filename177- [ ] No path traversal possible178179Secrets:180- [ ] No secrets in source code or committed config181- [ ] .env in .gitignore — confirmed182- [ ] CI secrets injected at runtime183184Transport:185- [ ] Security headers present on all responses186- [ ] CORS allowlist is explicit, not wildcard on authenticated routes187- [ ] Rate limiting on auth endpoints188189Dependencies:190- [ ] npm/pip/cargo audit passes with no high/critical CVEs191- [ ] Lockfile committed and up to date192```193194## OWASP Top 10 Quick Reference195196| OWASP ID | Category | Primary Mitigation |197|---|---|---|198| A01 | Broken Access Control | Object-level + function-level authZ on every endpoint |199| A02 | Cryptographic Failures | TLS everywhere, no secrets in code, verified token signatures |200| A03 | Injection | Parameterized queries, input validation at boundary, no dynamic eval |201| A04 | Insecure Design | Threat modeling at design phase, principle of least privilege |202| A05 | Security Misconfiguration | Security headers, no default credentials, no debug endpoints in prod |203| A06 | Vulnerable Components | Regular dependency audits, pinned versions |204| A07 | Authentication Failures | Rate limiting, secure token storage, rotation support |205| A08 | Software Integrity | Lockfiles, signed artifacts, no untrusted CDN scripts |206| A09 | Logging Failures | Log auth events, never log credentials or tokens |207| A10 | SSRF | URL allowlists, block internal ranges from user-supplied URLs |208209## Anti-Patterns to Avoid210211| Anti-Pattern | Instead Do |212|---|---|213| Security review as final step before deploy | Security constraints baked into design and review |214| `SELECT * FROM users WHERE id = '${userId}'` | Parameterized query or ORM |215| Trusting user-supplied `role` or `isAdmin` from JWT payload | Verify against database at request time |216| `Access-Control-Allow-Origin: *` on authenticated API | Explicit origin allowlist |217| Plain-text or reversibly encrypted passwords | One-way password hashing (`argon2` or `bcrypt`) with strong parameters |218| Hardcoded `SECRET_KEY = "dev-secret"` in source | Environment variable, fail fast if missing |219| Logging `Authorization: Bearer <token>` for debugging | Log presence/absence only, never token value |220| File path: `path.join(uploadDir, userFilename)` | `path.resolve` + check result starts with `uploadDir` |221| `npm install` without lockfile in CI | Commit lockfile, use `npm ci` |222223## Connected Skills224225- `code-review` — run security-hardening as part of every backend code review226- `api-contract` — API design decisions (auth scheme, input validation contract) are security decisions227- `technical-context-discovery` — discover existing security patterns before introducing new ones228- `nestjs` — NestJS-specific guards, interceptors, and validation pipe patterns229- `python-fastapi` — FastAPI-specific dependency injection for auth and exception handlers230- `sql-and-database` — parameterized queries, least-privilege DB users, migration safety