Files
moloapp/plans/03-security.md
T
retoor 54068f1953 docs: split plan into numbered detailed files with research
Ten sequential spec files plus index covering runtime,
observability, security, data, routing, realtime, supervisor,
clients, CLI ops, and workflow, each with best practices,
structure, rules, guidelines, specs, and cross-references.
2026-10-23 14:22:29 +02:00

136 lines
15 KiB
Markdown

# 03 - Security
Scope: plan.md sections 2, 4, 10 step 2. Topics: TLS wolfSSL, auth, hashing and crypto, sessions, guards, upload safety. Also covers CSRF, CORS, security headers, rate limiting, input caps, secrets, XSS-safe defaults, path traversal, ORM query safety contract.
## Purpose and philosophy
Secure by default, specified not folkloric. Every guard ships on with safe defaults and an explicit override path. Deny by default: closed CORS, strict headers, capped inputs, authenticated sessions. One shared core owns each primitive (hashing, encryption, sessions, TLS); features consume it through declared interfaces, never reimplement. Static ceilings everywhere, no autotuning. Loud rejection with structured reason and counter, never silent downgrade.
## Best practices (from research)
- Passwords: Argon2id first choice, minimum m=19456 (19 MiB), t=2, p=1; bcrypt work factor 10 or more for legacy; PBKDF2 600,000+ with HMAC-SHA-256 only for FIPS-140; unique random salt per password; optional pepper stored outside the DB in a vault or HSM; hash verification under one second; re-hash on login when work factor rises (OWASP Password Storage).
- Argon2 standard: Argon2id MUST be supported; salt 16 bytes RECOMMENDED, unique per password; FIRST RECOMMENDED option t=1, p=4, m=2 GiB; SECOND for constrained hosts t=3, p=4, m=64 MiB; OWASP minimum (19 MiB, t=2, p=1) is the interactive-login tuning of the same primitive, RFC values are the max-security anchors (RFC 9106, triangulated with OWASP).
- Sessions: ID minimum 64 bits entropy, framework uses 128-bit CSPRNG IDs; value meaningless, logic server-side; cookies Secure, HttpOnly, SameSite Strict or Lax; renew ID on any privilege change; idle plus absolute plus renewal timeouts; validate IDs as untrusted input (OWASP Session Management).
- CSRF: stateful apps use synchronizer token pattern; stateless or API fallback is signed double-submit cookie with HMAC bound to session data, naive unsigned double-submit is discouraged (cookie injection via sibling subdomain); Fetch Metadata Sec-Fetch-Site as lightweight cross-site block; custom headers for AJAX APIs (OWASP CSRF Prevention).
- Uploads: allow-list business-critical extensions only; never trust Content-Type header, verify magic bytes and content; generate server-side filenames; cap size and name length; store outside webroot or on separate host with ID mapping handler; AV or sandbox plus CDR hook; authorized users only (OWASP File Upload).
- Headers: X-Frame-Options DENY (frame-ancestors in CSP for modern browsers), X-Content-Type-Options nosniff, Referrer-Policy strict-origin-when-cross-origin set explicitly, HSTS max-age=63072000 with includeSubDomains and preload, restrictive CSP, minimal Permissions-Policy (OWASP HTTP Headers).
- TLS: wolfSSL small fast portable TLS up to 1.3 plus DTLS 1.3, wolfCrypt core, FIPS-validated builds available, OpenSSL-compat API for porting, dual GPL or commercial license; MoloApp uses commercial license for closed-source use, TLS 1.3 only on the server path (wolfSSL repo).
## Structure
Directory: `src/security/`, one class or concern per file. Namespace root `molo::security`, sub-namespaces `tls`, `auth`, `crypto`, `session`, `guard`, `upload`, `secret`.
- `tls/TlsConfig.h`: cipher suites, ALPN list (h2, http/1.1), cert paths, min version 1.3.
- `tls/TlsContext.h`: owns wolfSSL CTX lifecycle, loads certs, applies config, no per-connection state.
- `tls/TlsAcceptor.h`: non-blocking handshake on the event loop, SNI dispatch, failure counters.
- `crypto/PasswordHasher.h`: Argon2id verify and hash, params struct, needs-rehash check.
- `crypto/CryptoBox.h`: AES-GCM plus ChaCha20-Poly1305 encrypt and decrypt with key IDs.
- `crypto/KeyHierarchy.h`: master key, per-purpose subkeys (cookies, sessions, CSRF, uploads), rotation epochs.
- `crypto/SecureRandom.h`: CSPRNG bytes, token generation, constant-time compare.
- `auth/Authenticator.h`: Basic auth parsing, credential check, login and logout, re-auth gate.
- `auth/Permission.h` and `auth/Guard.h`: permission objects, route guards, deny-by-default checks.
- `session/Session.h`: session value object, expiry fields, privilege level.
- `session/SessionStore.h`: interface (get, put, rotate, destroy); `session/MemoryStore.h`, `session/DbStore.h` implement it.
- `session/SessionManager.h`: cookie issue, rotation, idle and absolute enforcement.
- `guard/CsrfGuard.h`: synchronizer tokens plus signed double-submit fallback, Fetch Metadata check.
- `guard/CorsPolicy.h`: closed default, explicit origin and method allow-lists, preflight handler.
- `guard/SecurityHeaders.h`: HSTS, framing, nosniff, referrer, permissions, CSP builder hook.
- `guard/RateLimiter.h`: token bucket per IP, user, route; headers Retry-After.
- `guard/BodyLimits.h`: header, body, fields, JSON depth enforcement before parsing completes.
- `upload/UploadValidator.h`: extension allow-list, magic bytes, size and name caps, temp file handle.
- `upload/UploadStore.h`: quarantine dir, final store outside webroot, AV hook interface.
- `secret/SecretsProvider.h`: .env.json plus env overrides, typed validated secrets, rotation API.
## Rules (hard, numbered)
1. TLS 1.3 only on all server listeners; no TLS 1.2 fallback unless a major version spec reopens it.
2. wolfSSL is the sole third-party dependency for TLS; crypto (Argon2id, AES-GCM, ChaCha20-Poly1305) is in-house, never per-feature forks.
3. Parameterized queries only; raw SQL string concatenation with user input is a build-breaking violation (contract owned here, enforced in 04-data.md).
4. Passwords stored as Argon2id with unique 16-byte salt; plaintext or fast hashes (MD5, SHA-1, raw SHA-256) never accepted.
5. Session IDs are 128-bit CSPRNG, meaningless, rotated on login, privilege change, and re-auth.
6. Cookies carrying sessions or CSRF state are Secure, HttpOnly, SameSite (Strict default, Lax opt-in).
7. CSRF protection on every state-changing cookie route; exempted CORS or webhook endpoints must be listed explicitly with auth plus logging.
8. CORS default deny; wildcard origin with credentials is rejected at config load.
9. Security headers middleware is always in the default chain; removal requires explicit per-route opt-out recorded in config.
10. All inputs capped before allocation: headers, body, fields, JSON depth, uploads; over-cap requests fail closed with 413 or 431.
11. Uploaded files never execute: stored outside webroot, served only through an ID handler with nosniff and safe Content-Disposition.
12. Secrets never in code, logs, or error bodies; rotation revokes old keys within one absolute session lifetime.
## Development guidelines
- Extremely object-oriented: one small class per file, deep namespaces, shared behavior in exactly one base.
- Guards are middleware or router wrappers with a single `check(request)` entry; no inline security logic in handlers.
- Every denial emits a structured log plus metric with reason code, never the secret or full token.
- Constant-time compare for hashes, tokens, HMACs; zeroize key material on drop.
- Blocking crypto (Argon2id verify) runs off the event loop via the blocking-work path from 01-runtime.md.
- Config is typed and validated at startup; invalid security config aborts boot with a named error.
- Mobile-first has no security shortcut: same guards on small-screen and desktop template paths.
## Specifications (concrete behaviors, caps, formats)
- Argon2id: default m=19456 KiB, t=2, p=1; stored format `$argon2id$v=19$m=...,t=...,p=...$salt$hash`; verify budget under 1 s; `needs_rehash` true when stored params are weaker.
- Data encryption: AES-GCM with 96-bit nonce or ChaCha20-Poly1305 with 96-bit nonce; envelope `v1:key_id:nonce:ct:tag`; key rotation keeps previous epoch readable for 24 h.
- Sessions: cookie name `__Host-molo_sid`; 128-bit ID, base64url 22 chars min; idle timeout 30 min, absolute 12 h, renewal cap 24 h; rotation on privilege change; store interface supports memory and DB backends.
- CSRF: header `X-CSRF-Token` or form field `csrf_token` must match session-bound value; signed fallback token is `HMAC_SHA256(session_id, nonce)`; Fetch Metadata deny when `Sec-Fetch-Site: cross-site` on state-changing methods unless route is exempted.
- CORS: default no `Access-Control-Allow-Origin`; preflight cache max 600 s; allowed methods and headers explicit lists; credentials require exact origin match.
- Headers defaults: `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy: geolocation=(), camera=(), microphone=()`; CSP builder default `default-src 'self'; object-src 'none'; frame-ancestors 'none'`.
- Rate limits: per-IP 100 req/min, per-user 300 req/min, per-route override; burst 2x for 10 s; 429 with `Retry-After`; counters in memory with static cap 100k buckets, LRU evict.
- Input caps: headers 16 KB total, single header 8 KB; body 1 MB default (route override to 10 MB); form fields 1000; JSON depth 32, JSON size bound by body cap; upload default 10 MB per file, 4 files per request; filename 255 chars, generated storage name `uuid.ext`.
- Upload checks: extension allow-list per route (default images: jpg, png, webp; docs: pdf, docx); magic-byte sniff for declared type; temp dir with 0600 perms, swept after 1 h; AV hook `scan(path) -> clean or infected`, infected goes to quarantine, never to store.
- Auth: Basic `Authorization: Basic base64(user:pass)` parsed once, constant-time user lookup path; login rate limited 10 attempts per min per IP; high-risk actions require re-auth within last 10 min.
- Path traversal: static and upload paths canonicalized, `..` and absolute and `:` rejected, symlink escape rejected, serve root jailed (implements plan.md static route rule).
- TLS: min TLS 1.3, ALPN `h2,http/1.1`, H2 over TLS plus h2c cleartext per 01-runtime.md; cert reload without restart; handshake timeout 5 s; failed-handshake counter with per-IP backoff.
## Execution steps (in build order)
1. SecureRandom, KeyHierarchy, CryptoBox with RFC test vectors and round-trip tests.
2. PasswordHasher Argon2id with OWASP params, salt, pepper hook, needs-rehash.
3. TlsConfig, TlsContext, TlsAcceptor on wolfSSL; ALPN, SNI, reload, handshake caps.
4. Session, SessionStore (memory first), SessionManager with rotation and timeouts.
5. Authenticator plus Permission and Guard; Basic auth, login, re-auth, deny-by-default.
6. CsrfGuard (synchronizer first, signed double-submit second, Fetch Metadata third).
7. CorsPolicy and SecurityHeaders middleware in the default chain.
8. RateLimiter and BodyLimits wired before routing and parsing.
9. UploadValidator and UploadStore with quarantine and AV hook.
10. SecretsProvider with .env.json, env overrides, rotation; audit pass over all denials and logs.
## Validation (how to verify each step)
- Steps 1 to 2: RFC 9106 Argon2id test vectors pass; OWASP params confirmed in config dump; wrong-password, wrong-pepper, weak-params cases covered; verify latency under 1 s on reference host; integration test through a login URL, not a unit-only hash call.
- Step 3: real TLS 1.3 handshake against the server with valid and expired and wrong-SNI certs; ALPN negotiates h2 and http/1.1; TLS 1.2 client fails closed; cert reload keeps existing connections alive.
- Steps 4 to 5: login rotates session ID; privilege change rotates again; idle expiry at 31 min and absolute at 12 h enforced through real requests; Basic auth good and bad credentials; guarded route denies anonymous with 401 or 403 and structured log.
- Step 6: POST without token fails 403; token from another session fails; signed double-submit passes with matching cookie and header; cross-site Fetch Metadata POST blocked; exempted webhook passes with auth and audit entry.
- Step 7: default responses carry all six header families; CORS preflight from unlisted origin gets no allow header; wildcard-with-credentials config refuses to boot.
- Step 8: burst over per-IP cap yields 429 with Retry-After; oversized body yields 413 before handler runs; JSON depth 33 rejected; header flood yields 431.
- Step 9: double extension `x.jpg.php`, null byte, case trick `x.pHp`, and spoofed Content-Type all rejected; valid file stored outside webroot under generated name and served via ID handler; infected fixture quarantined via stub AV hook; quota overage rejected.
- Step 10: secrets load from .env.json with env override winning; rotation invalidates old cookie crypto epoch; no secret appears in logs or error pages under failure injection; full integration suite green in the same session.
## References
- Depends on 01-runtime.md for event loop off-loop work, cookies, static serving, H2 and ALPN, lifecycle caps.
- Depends on 02-observability.md for structured denial logs, auth and TLS metrics, trace linkage.
- Feeds 04-data.md the parameterized-query rule and DB session store backend.
- Feeds 05-request-routing.md guards, validation caps, error mapping, CSP builder for templates.
- Feeds 06-realtime.md auth and session behavior reused by WebSocket, SSE, Socket.IO.
- Feeds 07-supervisor-replication.md session sharing across instances and secret distribution.
- Feeds 09-cli-ops.md health endpoints that must bypass auth safely and i18n-safe error pages.
- Demonstrated end to end by 10-workflow-example.md living example and its integration suite.
## Open questions
1. Pepper management: single global pepper with forced reset on rotation, or per-epoch peppers with dual-verify window? Leaning to per-epoch with 24 h overlap.
2. Session store default in production: DB-backed sessions or a Redis-like external store? DB first keeps zero extra deps; external store may follow in 04-data.md.
3. AV hook shape: synchronous `scan(path)` blocking upload response, or async quarantine-then-release with event? Async fits non-blocking rule but complicates UX.
4. Rate limit sharing across instances: local buckets only for v1, or replicated counters in step 6? Local first, document divergence.
5. Commercial wolfSSL licensing integration into CI builds: vendored tarball or system package? Needs a build-owner decision.
## Sources
- OWASP Password Storage Cheat Sheet, https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html (retrieved 2026-10-23)
- OWASP Session Management Cheat Sheet, https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html (retrieved 2026-10-23)
- OWASP Cross-Site Request Forgery Prevention Cheat Sheet, https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html (retrieved 2026-10-23)
- OWASP File Upload Cheat Sheet, https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html (retrieved 2026-10-23)
- OWASP HTTP Headers Cheat Sheet, https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html (retrieved 2026-10-23)
- RFC 9106 Argon2 Memory-Hard Function, https://www.rfc-editor.org/rfc/rfc9106.html (retrieved 2026-10-23)
- wolfSSL repository (TLS 1.3, wolfCrypt, licensing), https://github.com/wolfSSL/wolfssl (retrieved 2026-10-23)