Skip to content

fix(security): enforce strict revocable sessions on every JWT transport - #414

Closed
seonghobae wants to merge 45 commits into
developfrom
fix/session-revocation-url-token-endpoints
Closed

fix(security): enforce strict revocable sessions on every JWT transport#414
seonghobae wants to merge 45 commits into
developfrom
fix/session-revocation-url-token-endpoints

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Enforce one fail-closed, database-backed session boundary for every ScopeWeave endpoint that accepts a session JWT:

  • bearer middleware;
  • /api/projects/:id/calendar.ics?token=...;
  • /api/projects/:id/stream?token=...;
  • /api/projects/:id/attachments/:attachment_id/view?token=....

signToken refuses to mint a session unless the subject, token version, and lifetime are bounded safe integers. verifyToken authenticates the compact signature before parsing claims, requires a signed HS256 / JWT header, requires an object payload with a positive safe-integer subject and future safe-integer expiry, requires a non-negative safe-integer tv, requires the user to exist, and compares tv exactly with users.token_version until the canonical database-object migration in #433.

Missing, null, Boolean, string, fractional, negative, unsafe, expired, forged, wrong-header, malformed-payload, nonexistent-user, and stale tokens fail before tenant or resource lookup.

Security impact

Before this change, URL-token session JWTs could remain usable until expiration after logout-all incremented token_version. Route-local coercion could also treat malformed token-version claims as version zero. Missing expiry and weakly typed subject claims were not rejected centrally. Consolidating the contract removes transport-specific omissions and prevents internal callers from minting malformed sessions.

This is the revocation prerequisite for #413, which will replace full session JWTs in URLs with narrowly scoped ephemeral grants and independently revocable calendar subscription secrets.

Regression coverage

tests/api/session-revocation.test.mjs proves signer claim validation, two-device authentication across bearer/calendar/SSE/attachment-view transports, malformed/forged/expired/stale token rejection, nonexistent-user rejection before resource lookup, logout-all revocation, replacement-token continuity, and authentication-before-resource-lookup ordering.

All shipped authentication primitives and helpers have beginner-readable JSDoc. The regression is wired into test:api and the c8 coverage gate.

Standards traceability

docs/doctoring/session-revocation.md records the shared invariant, regression contract, modular authentication boundary, and APA 7th references to RFC 7519, RFC 6750, RFC 8725, and RFC 9700. The implementation follows pinned-algorithm, strict-claim, and fail-closed session semantics without claiming that full session JWT query transport is an acceptable long-term design.

Stacked verification and merge order

This PR is intentionally Draft and currently targets #432's exact branch. Its head is a focused session-security layer on top of #432, so review and test evidence remain isolated.

After #432 merges:

  1. retarget this PR to develop;
  2. confirm the exact protected base is an ancestor of the current head;
  3. rerun unit/API/coverage, docstring, cloud E2E, and all repository/central security checks;
  4. obtain an independent current-head approval;
  5. restore auto-merge only after every protection is satisfied.

Protections must not be bypassed.

@coderabbitai

coderabbitai Bot commented Aug 3, 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: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c141d31-c516-4254-a770-64c75eae3fa5

📥 Commits

Reviewing files that changed from the base of the PR and between 5ed7fa1 and c2c3ad5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • CHANGELOG.md
  • docs/doctoring/session-revocation.md
  • package.json
  • server/app.mjs
  • server/attachment_status.mjs
  • server/auth.mjs
  • server/clearfolio.mjs
  • tests/api/attachment-status.test.mjs
  • tests/api/session-revocation.test.mjs
  • tests/unit/attachment-status.test.mjs
  • tests/unit/clearfolio-status-signal.test.mjs
  • tests/unit/coverage-script-contract.test.mjs
📝 Walkthrough

Walkthrough

세션 JWT 검증을 공통 함수로 통합하고 URL 토큰 경로에 token_version 폐기 검사를 적용했습니다. 관련 회귀 테스트와 실행 환경을 갱신했습니다. MSP XML 파서는 정규식 대신 선형 문자열 탐색을 사용합니다.

