Skip to content

fix(webapp): fold S2 token scope into access-token cache key#3668

Merged
ericallam merged 1 commit into
mainfrom
fix/s2-access-token-cache-ops-fingerprint
May 19, 2026
Merged

fix(webapp): fold S2 token scope into access-token cache key#3668
ericallam merged 1 commit into
mainfrom
fix/s2-access-token-cache-ops-fingerprint

Conversation

@ericallam
Copy link
Copy Markdown
Member

@ericallam ericallam commented May 19, 2026

Summary

The S2 access-token cache key was ${basin}:${streamPrefix} — purely server-derived but blind to the scope/ops list hardcoded one method away. When the ops list changes in code (e.g. #3644 added trim so chat.agent's per-turn trim chain can issue AppendRecord.trim()), pre-deploy tokens still in cache get returned to SDK callers for up to the token's TTL (24h default), surfacing as Operation not permitted 403s on any op outside the old scope.

Fix

Lift the ops list to a module constant and fold its sorted-join fingerprint into the cache key:

const S2_TOKEN_OPS = ["append", "create-stream", "trim"] as const;
const S2_TOKEN_OPS_FINGERPRINT = [...S2_TOKEN_OPS].sort().join(",");

// in getS2AccessToken
const cacheKey = `${this.basin}:${this.streamPrefix}:${S2_TOKEN_OPS_FINGERPRINT}`;

// in s2IssueAccessToken
scope: { /* ... */ ops: [...S2_TOKEN_OPS], /* ... */ }

The fingerprint is derived from the single source of truth, so any future scope change auto-invalidates without anyone remembering to bump a literal version. The Unkey L1 (in-memory LRU) and L2 (Redis) layers share the same key derivation, so both reset together on the next deploy with no manual cache busting.

Test plan

  • pnpm run typecheck --filter webapp
  • Run a multi-turn chat.agent chat via references/ai-chat and confirm no chat.agent: trim failed; will retry next turn warn span fires across turn-completes.

The S2 access-token cache key was `${basin}:${streamPrefix}` — purely
server-derived but not aware of the scope/ops the server hardcodes when
minting. After a scope change in code (e.g. #3644 adding `trim` to the
ops list), pre-deploy cached tokens still in Redis/L1 LRU keep getting
returned for up to 24h, surfacing as "Operation not permitted" 403s
on any operation outside the old scope.

Lifting the ops list to a module constant and folding its sorted-join
fingerprint into the cache key makes scope changes self-invalidating —
the next deploy's first call mints fresh tokens under a new key and the
stale entries TTL out without anyone busting Redis.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 19, 2026

⚠️ No Changeset found

Latest commit: 5d8229b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Review Change Stack

Walkthrough

This PR adds scope fingerprinting to the S2 access-token cache mechanism. A new constant defines the scoped operations (append, create-stream, trim), and a derived fingerprint is computed. The access-token cache key is updated to include this fingerprint alongside basin and stream prefix, so when scoped operations change, previously cached tokens become invalidated. Token issuance now references the shared ops constant instead of an inline array. A changelog entry documents that cached tokens are automatically invalidated upon scope changes rather than returning stale tokens for up to 24 hours.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: folding the S2 token scope/ops fingerprint into the access-token cache key to fix stale token issues.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 The PR description comprehensively explains the problem, fix, and includes a test plan with specific steps.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/s2-access-token-cache-ops-fingerprint

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 and usage tips.

@ericallam ericallam marked this pull request as ready for review May 19, 2026 12:29
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@ericallam ericallam merged commit 436b7a9 into main May 19, 2026
31 checks passed
@ericallam ericallam deleted the fix/s2-access-token-cache-ops-fingerprint branch May 19, 2026 12:33
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.

2 participants