Skip to content

security(auth): reject unsupported JWT critical headers - #895

Open
seonghobae wants to merge 16 commits into
mainfrom
sentinel/crit-header-validation-6751254346309430816
Open

security(auth): reject unsupported JWT critical headers#895
seonghobae wants to merge 16 commits into
mainfrom
sentinel/crit-header-validation-6751254346309430816

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Security outcome

OIDC bearer tokens now fail closed when their protected JOSE header declares a critical extension that this deployment profile does not understand and process. Rejection occurs before JWKS retrieval, so an unsupported extension cannot proceed by selecting another signing key or algorithm.

Exact current scope

  • head: 73d64e095b1144a1bef82cd18d11864921fac7c2
  • protected base captured by GitHub: 466e616e96fc3069a5022351db35fa378b342d64
  • four effective files: authentication validation, focused regression tests, APA 7 doctoring, and CHANGELOG.md
  • generated Jules notes and unrelated import/format churn were removed
  • no dependency, lockfile, database, frontend, workflow, provider, or deployment change

Implemented contract

A present crit member is invalid when it is:

  • JSON null or any non-array value;
  • an empty array;
  • composed of non-string or empty names;
  • duplicated;
  • naming a member absent from the JOSE header;
  • naming a registered JWS parameter such as alg, typ, cty, or crit.

A structurally valid declaration still receives a fixed non-reflecting 401 unsupported critical parameter, because this application currently supports no critical JOSE extensions. Tokens that omit crit retain the existing token-type, content-type, algorithm allowlist, key-family, issuer, audience, expiry, and revocation boundaries.

Verification design

Focused tests cover the supported no-crit path, every malformed shape above, registered-name rejection, absent extension members, an RFC Appendix E-style unknown extension, and the production decode path proving no JWKS I/O occurs before rejection. Exact-head repository CI, coverage, SAST, Strix, OpenCode, and independent review remain authoritative.

Standards

The implementation follows RFC 7515 Section 4.1.11 and Appendix E. The doctoring record also traces the deployment boundary to RFC 8725 (BCP 225). Adding support for any critical extension requires a separate ADR, extension-specific processing, negative tests, and security review; merely allowlisting a name is prohibited.

Merge boundary

Do not merge until this unchanged head has every required check terminal-success, zero valid unresolved findings, and any policy-required qualifying independent non-author approval. Queued, stale, absent, skipped-required, predecessor, or status-only evidence is not passing.

Summary by CodeRabbit

  • 버그 수정

    • 잘못된 형식, 중복 또는 누락된 JOSE crit 헤더를 인증 단계에서 거부합니다.
    • 등록 헤더를 critical 확장으로 지정한 토큰을 차단합니다.
    • 지원되지 않는 critical 확장은 JWKS 조회 및 서명 검증 전에 고정된 401 오류로 거부합니다.
    • crit 헤더가 없는 기존 JWT 검증 동작은 유지됩니다.
  • 문서

    • JWT critical 헤더 검증 기준과 운영 지침을 추가했습니다.

Open in Devin Review

@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ddc6c357-42d5-4b7a-b7a9-428127c1a068

📥 Commits

Reviewing files that changed from the base of the PR and between 85f371f and 4a5adde.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • backend/app/auth.py
  • backend/tests/test_auth_security.py
📝 Walkthrough

Walkthrough

JWT 검증을 PyJWT와 PyJWK 기반으로 변경했습니다. JOSE crit 헤더의 잘못된 선언과 미지원 critical extension을 JWKS 조회 전에 고정 401로 거부합니다. 회귀 테스트와 운영 문서를 추가했습니다.

Changes

JWT 검증 및 crit 헤더 처리

Layer / File(s) Summary
PyJWT 검증 계약 변경
backend/app/auth.py, backend/pyproject.toml, backend/tests/test_auth_security.py
JWK를 PyJWK 객체로 변환합니다. 필수 클레임과 검증 옵션을 jwt.decode 호출에 맞게 변경합니다. 관련 테스트가 변환된 키 객체와 새 옵션을 검증합니다.
crit 헤더 검증 구현
backend/app/auth.py
crit의 형식, 중복, 헤더 존재 여부, 등록 이름 사용 여부를 검증합니다. 미지원 critical extension은 401 unsupported critical parameter로 거부합니다.
crit 거부 회귀 테스트
backend/tests/test_jwt_critical_header.py, backend/tests/test_auth_security.py
no-crit 경로와 잘못된 선언, 등록 이름, 누락된 확장, 미지원 확장을 검증합니다. 미지원 확장이 JWKS 조회 전에 거부되는지 확인합니다.
검증 범위 및 운영 기록
CHANGELOG.md, docs/doctoring/jwt-critical-header-boundary.md, .jules/sentinel.md, frontend/package.json
crit 거부 조건, RFC 근거, 검증 범위, 운영 지침 및 관련 기록을 갱신합니다. 프런트엔드 테스트 의존성 버전을 변경합니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: 🟠 High · up to 85f37