Changes

세션 JWT 폐기 검증

Layer / File(s) Summary
공통 세션 검증
server/app.mjs
verifySessionJwt가 JWT, 사용자 존재 여부와 token_version을 검증합니다. 인증 미들웨어, 캘린더, SSE, 첨부파일 경로가 이 함수를 사용합니다.
폐기 회귀 검증 및 실행 설정
tests/api/session-revocation.test.mjs, package.json, .github/workflows/*
두 세션의 logout-all 전후 상태와 교체 토큰을 검증합니다. Node.js, npm, 테스트 명령과 관련 패키지 버전을 갱신했습니다.
보안 문서와 변경 기록
.jules/*, CHANGELOG.md
세션 폐기 불변식과 회귀 검증 절차를 문서화하고 보안 변경사항을 기록했습니다.

MS Project XML 파서

Layer / File(s) Summary
선형 XML 스캔
cloud-sync.js
태그, Task, PredecessorLink 블록을 indexOfslice로 순차 수집합니다. 닫히지 않은 블록은 추가 탐색 없이 중단합니다.
파서 경계 조건 검증
tests/unit/msproject.test.mjs
리터럴 정규식 문자, null 입력, 닫히지 않은 태그와 대량의 불완전한 작업 블록을 검증합니다.

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

Possibly related issues

  • 이슈 413: 동일한 캘린더, 스트림, 첨부파일 URL 토큰 엔드포인트에 세션 JWT 폐기 검사를 적용합니다.

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant URLTokenEndpoint
  participant verifySessionJwt
  participant Database
  Client->>URLTokenEndpoint: JWT 요청
  URLTokenEndpoint->>verifySessionJwt: 세션 JWT 검증
  verifySessionJwt->>Database: token_version 조회
  Database-->>verifySessionJwt: 현재 세션 버전
  verifySessionJwt-->>URLTokenEndpoint: 인증 성공 또는 실패
  URLTokenEndpoint-->>Client: HTTP 200 또는 401
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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 전송 경로에 세션 폐기 검증을 적용하는 주요 보안 변경을 정확하고 간결하게 설명합니다.
✨ 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 fix/session-revocation-url-token-endpoints

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

@seonghobae
seonghobae enabled auto-merge (squash) August 3, 2026 14:03

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/app.mjs`:
- Around line 32-35: Update verifySessionJwt to require the payload.tv claim to
be present and an integer before querying or comparing the user token version;
reject missing, null, false, and other non-integer values, then compare the
validated claim directly with user.token_version.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e60b929a-645c-43d7-a11e-153b03953950

📥 Commits

Reviewing files that changed from the base of the PR and between a756b7e and 5ed7fa1.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • .github/workflows/fuzz.yml
  • .github/workflows/server-tests.yml
  • .jules/sentinel.md
  • .jules/verification-session-revocation.md
  • CHANGELOG.md
  • cloud-sync.js
  • package.json
  • server/app.mjs
  • tests/api/session-revocation.test.mjs
  • tests/unit/msproject.test.mjs

Comment thread server/app.mjs Outdated
@opencode-agent
opencode-agent Bot disabled auto-merge August 3, 2026 14:22
@seonghobae
seonghobae enabled auto-merge (squash) August 3, 2026 15:09
@seonghobae
seonghobae changed the base branch from develop to fix/security-hono-csv-hardening August 3, 2026 15:12
@seonghobae
seonghobae changed the base branch from fix/security-hono-csv-hardening to develop August 3, 2026 15:14
@seonghobae
seonghobae marked this pull request as draft August 3, 2026 15:14
auto-merge was automatically disabled August 3, 2026 15:14

Pull request was converted to draft

@seonghobae
seonghobae changed the base branch from develop to fix/security-hono-csv-hardening August 3, 2026 16:04
@seonghobae
seonghobae force-pushed the fix/session-revocation-url-token-endpoints branch 2 times, most recently from 83fb51f to 6c3f91b Compare August 3, 2026 16:14
@seonghobae
seonghobae marked this pull request as ready for review August 3, 2026 16:16
@seonghobae
seonghobae marked this pull request as draft August 3, 2026 16:16
@seonghobae
seonghobae force-pushed the fix/session-revocation-url-token-endpoints branch from 6c3f91b to 382d683 Compare August 3, 2026 16:20

Copy link
Copy Markdown
Contributor Author

Current-head maintainer verification for 382d6834f1b22c8fa21480356a3ef78783a99087: Server Tests, Dependency Review, and OSV Scanner completed successfully. The branch is now a focused five-file, single-commit stack directly on #386, with database-backed revocation centralized in verifyToken and regression coverage for malformed token versions, nonexistent users, cross-device logout-all, and replacement tokens across bearer, calendar, SSE, and attachment-view transports. Fresh CodeRabbit auto-review is deferred because the current target is the non-default parent branch; this PR remains Draft until #386 merges, after which it must be retargeted to develop and fully revalidated.

Base automatically changed from fix/security-hono-csv-hardening to develop August 3, 2026 23:22
server/app.mjs에서 첨부파일(attachments)의 PENDING 상태를 동기화할 때,
기존 for...of 루프 내부에서 jobStatus를 순차적으로 await 하던 로직을
Promise.all(rows.map(...))을 사용하도록 변경했습니다.
이를 통해 첨부파일이 여러 개일 경우 발생하는 네트워크 호출 병목을
효과적으로 줄이고 응답 지연을 방지합니다.
server/app.mjs에서 첨부파일(attachments)의 PENDING 상태를 동기화할 때,
기존 for...of 루프 내부에서 jobStatus를 순차적으로 await 하던 로직을
Promise.all(rows.map(...))을 사용하도록 변경했습니다.
이를 통해 첨부파일이 여러 개일 경우 발생하는 네트워크 호출 병목을
효과적으로 줄이고 응답 지연을 방지합니다.

추가로 CI Trivy 스캔에서 발견된 hono 패키지의 취약점(CVE-2026-69207)을
해결하기 위해 버전을 4.12.32에서 4.13.0으로 업데이트했습니다.
@seonghobae
seonghobae marked this pull request as ready for review August 4, 2026 04:13
Address CodeRabbit feedback: unbounded Promise.all over all pending
attachments could exceed Clearfolio connection/rate limits. Filter to
PENDING/RUNNING rows and process in chunks of 5, preserving best-effort
stale-status handling. Also revise the .jules/bolt.md guidance to require
bounded concurrency for external calls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@seonghobae
seonghobae enabled auto-merge (squash) August 4, 2026 05:44
@seonghobae
seonghobae marked this pull request as draft August 4, 2026 05:44
auto-merge was automatically disabled August 4, 2026 05:44

Pull request was converted to draft

@seonghobae
seonghobae marked this pull request as ready for review August 4, 2026 05:45
@seonghobae
seonghobae enabled auto-merge (squash) August 4, 2026 05:45
@seonghobae
seonghobae changed the base branch from develop to fix/security-hono-attachment-refresh-final August 4, 2026 11:06
@seonghobae
seonghobae force-pushed the fix/session-revocation-url-token-endpoints branch from f11761b to 1756da0 Compare August 4, 2026 11:14
@seonghobae
seonghobae force-pushed the fix/session-revocation-url-token-endpoints branch from 1756da0 to 02c8d84 Compare August 4, 2026 11:17
@seonghobae
seonghobae changed the base branch from fix/security-hono-attachment-refresh-final to develop August 4, 2026 11:41

Copy link
Copy Markdown
Contributor Author

Superseded by clean replacement #436. The replacement is based directly on #432 exact head 545de5ffd71ef585bdd2a2ef97c25de78c7098fc and changes only the five session-security files. This avoids reintroducing the historical attachment/application diff present on this diverged branch. No valid session-revocation implementation or test coverage is being discarded.

@seonghobae seonghobae closed this Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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