fix(spa): SPA hardening cluster (state/nonce, dev-bypass feature, pending TTL, CSP, doc) #384
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/mokosh-apps!384
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/MAPPS-338-spa-hardening-cluster"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
MAPPS-338: four surgical hardening items from the 2026-06-30 audit. Item 1 of the original five (id_token signature verify in WASM via JWKS) is deferred to a separate ticket because it requires more infrastructure (WASM-compatible jsonwebtoken + JWKS fetch + kid cache); the four landing here are all bounded edits.
src/modules/oidc/pkce.rs::random_opaquenow generates 32 bytes (256 bits) instead of 16. RFC 6819 §5.3.5 floorsstateat 128 bits; matching the PKCE verifier entropy budget removes nonce as the weakest link in the flow.src/hooks/auth.rs::initial_auth_contextwas gated oncfg(debug_assertions)alone, so a debug WASM that shipped to staging by accident (CI mishap, operator build) auto-signed the user in as Admin from compile-timeADMIN_EMAIL/ADMIN_PASSWORD. Newdev_admin_bypassfeature is OFF by default even in debug builds; dev devs opt in viacargo run --features dev_admin_bypass.src/modules/oidc/storage.rs::PendingFlownow carriesissued_at_ms, stamped onsave_pendingand checked ontake_pending. Flows older thanPENDING_FLOW_TTL_MS(10 min) are refused, so a week-old tab does not satisfy the OIDC callback. The mint side insrc/modules/oidc/flow.rsis updated to pass&mut PendingFlow;#[serde(default)]on the new field keeps existing in-flight session storage rows deserializable (theirissued_at_ms = 0reads as expired, which forces a clean re-auth).oci-build/Caddyfilepreviously emitted onlyContent-Security-Policy: frame-ancestors 'none'becauseconnect-srcdepends onMOKOSH_API_BASE/MOKOSH_OIDC_ISSUER(set by the entrypoint at container start and unknown at build time). Caddy's{$VAR:-default}expands at startup, so the same image now ships a full CSP (default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self' {$MOKOSH_API_BASE:-} {$MOKOSH_OIDC_ISSUER:-}; ...) that pins the SPA's exfil surface. When the env vars are empty the directive collapses to'self'for those sources, which is correct for the same-origin (host-prefix-derived) case.#MAPPS-338
MAPPS-338: four surgical hardening items from the 2026-06-30 audit. Item 1 of the original five (id_token signature verify in WASM via JWKS) is deferred to a separate ticket because it requires more infrastructure (WASM-compatible jsonwebtoken + JWKS fetch + kid cache); the four landing here are all bounded edits. 1. **state + nonce 32 bytes.** `src/modules/oidc/pkce.rs::random_opaque` now generates 32 bytes (256 bits) instead of 16. RFC 6819 §5.3.5 floors `state` at 128 bits; matching the PKCE verifier entropy budget removes nonce as the weakest link in the flow. 2. **dev-admin bypass behind a feature flag.** `src/hooks/auth.rs::initial_auth_context` was gated on `cfg(debug_assertions)` alone, so a debug WASM that shipped to staging by accident (CI mishap, operator build) auto-signed the user in as Admin from compile-time `ADMIN_EMAIL`/`ADMIN_PASSWORD`. New `dev_admin_bypass` feature is OFF by default even in debug builds; dev devs opt in via `cargo run --features dev_admin_bypass`. 3. **PendingFlow TTL.** `src/modules/oidc/storage.rs::PendingFlow` now carries `issued_at_ms`, stamped on `save_pending` and checked on `take_pending`. Flows older than `PENDING_FLOW_TTL_MS` (10 min) are refused, so a week-old tab does not satisfy the OIDC callback. The mint side in `src/modules/oidc/flow.rs` is updated to pass `&mut PendingFlow`; `#[serde(default)]` on the new field keeps existing in-flight session storage rows deserializable (their `issued_at_ms = 0` reads as expired, which forces a clean re-auth). 4. **Runtime CSP from env.** `oci-build/Caddyfile` previously emitted only `Content-Security-Policy: frame-ancestors 'none'` because `connect-src` depends on `MOKOSH_API_BASE` / `MOKOSH_OIDC_ISSUER` (set by the entrypoint at container start and unknown at build time). Caddy's `{$VAR:-default}` expands at startup, so the same image now ships a full CSP (`default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self' {$MOKOSH_API_BASE:-} {$MOKOSH_OIDC_ISSUER:-}; ...`) that pins the SPA's exfil surface. When the env vars are empty the directive collapses to `'self'` for those sources, which is correct for the same-origin (host-prefix-derived) case. #MAPPS-338