Skip to content

Guest sessions#2003

Open
chelojimenez wants to merge 3 commits intomainfrom
guest-sesions
Open

Guest sessions#2003
chelojimenez wants to merge 3 commits intomainfrom
guest-sesions

Conversation

@chelojimenez
Copy link
Copy Markdown
Contributor

@chelojimenez chelojimenez commented May 2, 2026

Note

High Risk
Touches security-sensitive session plumbing (cookie forwarding, token caching, and guest migration) across both client and server, so mistakes could break guest continuity or inadvertently leak/strip cookies. Also adds retry/backoff behavior that could change request volume and edge-case timing.

Overview
Guest sessions are now cookie-backed. The client guest-session module stops persisting JWTs to localStorage, keeps tokens in memory with deduped in-flight requests, adds getExistingGuestBearerToken() (lookup_only) to avoid minting new guests, and implements one-time legacy localStorage token migration via legacyToken plus a generation guard to prevent stale in-flight responses from resurrecting cleared tokens.

Server guest-session proxying is tightened and expanded. POST /api/web/guest-session now parses mode/legacyToken, forwards browser context (but only the __Host-mcpjam_guest_session cookie) to upstream, passes through upstream Set-Cookie, returns 204 on lookup misses, and distinguishes 403 revocations from other upstream errors. Convex provisioning now also sets GUEST_SESSION_HASH_PEPPER, with new reusable local-secret-store for dev-only secret persistence.

Registry guest-star merge is safer. useRegistryServers switches to lookup_only before merging guest stars after sign-in, adds bounded retries with backoff, and includes new tests for the merge/no-guest cases.

Reviewed by Cursor Bugbot for commit 3bac0c6. Bugbot is set up for automated code reviews on this repo. Configure here.

@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label May 2, 2026
@chelojimenez chelojimenez temporarily deployed to preview-pr-2003 May 2, 2026 07:56 — with GitHub Actions Inactive
@chelojimenez
Copy link
Copy Markdown
Contributor Author

chelojimenez commented May 2, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@dosubot dosubot Bot added the enhancement New feature or request label May 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Internal preview

Preview URL: https://mcp-inspector-pr-2003.up.railway.app
Deployed commit: 0cd7283
PR head commit: 3bac0c6
Backend target: staging fallback.
Health: ❌ Convex unreachable — see job logs (staging may need convex deploy)
Access is employee-only in non-production environments.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e5d15e696

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"lookup_or_create",
legacyToken,
);
if (session) cachedSession = session;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Prevent stale in-flight session from overwriting refresh

A forceRefreshGuestSession() call no longer protects against an older getOrCreateGuestSession() request finishing later and writing a stale token back into cachedSession. forceRefreshGuestSession() only nulls inFlightRequest, which does not cancel the original fetch, and this unconditional assignment lets the late response overwrite the freshly refreshed JWT; subsequent requests can reuse that stale token and re-trigger 401/retry loops.

Useful? React with 👍 / 👎.

