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

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-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.
  • 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.