Skip to content

fix(auth): require an access-token type on the OIDC bearer path (supersedes #1077)#1078

Open
seonghobae wants to merge 6 commits into
developfrom
fix/oidc-require-access-token-type
Open

fix(auth): require an access-token type on the OIDC bearer path (supersedes #1077)#1078
seonghobae wants to merge 6 commits into
developfrom
fix/oidc-require-access-token-type

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

What

Fix the Strix HIGH (CVSS 8.8) "OIDC ID tokens accepted as API bearer sessions without token-type validation" in backend/api/auth.py, and add regression tests.

The OIDC verifier accepted any RS256 token whose signature, issuer, and aud matched OIDC_CLIENT_ID, with no token-type check. An OIDC ID token carries aud == client_id, so a frontend ID token could be replayed directly as an API access token (RFC 8725 §3.11).

Why this direction

naruon's API credential is the OIDC access token, not the ID token:

  • frontend/src/app/auth/oidc/callback/route.ts:112 and frontend/src/app/auth/session/route.ts:288 send token_response.access_token as the bearer. There is no id_token credential path anywhere in the frontend.
  • The IdP is Keycloak 24 (docker-compose.infra.yml, docker-compose.gateway.yml). Keycloak access tokens carry body typ: "Bearer"; ID tokens carry typ: "ID".

So the fix requires the token to be marked as an access token — Keycloak body typ: "Bearer", or RFC 9068 header typ: "at+jwt" — and rejects ID-token material and unmarked tokens.

Change

  • _require_oidc_access_token(header, payload) on the OIDC decode path; rejects anything not marked as an access token.
  • Regression tests: an ID-token-shaped token (typ: "ID"), the unmarked PoC shape, and a header-id variant all assert 401; an RFC 9068 at+jwt access token is accepted. The reject tests fail on the prior code and pass here.
  • OIDC access-token fixtures updated to carry the realistic typ: "Bearer".

Verification

  • pytest tests/test_auth_real.py94 passed.
  • Reverting auth.py to develop makes the new reject tests fail (proves they're load-bearing).
  • ruff check clean.

Supersedes #1077

#1077 took the inverse approach (treat the ID token as the credential, reject scope/scp). Run against a realistic Keycloak access token, #1077 rejects it (breaks every SSO login) and accepts the ID token (leaves the vuln open). #1077 has been converted to draft; please close it in favor of this PR.

🤖 Generated with Claude Code

The OIDC verifier accepted any RS256 token whose signature, issuer, and
audience matched, without checking token type. Since an OIDC ID token carries
aud == client_id, a frontend ID token could be replayed as an API access token
(RFC 8725 §3.11 / Strix HIGH, CVSS 8.8).

naruon's API credential is the OIDC access token (the frontend sends
token_response.access_token). Require the token to be marked as an access
token — Keycloak body typ="Bearer" or RFC 9068 header typ="at+jwt" — and reject
ID-token material (Keycloak typ="ID") and unmarked tokens.

Regression tests replay an ID-token-shaped token and assert 401; they fail on
the prior code and pass here. OIDC access-token fixtures updated to carry the
realistic typ="Bearer".

Supersedes #1077, whose inverse approach rejected scope-bearing Keycloak access
tokens (breaking SSO login) while still admitting ID tokens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

PR governance metadata gate is not ready for 51c7b931d4419954b9d68a258e20d37e5167a278:

  • Merge state is UNKNOWN; resolve conflicts or refresh mergeability.
  • Required check frontend is FAILURE on the current head.
  • Required check opencode-review is FAILURE on the current head.
  • Required check Analyze (javascript-typescript) is FAILURE on the current head.

Comment thread backend/api/auth.py Fixed
@opencode-agent opencode-agent Bot disabled auto-merge July 13, 2026 12:21
@github-advanced-security

Copy link
Copy Markdown
Contributor

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@seonghobae seonghobae enabled auto-merge July 13, 2026 12:26
@opencode-agent

opencode-agent Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 51c7b931d4419954b9d68a258e20d37e5167a278
  • Workflow run: 29253516271
  • Workflow attempt: 1
  • Gate result: APPROVE (exit 0)

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Backend (3 files)"]
  S1 --> I1["API and service runtime"]
  I1 --> R1["Review risk: Backend (3 files)"]
  R1 --> V1["backend tests"]
  Evidence --> S2["Changed file: docker-compose.infra.yml"]
  S2 --> I2["repository behavior"]
  I2 --> R2["Review risk: Changed file: docker-compose.infra.yml"]
  R2 --> V2["required checks"]
  Evidence --> S3["Frontend: screenshot.cjs"]
  S3 --> I3["browser runtime and bundle"]
  I3 --> R3["Review risk: Frontend: screenshot.cjs"]
  R3 --> V3["frontend tests"]
Loading

Comment thread frontend/screenshot.cjs Fixed
@opencode-agent opencode-agent Bot disabled auto-merge July 13, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants