Skip to content

clients/web: scope AI assistant MCP token to the current organization - #14154

Closed
pieterbeulque wants to merge 1 commit into
mainfrom
fix/ai-product-creation-org-scoping
Closed

pieterbeulque wants to merge 1 commit into
mainfrom
fix/ai-product-creation-org-scoping

Conversation

@pieterbeulque

@pieterbeulque pieterbeulque commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Bug

The AI product-creation assistant creates products under the wrong organization.

generateOAT() in clients/apps/web/src/app/(main)/dashboard/[organization]/(header)/products/new/ai/chat/route.ts cached the MCP access token in the polar_mcp_session cookie and returned it unconditionally whenever the cookie existed. The token is minted with sub_type: 'organization', sub: organizationId — i.e. bound to whichever org was open the first time the assistant was used — and the cookie name is a fixed constant, not per-org.

Server-side, an organization-scoped token always resolves to its own org (and 422s if a different organization_id is passed), so after switching organizations in the dashboard, the assistant kept reusing org A's token and deterministically created products in org A.

Fix

Minimal, value-scoped cache:

  • New colocated helper mcpSessionCookie.ts with serializeMCPSessionCookie / parseMCPSessionCookie — the cookie value now stores { organizationId, token } as JSON.
  • generateOAT() only reuses the cached token when its organizationId matches the organization the user currently has open; on mismatch (or any unparseable value) it mints a fresh org-scoped token and overwrites the cookie.
  • The hasToolAccess heuristic in POST now also checks the cached token's org instead of mere cookie presence, so a stale token from another org no longer counts as tool access.
  • Legacy bare-token cookie values fail JSON parsing and are treated as a cache miss, so existing sessions transparently migrate to the scoped format.

Verification

  • New unit tests mcpSessionCookie.test.ts (4 tests: same-org hit, cross-org miss, legacy bare-token miss, malformed/incomplete payload miss) — pnpm vitest run passes.
  • pnpm typecheck in clients/apps/web — clean.
  • oxlint --type-aware on the touched files and oxfmt --check — clean.

🤖 Generated with Claude Code

Review in cubic

The AI product-creation assistant cached its MCP access token in the
polar_mcp_session cookie and reused it unconditionally. The token is
minted with sub_type=organization for the org that was open at mint
time, so after switching organizations the assistant kept acting on the
first org and created products there.

Store the organization id alongside the token in the cookie value and
only reuse the cached token when it matches the organization the user
has open; otherwise mint a fresh org-scoped token (overwriting the
cookie). Legacy bare-token cookie values fail parsing and are treated
as a cache miss, so they are transparently re-minted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
orbit Ready Ready Preview Sep 5, 2026 1:59pm UTC
polar-test Ready Ready Preview Sep 5, 2026 1:59pm UTC

Request Review

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 3 files

Confidence score: 2/5

  • In clients/apps/web/src/app/(main)/dashboard/[organization]/(header)/products/new/ai/chat/route.ts, the cache can reuse the prior account’s user-subject token when accounts switch within the same organization, risking requests being attributed to the wrong user—include the authenticated user subject in the cache key or invalidation logic.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="clients/apps/web/src/app/(main)/dashboard/[organization]/(header)/products/new/ai/chat/route.ts">

<violation number="1" location="clients/apps/web/src/app/(main)/dashboard/[organization]/(header)/products/new/ai/chat/route.ts:251">
P1: When another account logs into the same browser while the organization is unchanged, this cache reuses the previous account's user-subject token because it compares only `organizationId`. Include the authenticated user in the cookie value and validation, or clear this cookie on logout, so MCP actions cannot run with the previous user's permissions.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

return requestCookies.get(CONFIG.AUTH_MCP_COOKIE_KEY)!.value
const cachedSession = requestCookies.get(CONFIG.AUTH_MCP_COOKIE_KEY)
if (cachedSession) {
const cachedToken = parseMCPSessionCookie(

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.

P1: When another account logs into the same browser while the organization is unchanged, this cache reuses the previous account's user-subject token because it compares only organizationId. Include the authenticated user in the cookie value and validation, or clear this cookie on logout, so MCP actions cannot run with the previous user's permissions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At clients/apps/web/src/app/(main)/dashboard/[organization]/(header)/products/new/ai/chat/route.ts, line 251:

<comment>When another account logs into the same browser while the organization is unchanged, this cache reuses the previous account's user-subject token because it compares only `organizationId`. Include the authenticated user in the cookie value and validation, or clear this cookie on logout, so MCP actions cannot run with the previous user's permissions.</comment>

<file context>
@@ -242,8 +246,15 @@ async function generateOAT(
-    return requestCookies.get(CONFIG.AUTH_MCP_COOKIE_KEY)!.value
+  const cachedSession = requestCookies.get(CONFIG.AUTH_MCP_COOKIE_KEY)
+  if (cachedSession) {
+    const cachedToken = parseMCPSessionCookie(
+      cachedSession.value,
+      organizationId,
</file context>

@pieterbeulque

Copy link
Copy Markdown
Contributor Author

Will pick it back up later.

@pieterbeulque
pieterbeulque deleted the fix/ai-product-creation-org-scoping branch September 22, 2026 06:29

This branch was successfully deployed

2 active deployments
Preview – polar-test — 3a4876fd Deployed Sep 5, 2026 by vercel[bot]
Preview – orbit — 3a4876fd Deployed Sep 5, 2026 by vercel[bot]
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