Skip to content

security: bind OIDC login flow to the browser (login-CSRF guard)#92

Merged
tcconnally merged 1 commit into
mainfrom
fix/oauth-login-csrf
Jul 5, 2026
Merged

security: bind OIDC login flow to the browser (login-CSRF guard)#92
tcconnally merged 1 commit into
mainfrom
fix/oauth-login-csrf

Conversation

@tcconnally

Copy link
Copy Markdown
Contributor

Security fix — OIDC login-CSRF (HIGH)

From the 2026-07-05 pre-launch security review.

Problem: the OAuth state was stored only in a process-global pool (_pending) and never bound to the initiating browser. An attacker could complete their own Google sign-in, capture their unredeemed code+state, and send a victim a /auth/callback?code=…&state=… link. The victim's browser would exchange the attacker's code and be issued a session for the attacker's account/tenant — after which the victim's actions (and any API keys they create) land in, and are readable by, the attacker.

Fix: /auth/login now sets a short-lived HttpOnly; SameSite=Lax (Secure when base_url is https) plutus_oauth_state cookie carrying the state. handle_callback requires the callback state to secrets.compare_digest-match that cookie, in addition to the existing _pending nonce check — binding the flow to the browser that began it. The cookie is cleared on successful callback.

Changes

  • auth.py: login_url returns (url, state); handle_callback takes cookie_state and enforces the match; read_cookie(handler, name=…) generalized; set_/clear_state_cookie_header added; STATE_COOKIE constant.
  • app.py: _auth_login sets the state cookie; _auth_callback reads it, passes it, and clears it on success.
  • tests/test_auth.py: updated the two callback tests for the new signature + added test_callback_rejects_state_cookie_mismatch.

Tests

254 passed locally (python -m pytest tests/ -q), incl. the new mismatch test.

The OAuth state lived only in a process-global pool, so an attacker could
replay their own code+state into a victim's browser and plant an
attacker-owned session. /auth/login now sets a short-lived HttpOnly;
SameSite=Lax plutus_oauth_state cookie and handle_callback requires the
callback state to match it (constant-time) on top of the _pending nonce check.

Fixes the HIGH login-CSRF finding from the 2026-07-05 security review. 254 tests
pass (login_url now returns (url, state); added a state-cookie-mismatch test).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tcconnally tcconnally merged commit c9d7ece into main Jul 5, 2026
8 checks passed
@tcconnally tcconnally deleted the fix/oauth-login-csrf branch July 5, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant