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
- Security headers on every response:
Strict-Transport-Security: max-age=31536000; includeSubDomains,X-Content-Type-Options: nosniff,Content-Security-Policy(startdefault-src 'self'),frame-ancestors 'none'(or the one legit embedder),Referrer-Policy: strict-origin-when-cross-origin. - 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.
- 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.
- Dependencies: lockfile + scanner + cadence. Commit the lockfile, run
pip-audit/npm audit/Dependabot in CI, patch criticals within days not quarters. - 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
- 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.
- Errors never leak internals. Map exceptions to problem+json with a correlation ID; stack traces, SQL, and versions go to logs only.
- Service accounts get least privilege — scoped API keys, no wildcard IAM, one identity per service so revocation is surgical.
Workflow
- Inventory: endpoints, secrets, outbound URL fetches, cookie-authenticated mutations, third-party dependencies.
- Apply rules 1–2 at the middleware/edge layer (one place, not per route).
- Move any in-code secrets out (rule 3) and rotate them — moving without rotating fixes nothing.
- Add SSRF/CSRF defenses where the inventory found exposure (rules 5–6); wire the error mapper (rule 7).
- Validate:
curl -sIeach 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 tounsafe-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.