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.
136 lines
15 KiB
Markdown
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)
|