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>
2.7 KiB
2.7 KiB
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-IPare honoured only when the service binds to127.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 isAPP_ENCRYPTION_KEY): symmetric key, SHA-256 derives the actual AES-256-GCM key. - Startup behaviour: missing key + no
--allow-plaintextdev 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). AddSecurewhen 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.