Repository navigation
Verify session tokens before trusting server-side identity - #452
Merged
Merged
Conversation
getSession() returned the session straight from the auth cookie, and supabase.auth.getSession() does not check the access token signature, so server-side owner and admin checks trusted unverified cookie contents. Derive the user from getClaims() instead, which verifies the JWT against the project's JWKS (or the Auth server for symmetric keys). Co-authored-by: Eric Zakariasson <ericzakariasson@users.noreply.github.com>
The callback appended the raw next query param to the base URL, so values such as @evil.example redirected users off-site after signing in. Resolve next against the request origin and fall back to / for anything that leaves it. Co-authored-by: Eric Zakariasson <ericzakariasson@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Expired tokens crash session reads
- Reproduced that the no-argument getClaims() in the locked auth-js 2.105.4 throws a raw Error('JWT has expired') out of getSession() when the cookie session's expires_at is null or still in the future but its access token has expired, so getSession() now catches any throw from getClaims() and returns null, with createClient() left outside the catch so Next.js cookies() errors still propagate.
Or push these changes by commenting:
@cursor push cc5236ce0a
Preview (cc5236ce0a)
diff --git a/apps/cursor/src/utils/supabase/auth.ts b/apps/cursor/src/utils/supabase/auth.ts
--- a/apps/cursor/src/utils/supabase/auth.ts
+++ b/apps/cursor/src/utils/supabase/auth.ts
@@ -19,7 +19,11 @@
*/
export async function getSession(): Promise<VerifiedSession | null> {
const supabase = await createClient();
- const { data, error } = await supabase.auth.getClaims();
+ // getClaims() throws, instead of returning `error`, for some unverifiable
+ // tokens (e.g. an expired one whose session `expires_at` hasn't passed).
+ const { data, error } = await supabase.auth
+ .getClaims()
+ .catch((error: unknown) => ({ data: null, error }));
if (error || !data?.claims.sub) {
return null;You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 604eb26. Configure here.
getClaims() in @supabase/auth-js 2.105 throws a plain Error for an expired token instead of returning it as an error, so getSession() let the exception escape and server pages that call it could 500. Any getClaims failure now means there's no verified session. Co-authored-by: Cursor <cursoragent@cursor.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Two auth hardening fixes. The Community Plugins MCP server (#453) builds on these, but they stand on their own and should merge first.
1. Server-side identity now comes from verified token claims
getSession()inapps/cursor/src/utils/supabase/auth.tsreturned the session straight from the auth cookie.supabase.auth.getSession()does not check the access token's signature. So every server-side owner and admin check trusted unverified cookie contents:authActionClient,adminActionClient,/admin/plugins, and the edit pages. Several of those actions then write with the secret-key client, which bypasses row-level security.getSession()now callssupabase.auth.getClaims(), which verifies the JWT against the project's JWKS (or asks the Auth server when the project uses symmetric keys). It builds the user fromsub,email, anduser_metadata.session.user.id, plusemailanduser_metadatainsafe-action.ts, so no call sites change.getSession()for UI gating. That is fine because the server enforces everything.2.
/auth/callbackonly redirects within the siteThe callback appended the raw
nextquery parameter to the base URL.next=@evil.exampletherefore producedhttp://host@evil.example/, sending users off-site after they signed in.nextis now resolved against the request origin, and anything that would leave the origin falls back to/.Verification (local Supabase stack +
bun dev)/admin/pluginsrendered the moderation queue, andapprovePluginActionpublished a hidden plugin.Session not found!, the plugin stays hidden, and/admin/pluginsrenders no queue data.Unauthorized: admin access required. An admin still sees the queue and can approve./auth/callback:next=@evil.exampleredirected tohttp://localhost:3000@evil.example/.@evil.example→/@evil.example,//evil.example→/, and/plugins/acme-rulesis preserved.bunx biome ci .andbun run typecheckpass. The remaining Biome warnings already existed in files this PR doesn't touch.Notes
getClaims()makes one Auth server call per server-side session read. With asymmetric signing keys it verifies locally against a cached JWKS.maincommit since Add admin moderation actions to the plugin detail page #434 (Sep 11). This PR doesn't cause it, but it blocks deploying this fix until someone checks the Vercel build logs.Note
High Risk
Touches authentication and authorization paths; behavior changes for forged cookies and redirect handling, though the intent is security hardening.
Reviewed by Cursor Bugbot for commit e437a94. Bugbot is set up for automated code reviews on this repo. Configure here.