The authentication path may accept a token under an algorithm that conflicts with the signing key’s declared constraint, weakening signature-validation policy. This is a concrete security issue, so the PR is not merge-ready until the mismatch is rejected before decoding and covered by a regression test.

Sequence Diagram(s)

sequenceDiagram
  participant OIDCToken as OIDC JWT
  participant Decode as _decode_verified_oidc_token
  participant Header as _validate_jwt_header
  participant JWKS as _get_jwks
  participant PyJWT as jwt.decode
  OIDCToken->>Decode: JOSE 헤더 추출
  Decode->>Header: crit 및 알고리즘 검증
  Header-->>Decode: 정규화된 알고리즘 또는 401 오류
  Decode->>JWKS: 유효한 헤더의 JWKS 조회
  Decode->>PyJWT: PyJWK 키와 클레임 검증 옵션 전달
  PyJWT-->>Decode: 검증된 JWT 클레임 반환
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 지원되지 않거나 잘못된 JWT critical header를 거부하는 이번 변경의 핵심을 정확하고 간결하게 설명합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentinel/crit-header-validation-6751254346309430816

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae seonghobae changed the title 🛡️ Sentinel: [HIGH] JWT crit header validation security(auth): reject unsupported JWT critical headers Aug 16, 2026

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 137d943fac612a1b20023fea4e34a4d8d0898328.

  • Head SHA: 137d943fac612a1b20023fea4e34a4d8d0898328

  • Workflow run: 32075757301

  • Workflow attempt: 1

Coverage evidence

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 1

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file: CHANGELOG.md"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file: CHANGELOG.md"]
  R1 --> V1["required checks"]
  Evidence --> S2["Backend (3 files)"]
  S2 --> I2["API and service runtime"]
  I2 --> R2["Review risk: Backend (3 files)"]
  R2 --> V2["backend tests"]
  Evidence --> S3["Docs: jwt-critical-header-boundary.md"]
  S3 --> I3["operator or user guidance"]
  I3 --> R3["Review risk: Docs: jwt-critical-header-boundary.md"]
  R3 --> V3["docs review"]
Loading

@opencode-agent

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 137d943fac612a1b20023fea4e34a4d8d0898328
  • Workflow run: 32075757301
  • Workflow attempt: 1
  • Gate result: REQUEST_CHANGES (approval step)

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 137d943fac612a1b20023fea4e34a4d8d0898328.

  • Head SHA: 137d943fac612a1b20023fea4e34a4d8d0898328

  • Workflow run: 32075757301

  • Workflow attempt: 1

Coverage evidence

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 1

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file: CHANGELOG.md"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file: CHANGELOG.md"]
  R1 --> V1["required checks"]
  Evidence --> S2["Backend (3 files)"]
  S2 --> I2["API and service runtime"]
  I2 --> R2["Review risk: Backend (3 files)"]
  R2 --> V2["backend tests"]
  Evidence --> S3["Docs: jwt-critical-header-boundary.md"]
  S3 --> I3["operator or user guidance"]
  I3 --> R3["Review risk: Docs: jwt-critical-header-boundary.md"]
  R3 --> V3["docs review"]
Loading

@seonghobae
seonghobae force-pushed the sentinel/crit-header-validation-6751254346309430816 branch from 2157a64 to af5cd48 Compare August 20, 2026 02:53
@seonghobae

Copy link
Copy Markdown
Collaborator Author

@OpenCode review

@seonghobae
seonghobae enabled auto-merge August 20, 2026 02:53
coderabbitai[bot]

This comment was marked as resolved.

@google-labs-jules

Copy link
Copy Markdown

@OpenCode review

의존성 검사 과정에서 python-jose 패키지의 종속성인 ecdsa 라이브러리(0.19.2 버전)에 취약점(CVE-2024-23342, Minerva timing attack on P-256)이 발견되어 CI에서 실패가 발생했습니다.

이를 해결하기 위해 python-josePyJWT로 마이그레이션하여 문제가 되는 종속성을 제거했으며, 이로 인해 변경 사항이 추가되었습니다. 이 점 코드 리뷰 시 참고해 주시기 바랍니다.

coderabbitai[bot]

This comment was marked as resolved.

