feat: per-app MFA guard in the proxy (#1005 slice 4) - #1011
Merged
Conversation
milkway
force-pushed
the
feat/1005-mfa-guard
branch
2 times, most recently
from
July 18, 2026 03:49
e459b65 to
ebd7cad
Compare
milkway
marked this pull request as ready for review
July 18, 2026 03:50
milkway
force-pushed
the
feat/1005-mfa-challenge
branch
from
July 18, 2026 03:51
c1def60 to
1b0cfca
Compare
The enforcement slice — the epic's last. The guard is step 2e in forward(): after access control (2d), BEFORE backend check, splash, sticky selection and pick/spawn — a blocked request can never select, wake or spawn a replica. - /app/*: anonymous → 303 login (safe, encoded next, base-path aware); unenrolled → 303 enrollment; stale/absent proof → 303 challenge; blocked WS handshake → 401 (no redirect on upgrades). - /api/*: fail closed and explicit — 401 (no session) / 403 (MFA unsatisfied), CORS-wrapped, never a Location header. - Break-glass (token session, no actor): bypasses so an operator can't be locked out — but LOUD: warn! on every bypass + audit mfa.break_glass_bypass deduplicated per (session, spec) for 15 min. - Zero cost for specs without require-mfa (no DB reads). - Staged-rollout scaffolding retired: MfaNotYetEnforced warning (+CLI formatter, tests), the form's staged note ×4 locales, and the docs' "not yet enforced" caveat are gone; YAML_SCHEMA/SECURITY/admin guide describe the real semantics. - tests/mfa_guard.rs: 8 scenarios incl. the no-spawn guarantee (mock backend flags any spawn attempt), 0-day session binding through the guard, break-glass audit dedup, and API-vs-app behavior. Implemented by a Codex (gpt-5.6, high) agent; full gate green in its sandbox; verification (rebase onto the fixed slice 3, cold-build gate, postgres-it, live end-to-end smoke, independent /code-review) follows in the next commits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…#1005 slice 4 review) Two codex-review findings: - P1: require-mfa on an External-link spec did nothing — External specs are plain links Ruscker never proxies, so the guard (which lives in forward()) is never reached, yet the schema/form accepted the flag with no signal. An operator could believe the linked app is protected while anonymous users reach it freely. New validation warning MfaOnExternalSpec (CLI formatter + test + YAML_SCHEMA note); the guard genuinely can't run there, so flagging config is the correct fix. - P2: the break-glass audit dedup reserved the (session, spec) key BEFORE the DB write; a transient write failure left the key timestamped, suppressing the audit for the whole 15-min cooldown — a persistent gap even after the DB recovered. The reservation is now rolled back (removed) on write failure and on the no-DB path, so the next bypass retries. Over-auditing is safe; a gap is not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Slice 3's UPSERT redesign dropped issue()'s replace_grant_id argument; the guard test's grant helper is updated to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
milkway
force-pushed
the
feat/1005-mfa-guard
branch
from
July 18, 2026 03:52
ebd7cad to
5ad85f6
Compare
14 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Slice 4 of 4 — the final slice: enforcement. Stacked on #1009 (slice 3) — merge order 1→2→3→4. Closes the epic.
What changed
forward(): after access control (2d), BEFORE backend check / splash / sticky selection / pick-spawn — so a blocked request can never select, wake or spawn a replica./app/*303login (safe, encodednext, base-path aware)/app/*303enrollment/app/*303challenge/app/*401(no redirect on upgrades)/api/*401, CORS-wrapped, noLocation/api/*403, CORS-wrapped, noLocationwarn!+ auditmfa.break_glass_bypass(deduped per session/spec, 15 min)require-mfa(no DB reads).MfaNotYetEnforcedwarning (+ CLI formatter, tests), the form's staged note ×4 locales, and the docs' "not yet enforced" caveat are gone; YAML_SCHEMA/SECURITY/admin guide describe the real semantics.Review hardening (3 findings across rounds, all fixed → r3 clean)
require-mfaon an External spec did nothing (guard never runs on a link) → validation warningMfaOnExternalSpec.Validation
tests/mfa_guard.rs: 8 scenarios incl. the no-spawn guarantee (mock backend panics on any spawn), 0-day session binding through the guard, break-glass audit dedup, API-vs-app behavior./app→ redirected to enrollment → QR → challenge → app opens (container born only after the grant, confirmed by timestamp); protected/api→ 401/403; break-glass → 200 + exactly one audit row; a blocked request never spawned a container.🤖 Generated with Claude Code