# Security baseline — language-neutral constants These are the defaults enforced by the C++ interceptors and utilities. Consumers on other stacks (including palibu's C backend, while it's still pre-migration) should mirror the same values rather than re-deriving them. ## Response headers (SecurityHeadersInterceptor) | Header | Value | Why | |--------|-------|-----| | `Content-Security-Policy` | `default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'` | Strict by default. Opt in to `https://unpkg.com` only for the Swagger UI route on apps that serve it. | | `X-Frame-Options` | `DENY` | Defence in depth against clickjacking (CSP `frame-ancestors` is primary). | | `X-Content-Type-Options` | `nosniff` | | | `Referrer-Policy` | `strict-origin-when-cross-origin` | | | `Permissions-Policy` | `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()` | Disable sensors the apps don't need. | ## Request limits (BodySizeLimitInterceptor) - **Max body size:** 1 MiB (1,048,576 bytes). Fail with HTTP 413 (`Payload Too Large`). - **Paths exempt:** none in the baseline. Upload-heavy apps override per-route. ## Rate limiting (RateLimiter) - **Key:** `clientIpTrusted(request, bindAddress)` — returns a validated IPv4/IPv6 string, or `"invalid"`/`"unknown"`. Never attacker-chosen free-form text. - **Default bucket:** 60 requests / 60 seconds per key. Tune per endpoint. - **Loopback-only XFF trust:** `X-Forwarded-For` / `X-Real-IP` are honoured only when the service binds to `127.0.0.1` / `::1` / `localhost`. On a public interface we fall back to `"unknown"` (all untagged callers share one bucket, which is stricter than letting forged headers dilute it). ## Encryption key gate (startup::requireEncryptionKey) - **Required env var** (name is consumer-chosen; fewo uses `FEWO_ENCRYPTION_KEY`, the scaffold standard is `APP_ENCRYPTION_KEY`): symmetric key, SHA-256 derives the actual AES-256-GCM key. - **Startup behaviour:** missing key + no `--allow-plaintext` dev flag ⇒ refuse to start (throws). Missing key + `--allow-plaintext` ⇒ WARN and continue with plaintext storage. - **Rationale:** prevents PII / credentials silently landing in plaintext production databases when an operator forgets to populate `/etc//prod.env`. ## Session cookie (not in library v0.1 — convention documented here) - `Set-Cookie: session=; HttpOnly; SameSite=Strict; Path=/; Max-Age=2592000` (30 days). Add `Secure` when serving behind TLS. - Token: 32 random bytes base64url-encoded. Stored in the session table hashed (SHA-256) so DB compromise doesn't yield session hijacking.