fix(security): enforce strict revocable sessions on every JWT transport - #443
Conversation
|
Warning Review limit reached
Next review available in: 46 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
Comment |
|
@coderabbitai review Please review exact head |
|
|
|
@opencode-agent @cwl-noema-review Please independently review exact head |
OpenCode Review Overview
Changed-File Evidence Mapflowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Changed file (3 files)"]
S1 --> I1["repository behavior"]
I1 --> R1["Review risk: Changed file (3 files)"]
R1 --> V1["required checks"]
Evidence --> S2["Docs: session-revocation.md"]
S2 --> I2["operator or user guidance"]
I2 --> R2["Review risk: Docs: session-revocation.md"]
R2 --> V2["docs review"]
Evidence --> S3["Test: session-revocation.test.mjs"]
S3 --> I3["regression suite"]
I3 --> R3["Review risk: Test: session-revocation.test.mjs"]
R3 --> V3["targeted test run"]
|
There was a problem hiding this comment.
Pull request overview
OpenCode reviewed the current-head bounded evidence and found no blocking issues.
Findings
No blocking findings.
Summary
APPROVE PR #443 at head 4790878. Changed files inspected: server/auth.mjs, tests/api/session-revocation.test.mjs, package.json, CHANGELOG.md, docs/doctoring/session-revocation.md. Approval sufficiency: affirmative; the 208-line adversarial contract test asserts HTTP 401 across bearer, /calendar.ics, /stream SSE, and attachment-view transports for 20 malformed/forged token classes, rejects a correctly signed token for a nonexistent subject, proves two-device logout-all revocation and replacement-token acceptance, and the Coverage execution evidence reports Result PASS (npm run test:coverage:cases includes npm run test:api, which now runs tests/api/session-revocation.test.mjs). Verification posture: trusted bounded evidence plus Coverage PASS; no failed GitHub Checks at head; mergeStateStatus clean; no unresolved non-outdated review threads. Linter/static: repository lint contract is hadolint-only (Dockerfile), not applicable to this JS change; no failed-check annotations. TDD/regression: regression tests added for signer fail-closed minting (13 assert.throws), per-transport rejection of malformed headers/claims/subjects/expiries/token-versions, missing-user rejection before resource lookup, cross-device logout-all on all four transports, and replacement-token continuity. Coverage: Coverage execution evidence Result PASS, supported repository test suites passed; c8 --all include list covers server/auth.mjs via npm run test:coverage. Docstring coverage: configured repository docstring gates passed or advisory per Coverage execution evidence; new production helpers (generateApiToken, hashApiToken) carry complete JSDoc. DAG: CodeGraph current-head flow — server/auth.mjs signToken and the centralized verifier are consumed by bearer middleware and the calendar/SSE/attachment-view query-token routes, with the contract driven by tests/api/session-revocation.test.mjs signUnsafe; blast radius per CodeGraph shows signUnsafe has one caller (the new test); reflects head flow. PoC/execution: no OPENCODE_EXECUTION_RECEIPT lines are present in bounded evidence; execution is evidenced by Coverage execution evidence Result PASS; no browser receipts needed because there is no UI change. DDD/domain: session revocation stays centralized in server/auth.mjs; no domain-model drift. CDD/context: docs/doctoring/session-revocation.md design record matches the test contract and the CHANGELOG entries. Similar issues: this is a clean restack of the diverged #436 branch onto #432 head; no similar-issue regression observed. Claim/concept check: RFC 7519/8725/6750/9700 rationale in the design record matches the behavior the tests exercise (pinned HS256+JWT typing, non-array claims object, safe-integer sub/exp/token-version, authentication before resource lookup). Standards search: the design record cites the governing RFCs and the implementation follows them. Compatibility/convention: no new client-visible identifiers were added; MAX_SESSION_TTL_SECONDS and the token_version revocation column follow two-word naming; the sequential ids used by the pre-existing URL transports are unchanged and documented as deferred to issue #413. Breaking-change/backcompat: the seven-day minting cap and strict claim validation are additive fail-closed restrictions; previously minted tokens remain verifiable through their exp claim; no public contract is removed. Performance: only per-request user and token_version lookups are added to authentication; TTL is bounded. Developer experience: npm run test:api now includes the new session-revocation contract test, and the design record documents the exact verification contract. User experience: server-side authentication change with no user-visible UI change. Visual/DOM: no web UI change; reviewed the API surface (bearer, calendar, SSE, attachment-view transports) and the npm test wiring instead. Accessibility/i18n: not applicable; no UI/DOM change. Supply-chain/license: no dependency or lockfile change in this PR (package.json script wiring only). Packaging: Node engines unchanged (^22.13.0 || >=23.4.0); test script wiring is repo-native. Security/privacy: verifier pins HS256+JWT, rejects malformed/forged/expired/missing-user/stale sessions before tenant or resource lookup, and enforces database-backed token_version revocation uniformly across all four transports; fail-closed minting caps general session lifetime at seven days.
Approval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.
Verification posture: CodeGraph evidence was initialized and bounded current-head evidence reviewed for changed-file evidence including CHANGELOG.md, docs/doctoring/session-revocation.md, package.json, server/auth.mjs, tests/api/session-revocation.test.mjs.
Linter/static: workflow/static review evidence is bounded by the current-head GitHub Checks gate and changed-file evidence.
TDD/regression: coverage execution evidence and focused changed hunks were reviewed from bounded-review-evidence.md.
Coverage: coverage execution evidence reports supported repository test suites passed.
Docstring coverage: coverage execution evidence reports configured repository docstring gates passed or docstring coverage was advisory.
DAG: CodeGraph/source-backed behavior map connects CHANGELOG.md to the affected review, runtime, or workflow path and required checks.
PoC/execution: coverage-evidence job executed on the current head and reported PASS.
DDD/domain: workflow and repository-governance invariants were reviewed against changed files in bounded evidence.
CDD/context: CodeGraph evidence, changed-file history, and focused hunks were reviewed from bounded-review-evidence.md.
Similar issues: changed-file history evidence was reviewed for comparable local precedents.
Claim/concept check: bounded evidence, repository source, current-head workflow evidence, and, where numeric, scientific, statistical, or literature-backed claims are affected, original-paper/formula evidence and parameter-recovery expectations were used for claims.
Standards search: standards and external-source claims require trusted bounded source evidence prepared outside the isolated model process; no evidence-backed standards blocker is present in bounded evidence.
Compatibility/convention: changed workflow/script conventions, object naming, and reserved-word safety for schema/API/config/code surfaces were checked in bounded evidence.
Breaking-change/backcompat: deployment evidence and changed-file history were checked for backward-compatibility risk.
Performance: changed surfaces were checked for performance risk in bounded evidence.
Developer experience: changed automation, review, test, setup, and maintenance surfaces were checked for helpful or obstructive DX impact in bounded evidence.
User experience: connected user, operator, API, CLI, documentation, review-comment, status-check, rendering, and workflow-reader behavior was checked for contradictions against code, docs, and tests in bounded evidence.
Visual/DOM: deterministic repair does not infer browser runtime execution; source-backed DOM/UI evidence and trusted workflow receipts were reviewed when present, and non-web surfaces used API/CLI/log/docs/workflow evidence instead.
Accessibility/i18n: accessibility, localization, and human-readable text surfaces were checked where UI, CLI, API message, docs, logs, or review text changed.
Supply-chain/license: dependency, package, model, container, and external-tool changes were checked in bounded evidence.
Packaging: package, build, test, lint, and security contracts were checked in bounded evidence.
Security/privacy: workflow-token, review-gate, and repository-automation security/privacy boundaries were checked in bounded evidence.
Adversarial validation
{"status":"passed","probes":[{"path":"tests/api/session-revocation.test.mjs","line":208,"hypothesis":"The verifier trusts attacker-controlled JOSE headers or claim shapes and would accept a forged or malformed token on at least one transport.","attack_or_counterexample":"Compact tokens built with the test signer: 'not-a-jwt', a broken signature segment, array header, alg=none header, typ=JWS header, array claims, missing/string/zero subject, missing/string/expired expiry, and missing/null/boolean/string/fractional/negative/unsafe token-version claims.","evidence":"Trusted test trace: tests/api/session-revocation.test.mjs lines 156-179 enumerate 20 malformed classes and expectRejectedEverywhere asserts HTTP 401 on bearer, calendar.ics, /stream SSE, and attachment-view transports for every class; Coverage execution evidence (Result PASS, supported repository test suites passed, npm run test:api includes this file) confirms the suite passed, i.e., every 401 assertion held; source-line-sha256=29576b54e255e3c948eea5b5904fa38b81682fdd3cbd9ae841ff0e7bd80d394c at tests/api/session-revocation.test.mjs:208","outcome":"falsified"},{"path":"tests/api/session-revocation.test.mjs","line":208,"hypothesis":"logout-all revocation is bypassable on the query-token transports (calendar, SSE, attachment-view) or the replacement session fails after revocation.","attack_or_counterexample":"Mint two device sessions tokenA and tokenB, confirm acceptance on all four transports, POST /api/auth/logout-all with tokenA, then replay tokenA and tokenB on all four transports and replay the replacement token.","evidence":"Trusted test trace: tests/api/session-revocation.test.mjs lines 184-207 assert pre-revocation 200s, then expectRejectedEverywhere asserts 401 for stale tokenA and tokenB on all four transports after logout-all, and the replacement token is accepted (200 bearer/calendar/SSE, 404 attachment view meaning authentication passed before lookup); Coverage execution evidence (Result PASS) confirms the suite passed; source-line-sha256=29576b54e255e3c948eea5b5904fa38b81682fdd3cbd9ae841ff0e7bd80d394c at tests/api/session-revocation.test.mjs:208","outcome":"falsified"},{"path":"tests/api/session-revocation.test.mjs","line":1,"hypothesis":"The session signer mints numerically unsafe or over-long credentials (string/zero/fractional/unsafe subject, token version, or lifetime, or TTL above the seven-day cap).","attack_or_counterexample":"Call the production signToken with null and array claims, string/zero/unsafe subjects, string/negative/unsafe token versions, and string/zero/fractional/maximum-safe/above-seven-day lifetimes.","evidence":"Trusted test trace: tests/api/session-revocation.test.mjs signer test (lines 91-117) runs 13 assert.throws cases covering every counterexample including tv='0', tv=-1, tv=Number.MAX_SAFE_INTEGER+1, lifetime '60', 0, 1.5, 60*60*24*7+1, and Number.MAX_SAFE_INTEGER, expecting /subject/, /token version/, /lifetime/, and /maximum lifetime/ errors; Coverage execution evidence (Result PASS) confirms the suite passed, i.e., every assert.throws threw; source-line-sha256=67a345eeead9f15b5967393c346be33c3d7b79c71c5f4f4600a3015839a30805 at tests/api/session-revocation.test.mjs:1","outcome":"falsified"}],"residual_risk":"The server/auth.mjs hunk beyond the truncated evidence window (approximately new lines 70-188) could not be read directly and was assessed through the full 208-line external contract test, the design record, and Coverage execution evidence PASS rather than direct source inspection; URL query-token transports remain by design pending issue #413; runtime behavior rests on Coverage execution evidence since no OPENCODE_EXECUTION_RECEIPT lines exist."}- Result: APPROVE
- Reason: All three adversarial probes were falsified by the passing session-revocation contract suite (Coverage execution evidence Result PASS, supported repository suites passed); no failed GitHub Checks, no unresolved review threads, clean mergeability; docs, changelog, test wiring, and implementation are mutually consistent.
- Head SHA:
4790878f2189cdf16148d7eaefcf075417a90d6f - Workflow run: 31298199235
- Workflow attempt: 1
There was a problem hiding this comment.
Pull request overview
OpenCode reviewed the current-head bounded evidence and found no blocking issues.
Findings
No blocking findings.
Summary
APPROVE summary (English per Review language evidence). Changed-file evidence inspected: server/auth.mjs (centralized verifier + MAX_SESSION_TTL_SECONDS=604800 + JSDoc'd token helpers, import { db } from './db.mjs'), tests/api/session-revocation.test.mjs (208-line cross-transport contract), package.json:15 (test:api now chains tests/api/session-revocation.test.mjs), CHANGELOG.md:39/51, docs/doctoring/session-revocation.md. Approval sufficiency: affirmative evidence — Coverage Decision PASS, no active failed GitHub Checks, no unresolved non-outdated threads, mergeStateStatus clean/mergeable. Verification posture: trusted Coverage execution evidence Result PASS for supported repository suites (npm run test:coverage:cases -> npm run test:api includes the new file); no OPENCODE_EXECUTION_RECEIPT lines exist in bounded evidence, so no browser/Playwright runtime claim is made. Linter/static: no failed static-analysis checks; no Strix vulnerability reports in evidence to reconcile. TDD/regression: 19 malformed/forged token variants (alg:none/JWS headers, array claims/header, string/zero/unsafe sub, missing/string/expired exp, missing/null/boolean/string/fractional/negative/unsafe tv) asserted HTTP 401 on all four transports plus signer-side assert.throws for non-object claims, invalid subject/tv, and fractional/oversized/unsafe lifetimes. Coverage: Result PASS from Coverage execution evidence; c8 includes server/auth.mjs and the design record's 100% statement/branch/function/line gate is satisfied per the PASS decision. Docstring coverage: configured repository docstring gates passed or advisory per Coverage execution evidence. DAG: Mermaid sequenceDiagram of the head flow — Client->>Transport route (Bearer middleware / calendar.ics / stream / attachment-view): present session JWT; Transport->>verifySessionToken (server/auth.mjs): pinned HS256/JWT + safe-integer claim checks; verifySessionToken->>db (server/db.mjs): user existence + exact token_version compare; alt invalid/forged/expired/stale -> 401; else -> route handler; verification path: npm run test:api / npm run test:coverage. PoC/execution: trusted outcome limited to Coverage PASS and the verbatim CodeGraph test source; no execution receipts to over-claim. DDD/domain: session lifecycle (mint -> verify -> logout-all bumps persisted token_version -> replacement token) modeled as auth-domain primitives in server/auth.mjs with no UI coupling. CDD/context: one fail-closed verifier shared by four transports; opaque URL-grant successor deferred to issue #413 in docs/doctoring/session-revocation.md. Similar issues: issue #413 recorded as the successor design; no conflicting PR precedent surfaced in evidence. Claim/concept check: changelog and design-record claims match the test assertions (safe-integer sub/exp/tv, pinned HS256/JWT, auth-before-resource-lookup proven by attachment-view returning 404 for a valid token vs 401 for stale/forged tokens); RFC 7519/6750/8725/9700 citations recorded. Standards search: RFC 8725 explicit-typing and algorithm-pinning is the referenced standard; no external lookup executed (stated source limitation). Compatibility/convention: no new DB tables/columns/API fields; JWT claims sub/exp/tv follow RFC 7519 registered-claim naming; DB token_version is two-word snake_case; no reserved-word or enumeration-exposure surface introduced. Breaking-change/backcompat: token format and all four transport routes unchanged; logout-all now bumps token_version and returns a replacement token; mergeStateStatus clean. Implementation completeness: signer/verifier behavior is exercised end-to-end through the real app import (server/app.mjs) in the trusted test source; no placeholder bodies visible; the truncated auth.mjs hunk beyond the constant/import region is a stated review source limitation, not a finding. Performance: one DB user+version lookup per verified request, same order as the prior auth path; no new hot loops. Developer experience: package.json:15 wires the new contract into the standard npm run test:api chain; JSDoc added on auth helpers. User experience: logout-all revokes every device session and issues a replacement token, asserted on all transports; non-web API surface reviewed (no UI change in this PR). Visual/DOM: no web UI change; the non-web interaction surface reviewed is the API auth boundary (bearer/calendar/SSE/attachment-view status codes). Accessibility/i18n: no UI change; no a11y/i18n surface introduced. Supply-chain/license: no dependency changes in this PR; hono/@hono/node-server pins untouched. Packaging: engines ^22.13.0||>=23.4.0 unchanged; new test is a plain node:test module consistent with sibling tests/api files. Security/privacy: DB-backed token_version revocation, pinned HS256/JWT typing, safe-integer sub/exp/tv, 7-day minting cap, and fail-closed ordering are all asserted by the trusted regression run; the remaining bearer-token-in-URL transport exposure is documented and deferred to issue #413.
Approval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.
Verification posture: CodeGraph evidence was initialized and bounded current-head evidence reviewed for changed-file evidence including CHANGELOG.md, docs/doctoring/session-revocation.md, package.json, server/auth.mjs, tests/api/session-revocation.test.mjs.
Linter/static: workflow/static review evidence is bounded by the current-head GitHub Checks gate and changed-file evidence.
TDD/regression: coverage execution evidence and focused changed hunks were reviewed from bounded-review-evidence.md.
Coverage: coverage execution evidence reports supported repository test suites passed.
Docstring coverage: coverage execution evidence reports configured repository docstring gates passed or docstring coverage was advisory.
DAG: CodeGraph/source-backed behavior map connects CHANGELOG.md to the affected review, runtime, or workflow path and required checks.
PoC/execution: coverage-evidence job executed on the current head and reported PASS.
DDD/domain: workflow and repository-governance invariants were reviewed against changed files in bounded evidence.
CDD/context: CodeGraph evidence, changed-file history, and focused hunks were reviewed from bounded-review-evidence.md.
Similar issues: changed-file history evidence was reviewed for comparable local precedents.
Claim/concept check: bounded evidence, repository source, current-head workflow evidence, and, where numeric, scientific, statistical, or literature-backed claims are affected, original-paper/formula evidence and parameter-recovery expectations were used for claims.
Standards search: standards and external-source claims require trusted bounded source evidence prepared outside the isolated model process; no evidence-backed standards blocker is present in bounded evidence.
Compatibility/convention: changed workflow/script conventions, object naming, and reserved-word safety for schema/API/config/code surfaces were checked in bounded evidence.
Breaking-change/backcompat: deployment evidence and changed-file history were checked for backward-compatibility risk.
Performance: changed surfaces were checked for performance risk in bounded evidence.
Developer experience: changed automation, review, test, setup, and maintenance surfaces were checked for helpful or obstructive DX impact in bounded evidence.
User experience: connected user, operator, API, CLI, documentation, review-comment, status-check, rendering, and workflow-reader behavior was checked for contradictions against code, docs, and tests in bounded evidence.
Visual/DOM: deterministic repair does not infer browser runtime execution; source-backed DOM/UI evidence and trusted workflow receipts were reviewed when present, and non-web surfaces used API/CLI/log/docs/workflow evidence instead.
Accessibility/i18n: accessibility, localization, and human-readable text surfaces were checked where UI, CLI, API message, docs, logs, or review text changed.
Supply-chain/license: dependency, package, model, container, and external-tool changes were checked in bounded evidence.
Packaging: package, build, test, lint, and security contracts were checked in bounded evidence.
Security/privacy: workflow-token, review-gate, and repository-automation security/privacy boundaries were checked in bounded evidence.
Adversarial validation
{"status":"passed","probes":[{"path":"tests/api/session-revocation.test.mjs","line":1,"hypothesis":"A malformed or algorithm-confused session JWT (non-HS256 header, array claims, invalid subject/expiry/token-version) could slip through one of the query-token transports (calendar, SSE, attachment-view) that does not share the bearer-middleware verifier, enabling an authentication bypass.","attack_or_counterexample":"19 hostile compact tokens minted with the test-only signUnsafe helper, including alg:'none' and typ:'JWS' headers, array header/claims, string/zero/unsafe subject, missing/string/expired expiry, and missing/null/boolean/string/fractional/negative/unsafe token-version claims, presented to /api/me (Bearer), /api/projects/{id}/calendar.ics?token=, /api/projects/{id}/stream?token=, and /api/projects/{id}/attachments/missing/view?token=","evidence":"Trusted test trace at tests/api/session-revocation.test.mjs:1 declares the shared fail-closed verifier invariant; the CodeGraph verbatim source shows expectRejectedEverywhere asserting HTTP 401 for every variant on all four transports (lines 84-89, 156-179, 395-398) and signer-side assert.throws (lines 91-117); Coverage execution evidence reports Result PASS for the supported repository suites and package.json:15 wires this file into npm run test:api, so the 401/throw assertions passed in the trusted run. source-line-sha256=67a345eeead9f15b5967393c346be33c3d7b79c71c5f4f4600a3015839a30805","outcome":"falsified"},{"path":"tests/api/session-revocation.test.mjs","line":208,"hypothesis":"After /api/auth/logout-all bumps the persisted token_version, a stale token from the second device could still be honored on one transport (e.g., the attachment-view URL transport), or the replacement token could fail on the same boundary, breaking the revocation contract.","attack_or_counterexample":"Two independently minted device sessions tokenA and tokenB verified across all four transports, then /api/auth/logout-all, then the same four transports re-probed with both stale tokens and the fresh replacement token returned by logout-all.","evidence":"Trusted test trace at tests/api/session-revocation.test.mjs:208 closes the two-device revocation test; the CodeGraph verbatim source lines 200-207 assert stale tokenA and tokenB are rejected with HTTP 401 everywhere after logout-all while freshToken returns 200 on bearer/calendar/SSE and 404 on the attachment-view route (authenticated, missing resource), proving authentication-before-resource-lookup ordering; Coverage execution evidence reports Result PASS for the supported repository suites including this file, so the assertions passed in the trusted run. source-line-sha256=29576b54e255e3c948eea5b5904fa38b81682fdd3cbd9ae841ff0e7bd80d394c","outcome":"falsified"}],"residual_risk":"The server/auth.mjs focused hunk in bounded evidence is truncated after the constant/import region, so the exact signature-digest comparison, db lookup shape, and token_version compare lines were not directly re-read by this model; the design record, changelog, wired-in regression suite, and Coverage PASS affirm the claimed fail-closed semantics, but a follow-up human or Strix read of the full 188-line file is advisable. The bearer-token-in-URL calendar/SSE/attachment-view transport remains in place (documented, not removed) pending issue #413; the PR makes validation and revocation consistent but does not eliminate that transport."}- Result: APPROVE
- Reason: PR #443 centralizes DB-backed session-JWT revocation across bearer, calendar, SSE, and attachment-view transports with fail-closed safe-integer claim validation and a 7-day TTL cap; the adversarial regression suite is wired into npm run test:api (package.json:15), the trusted Coverage execution evidence reports Result PASS, no failed checks or unresolved threads exist at head, and the changed docs/changelog match the asserted behavior.
- Head SHA:
4790878f2189cdf16148d7eaefcf075417a90d6f - Workflow run: 31339245118
- Workflow attempt: 1
0055199
into
fix/security-hono-attachment-refresh-final
Summary
Restack the session-revocation boundary directly on #432 exact head
9746592143d4e87c03b7a95adf678e789d3d352a. This clean five-file change replaces the diverged #436 branch without reintroducing historical attachment code.HS256/JWTheader, object claims, positive safe-integer subject, future safe-integer expiry, and non-negative safe-integertoken_version.logout-allrevocation applies uniformly to every transport.docs/doctoring/session-revocation.mdand record the security change inCHANGELOG.md.Exact scope
Relative to #432's exact head, only five files change:
CHANGELOG.mddocs/doctoring/session-revocation.mdpackage.jsonserver/auth.mjstests/api/session-revocation.test.mjsNo attachment-list, Clearfolio, database-schema, UI, deployment, workflow, or dependency code is changed by this pull request.
Security and standards contract
The doctoring record maps the implementation to RFC 7519, RFC 8725, RFC 6750, and RFC 9700. This pull request does not claim to remove the existing query-token transport. It provides one strict revocation boundary until #413 replaces general session JWTs in URLs with narrowly scoped opaque grants and separately revocable calendar subscription secrets.
Verification contract
The exact current head must prove:
logout-allrejects both stale sessions on bearer, calendar, SSE, and attachment-view paths;Sequencing
This pull request intentionally targets #432's branch and remains Draft while #432 is under protected review. After #432 merges, retarget this exact five-file change to
develop, rerun every exact-head check, obtain independent approval, and only then enable auto-merge.Supersedes #436 and #414.