Comment on lines +144 to +145
if (response.status === 204 || response.status === 404) {
return { kind: "miss", setCookies };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat upstream 404 as failure in session creation path

Mapping all upstream 404 responses to kind: "miss" causes /api/web/guest-session to return 204 instead of an error, which silently disables guest auth when the upstream session endpoint is missing/misconfigured. For lookup_or_create callers this is not a normal miss condition, so the current behavior hides operational failures and makes client behavior look like “no guest session” rather than surfacing a recoverable server error.

Useful? React with 👍 / 👎.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e32d1fd-3e41-4e28-a98b-4793e5b9d5ba

📥 Commits

Reviewing files that changed from the base of the PR and between 943e95f and 3bac0c6.

📒 Files selected for processing (6)
  • mcpjam-inspector/client/src/hooks/useRegistryServers.ts
  • mcpjam-inspector/client/src/lib/guest-session.ts
  • mcpjam-inspector/package.json
  • mcpjam-inspector/server/routes/web/guest-session.ts
  • mcpjam-inspector/server/utils/guest-session-source.ts
  • mcpjam-inspector/server/utils/local-secret-store.ts
✅ Files skipped from review due to trivial changes (2)
  • mcpjam-inspector/package.json
  • mcpjam-inspector/server/routes/web/guest-session.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • mcpjam-inspector/server/utils/local-secret-store.ts
  • mcpjam-inspector/server/utils/guest-session-source.ts
  • mcpjam-inspector/client/src/lib/guest-session.ts

Walkthrough

Guest-session handling was refactored from localStorage persistence to an in-memory cache with cookie-backed identity and deduplicated requests. The client introduces getExistingGuestBearerToken (replacing peekStoredGuestToken) and adds guarded, retry-capable guest-star merge logic in useRegistryServers. The server /api/web/guest-session endpoint and guest-session fetchers accept structured request bodies (mode, legacyToken), forward a single guest cookie and client headers, and return discriminated results (session | miss | error) with propagated Set-Cookie. Local-secret management was centralized (getOrCreateLocalSecret) and a guest-session hash pepper was added to Convex provisioning. Tests updated and expanded accordingly.


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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcpjam-inspector/client/src/lib/guest-session.ts`:
- Around line 121-128: The async result of requestGuestSession can overwrite
cachedSession after clearGuestSession()/forceRefreshGuestSession(); add a
generation/epoch guard or abort logic so stale responses are ignored before
committing. Concretely: introduce a numeric epoch (e.g., guestEpoch) incremented
inside clearGuestSession and forceRefreshGuestSession, capture the current epoch
at the start of getOrCreateGuestSession/getExistingGuestBearerToken (or when
creating inFlightRequest), and before assigning cachedSession or returning set
the check "if (capturedEpoch !== guestEpoch) then abort/return undefined" to
prevent resurrecting old tokens; alternatively use an AbortController captured
and signalled on clear/forceRefresh and reject older requests before writing
cachedSession. Apply this guard consistently where inFlightRequest,
cachedSession, getOrCreateGuestSession, and getExistingGuestBearerToken are
mutated.

In `@mcpjam-inspector/server/routes/web/guest-session.ts`:
- Around line 115-120: The current GuestSessionFetchContext construction
forwards the entire cookie header (c.req.header("cookie")) which leaks unrelated
cookies; extract only the __Host-mcpjam_guest_session cookie value and set
context.cookie to either "__Host-mcpjam_guest_session=<value>" or null if
missing. Locate where GuestSessionFetchContext is built (the context object
creation) and replace the cookie assignment to parse c.req.header("cookie") for
the __Host-mcpjam_guest_session pair (or use a small helper to
getCookieValue("cookieHeader", "__Host-mcpjam_guest_session")) so only that
single cookie is forwarded upstream.
🪄 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

Run ID: 4a3d6d59-9f5d-4f79-bed1-c01c13b5e919

📥 Commits

Reviewing files that changed from the base of the PR and between b4cd170 and 8e5d15e.

📒 Files selected for processing (11)
  • mcpjam-inspector/client/src/hooks/__tests__/useRegistryServers.guest-merge.test.tsx
  • mcpjam-inspector/client/src/hooks/__tests__/useRegistryServers.test.tsx
  • mcpjam-inspector/client/src/hooks/useRegistryServers.ts
  • mcpjam-inspector/client/src/lib/__tests__/guest-session.test.ts
  • mcpjam-inspector/client/src/lib/guest-session.ts
  • mcpjam-inspector/server/routes/web/__tests__/guest-session.test.ts
  • mcpjam-inspector/server/routes/web/guest-session.ts
  • mcpjam-inspector/server/utils/__tests__/guest-session-source.test.ts
  • mcpjam-inspector/server/utils/convex-guest-auth-sync.ts
  • mcpjam-inspector/server/utils/guest-session-pepper.ts
  • mcpjam-inspector/server/utils/guest-session-source.ts

Comment thread mcpjam-inspector/client/src/lib/guest-session.ts Outdated
Comment thread mcpjam-inspector/server/routes/web/guest-session.ts
Comment thread mcpjam-inspector/client/src/hooks/useRegistryServers.ts
Comment thread mcpjam-inspector/server/utils/guest-session-pepper.ts
@chelojimenez chelojimenez temporarily deployed to preview-pr-2003 May 2, 2026 08:21 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcpjam-inspector/client/src/hooks/useRegistryServers.ts`:
- Around line 419-443: The effect currently uses refs (mergeRanRef,
mergeInFlightRef) so a transient failure leaves mergeRanRef false? actually
comment says transient failure becomes permanent — fix by only marking the merge
as done on success and/or surface completion via React state so the effect can
re-run: remove or delay setting mergeRanRef.current = true until after await
loadCatalog() (i.e., only set after successful mergeGuestRegistryStars and
loadCatalog), and/or replace mergeRanRef with a useState flag (e.g., [mergeRan,
setMergeRan]) included in the effect dependencies so the effect will retry on
subsequent renders when enabled/isAuthenticated/loadCatalog change; ensure
mergeInFlightRef is still reset in finally and do not set mergeRan on error.
Reference getExistingGuestBearerToken, mergeGuestRegistryStars,
clearGuestSession, resetTokenCache, loadCatalog, mergeRanRef, mergeInFlightRef.

In `@mcpjam-inspector/client/src/lib/guest-session.ts`:
- Around line 141-153: Multiple callers are independently calling
consumeLegacyToken() and racing to forward the same legacy cookie; change the
flow so consumeLegacyToken() is called exactly once per logical operation and
that same legacyToken value is passed into all requestGuestSession(...) calls
(e.g., the lookup_or_create, lookup_only, and forceRefresh branches), and mark
legacyMigrationConsumed as true immediately when you consume the token (or
before awaiting the outbound request) to prevent a concurrent caller from
reading the same token; use the existing currentInFlight / sessionGeneration
logic to serialize callers and ensure all code paths (the blocks around
requestGuestSession(...) at the current snippet and the other spots noted)
accept an externally captured legacyToken instead of calling
consumeLegacyToken() themselves.

In `@mcpjam-inspector/server/utils/local-secret-store.ts`:
- Around line 62-83: The current getOrCreateLocalSecret function falls through
to local-file behavior for any NODE_ENV that's not "production" or "test";
change this to an explicit allowlist so only "development" uses
loadPersistedLocalSecret/persistLocalSecret: after checking env var
(spec.envVar), if NODE_ENV === "development" return
loadPersistedLocalSecret(spec) || persistLocalSecret(spec), otherwise throw new
Error(spec.productionErrorMessage) so non-dev environments
(staging/preview/ci/unset) surface an error instead of silently regenerating
secrets.
🪄 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

Run ID: 79768e3f-bcc5-493d-8233-99c82fb82125

📥 Commits

Reviewing files that changed from the base of the PR and between 8e5d15e and 943e95f.

📒 Files selected for processing (10)
  • mcpjam-inspector/client/src/hooks/useRegistryServers.ts
  • mcpjam-inspector/client/src/lib/__tests__/guest-session.test.ts
  • mcpjam-inspector/client/src/lib/guest-session.ts
  • mcpjam-inspector/server/routes/web/__tests__/guest-session.test.ts
  • mcpjam-inspector/server/routes/web/guest-session.ts
  • mcpjam-inspector/server/utils/__tests__/guest-session-source.test.ts
  • mcpjam-inspector/server/utils/guest-session-pepper.ts
  • mcpjam-inspector/server/utils/guest-session-secret.ts
  • mcpjam-inspector/server/utils/guest-session-source.ts
  • mcpjam-inspector/server/utils/local-secret-store.ts
✅ Files skipped from review due to trivial changes (1)
  • mcpjam-inspector/server/routes/web/tests/guest-session.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • mcpjam-inspector/server/routes/web/guest-session.ts
  • mcpjam-inspector/client/src/lib/tests/guest-session.test.ts
  • mcpjam-inspector/server/utils/tests/guest-session-source.test.ts
  • mcpjam-inspector/server/utils/guest-session-source.ts

Comment thread mcpjam-inspector/client/src/hooks/useRegistryServers.ts
Comment on lines +141 to +153
const legacyToken = consumeLegacyToken();
const generation = sessionGeneration;
inFlightRequest = (async () => {
try {
const response = await fetch("/api/web/guest-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
});

if (!response.ok) {
console.error(
"Failed to create guest session:",
response.status,
response.statusText,
);
return null;
}
// Initialized to a placeholder so TS sees a definite assignment; the
// real value is assigned synchronously below before the IIFE's finally
// (which references currentInFlight via closure) can run.
let currentInFlight: Promise<GuestSession | null> = Promise.resolve(null);

const session: GuestSession = await response.json();
// Only write if no force-refresh has invalidated this generation
if (sessionGeneration === generation) {
writeToStorage(session);
currentInFlight = (async () => {
try {
const session = await requestGuestSession(
"lookup_or_create",
legacyToken,
);
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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Serialize legacy-token forwarding across request modes.

lookup_or_create, lookup_only, and forceRefresh each call consumeLegacyToken() independently, and legacyMigrationConsumed is only flipped after the response. Two concurrent callers can therefore capture and send the same legacyToken before the first Set-Cookie lands, which makes the migration path race itself on a cold browser.

Also applies to: 199-205, 258-262

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/lib/guest-session.ts` around lines 141 - 153,
Multiple callers are independently calling consumeLegacyToken() and racing to
forward the same legacy cookie; change the flow so consumeLegacyToken() is
called exactly once per logical operation and that same legacyToken value is
passed into all requestGuestSession(...) calls (e.g., the lookup_or_create,
lookup_only, and forceRefresh branches), and mark legacyMigrationConsumed as
true immediately when you consume the token (or before awaiting the outbound
request) to prevent a concurrent caller from reading the same token; use the
existing currentInFlight / sessionGeneration logic to serialize callers and
ensure all code paths (the blocks around requestGuestSession(...) at the current
snippet and the other spots noted) accept an externally captured legacyToken
instead of calling consumeLegacyToken() themselves.

Comment thread mcpjam-inspector/server/utils/local-secret-store.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 943e95f809

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +94 to +96
if (response.status === 204 || response.status === 404) {
if (legacyToken) deleteLegacyToken();
return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't delete legacy token on 404 guest-session responses

requestGuestSession() treats both 204 and 404 as definitive misses and immediately calls deleteLegacyToken(). A 404 in lookup_or_create is not definitive (it can come from a temporarily missing/misrouted /api/web/guest-session endpoint), so this path permanently discards the only migration token and prevents retrying migration after the server issue is fixed. Keep the legacy token for unexpected 404 responses (or only clear it for confirmed success / explicit lookup-only misses).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 943e95f. Configure here.

Comment thread mcpjam-inspector/server/utils/guest-session-source.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant