oatpp-authkit/docs/security-baseline.md
Uwe Schuster 32356ad226 v0.1.0: initial clean-lift from fewo-webapp
Header-only C++ library; CMake config package; zero-coupling files lifted
from fewo-webapp:

  interceptor/SecurityHeadersInterceptor.hpp
  interceptor/BodySizeLimitInterceptor.hpp
  handler/JsonErrorHandler.hpp
  util/RateLimiter.hpp
  util/TokenExtract.hpp    (extractToken, isValidIp, clientIpTrusted)
  startup/RequireEncryptionKey.hpp

fewo-specific couplings (bindAddress global, fewo::config) replaced with
explicit function arguments so the library stands alone.

AuthInterceptor + requireAdmin deferred to v0.2 — they need IAuthBackend /
IAuthPolicy / IRuntimeConfig seams designed first.

docs/security-baseline.md ships CSP / rate-limit / body-size / encryption
key constants as language-neutral baselines for non-C++ consumers.

Closes fewo-webapp#412

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:42:53 +02:00

48 lines
2.7 KiB
Markdown

# 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/<name>/prod.env`.
## Session cookie (not in library v0.1 — convention documented here)
- `Set-Cookie: session=<token>; 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.