@opencode-agent
opencode-agent Bot disabled auto-merge August 20, 2026 04:32
@seonghobae
seonghobae enabled auto-merge August 20, 2026 04:34
seonghobae and others added 12 commits August 20, 2026 12:30
JWT JOSE 헤더의 crit 파라미터가 포함된 경우를 안전하게 처리하기 위해 검증 로직을 추가했습니다. STRIX 보안 요건 및 RFC 7515에 따라 지원되지 않는 critical 확장을 명시적으로 거부하도록 합니다.
JWT JOSE 헤더의 crit 검증 로직을 상단으로 이동하여 RFC 7515 요구 사항에 맞게 처리되도록 수정했습니다.
- JWT JOSE 헤더의 `crit` (critical) 파라미터가 포함된 경우를 안전하게 처리하기 위해 검증 로직을 추가했습니다. STRIX 보안 요건 및 RFC 7515에 따라 지원되지 않는 critical 확장을 명시적으로 거부하도록 합니다.
- RFC 7515 스펙에 따라 지원하지 않는 critical 파라미터가 최우선으로 거부되도록 `_validate_jwt_header`의 상단에서 `crit` 검증을 수행하도록 했습니다.
- `crit` 헤더 검증에 관한 상세 테스트를 추가하고, mock 객체를 실제 동작에 맞게 수정했습니다.
- 보안 취약점이 발견된 `python-jose` 패키지 종속성(ecdsa) 문제를 해결하기 위해 `PyJWT` 라이브러리로 마이그레이션했습니다.
@seonghobae
seonghobae force-pushed the sentinel/crit-header-validation-6751254346309430816 branch from 8436cb4 to f0e474e Compare August 20, 2026 19:30
devin-ai-integration[bot]

This comment was marked as resolved.

@opencode-agent
opencode-agent Bot disabled auto-merge August 20, 2026 21:05

Copy link
Copy Markdown
Collaborator Author

Current HEAD validation

Updated current HEAD to b5a7b7c44641a09465949b1a75c65aacdace2cdf.

  • Removed stale types-python-jose and transitive types-pyasn1 from both dev dependency declarations and the lockfile.
  • Restored the base-compatible @testing-library/user-event 14.6.4 lock entry and pinned vulnerable transitive nanoid to 3.3.18 via the existing override.
  • Removed the unused rel_by_oid assignment reported by Ruff.

Local evidence: backend 405 passed, focused JWT/auth 48 passed, Ruff, mypy; frontend 28 files / 201 tests passed, typecheck, build, npm audit --audit-level=high (0 vulnerabilities), and git diff --check.

Please evaluate reviews and required Checks against this exact HEAD; the push starts fresh PR workflows.

Removed the unsupported `"verify_jti": True` option from the `jwt.decode`
configuration in `backend/app/auth.py` and `test_auth_security.py`. PyJWT
does not support this option and strict validation of the options dictionary
causes authentication to fail entirely with a generic 401 error. The JTI
requirement is already correctly enforced via the `"require": ["jti", ...]` list.

Reverted out-of-scope frontend `Dockerfile` and `package.json` dependency
bumps.

Copy link
Copy Markdown
Collaborator Author

현재 HEAD 9c0c14544b5fd37cf344c769160332ea7370acdf에서 JOSE crit fail-closed 검증, RFC 7515/8725 doctoring과 리뷰 수정 상태를 확인했습니다. unresolved thread는 0개이며 새 exact-head ci, SAST Semgrep, Security Scan은 queued입니다. queued checks와 qualifying independent approval 전에는 병합하지 않습니다.

@google-labs-jules

Copy link
Copy Markdown

현재 HEAD 9c0c14544b5fd37cf344c769160332ea7370acdf에서 JOSE crit fail-closed 검증, RFC 7515/8725 doctoring과 리뷰 수정 상태를 확인했습니다. unresolved thread는 0개이며 새 exact-head ci, SAST Semgrep, Security Scan은 queued입니다. queued checks와 qualifying independent approval 전에는 병합하지 않습니다.

확인했습니다. 알림 감사합니다. 추가 작업이 필요하면 알려주세요.

Removed the unsupported `"verify_jti": True` option from the `jwt.decode`
configuration in `backend/app/auth.py` and `test_auth_security.py`. PyJWT
does not support this option and strict validation of the options dictionary
causes authentication to fail entirely with a generic 401 error. The JTI
requirement is already correctly enforced via the `"require": ["jti", ...]` list.

Reverted out-of-scope frontend `Dockerfile` and `package.json` dependency
bumps.

Copy link
Copy Markdown
Collaborator Author

Current-head revalidation: 4a5adde59fb7627188db127b5fcb690a4861a2fd. Review threads are clear and CodeRabbit/Devin are successful; exact-head CI, Security Scan, and SAST Semgrep are queued. No qualifying independent non-author approval is present, so normal protected merge is not yet permitted.

@google-labs-jules

Copy link
Copy Markdown

Current-head revalidation: 4a5adde59fb7627188db127b5fcb690a4861a2fd. Review threads are clear and CodeRabbit/Devin are successful; exact-head CI, Security Scan, and SAST Semgrep are queued. No qualifying independent non-author approval is present, so normal protected merge is not yet permitted.

확인했습니다. CI 파이프라인(exact-head, Security Scan, SAST Semgrep)이 큐에 있고 승인이 필요한 상태임을 인지했습니다.

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