--- name: securing-backend-services description: Hardens backend services against common attacks — security headers, TLS, secret management, dependency scanning, SSRF and CSRF defenses, and safe error responses. Use when the user asks to secure a service or API, add security headers or CSP, manage secrets, run a security review of a backend, fix an SSRF/CSRF finding, or prepare for a pentest. Do not use for login flows (implementing-authentication), permission models (implementing-authorization), request schemas (validating-input), or database security (securing-databases). --- # Securing Backend Services ## When to use / when NOT to use - **Use for:** OWASP-aligned service hardening — headers, TLS, secrets, dependencies, SSRF/CSRF, error hygiene, service-account privilege. - **Do NOT use for:** authentication flows, authorization models, input schemas, or database roles/encryption — each has its own skill. This skill is the layer around them. ## Core rules 1. **Security headers on every response:** `Strict-Transport-Security: max-age=31536000; includeSubDomains`, `X-Content-Type-Options: nosniff`, `Content-Security-Policy` (start `default-src 'self'`), `frame-ancestors 'none'` (or the one legit embedder), `Referrer-Policy: strict-origin-when-cross-origin`. 2. **TLS everywhere, including service-to-service.** Redirect HTTP→HTTPS at the edge; internal traffic gets mTLS or a private mesh — "internal" networks are not trusted. 3. **Secrets never live in code or VCS.** Environment variables or a secret manager; distinct secrets per environment; rotate immediately on any exposure and treat the git history as public once pushed. 4. **Dependencies: lockfile + scanner + cadence.** Commit the lockfile, run `pip-audit`/`npm audit`/Dependabot in CI, patch criticals within days not quarters. 5. **SSRF: user-supplied URLs are hostile.** - ✅ Allowlist destination hosts; resolve DNS and reject private/link-local ranges (10.x, 172.16–31, 192.168, 169.254, ::1); disable redirects or re-check after each hop - ❌ `requests.get(user_url)` — hello, cloud metadata endpoint 6. **CSRF tokens for every cookie-authenticated state change.** SameSite helps but is not sufficient (top-level POST exemptions, old clients); use the framework's CSRF middleware. 7. **Errors never leak internals.** Map exceptions to problem+json with a correlation ID; stack traces, SQL, and versions go to logs only. 8. **Service accounts get least privilege** — scoped API keys, no wildcard IAM, one identity per service so revocation is surgical. ## Workflow 1. Inventory: endpoints, secrets, outbound URL fetches, cookie-authenticated mutations, third-party dependencies. 2. Apply rules 1–2 at the middleware/edge layer (one place, not per route). 3. Move any in-code secrets out (rule 3) and rotate them — moving without rotating fixes nothing. 4. Add SSRF/CSRF defenses where the inventory found exposure (rules 5–6); wire the error mapper (rule 7). 5. **Validate:** `curl -sI` each surface and confirm the rule-1 headers; run the dependency scanner to zero criticals; request a known-bad internal URL through any fetch feature (expect rejection); trigger an exception and confirm the response body contains no stack trace. All four must pass. ## Edge cases & failure modes - **CSP breaks inline scripts** → prefer nonces (`'nonce-...'`) over loosening to `unsafe-inline`. - **Header set twice (app + proxy)** → duplicated CSP is intersected by browsers; set each header in exactly one layer. - **Webhooks/health checks behind mTLS** → give external callers a dedicated ingress with its own auth, don't weaken the default. - **Secret manager outage** → cache secrets in memory with TTL; never fall back to a baked-in default secret. ## References Copy-paste patterns and gotchas: see [references/patterns.md](references/patterns.md).