Skip to content

fix(backend): Update logic for cross origin sync handshake #6600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

brkalow
Copy link
Member

@brkalow brkalow commented Aug 21, 2025

Description

Fixes an issue in the cross origin sync handshake logic where we were not considering "known" auth referrers, such as our Account Portal or Frontend API. This resulted in unnecessary handshakes even if a user was newly signed in.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • Bug Fixes
    • Requests from Clerk-managed domains (accounts portal and frontend API) no longer trigger unnecessary primary-domain session handshakes, reducing unexpected redirects and keeping users signed in; non-Clerk cross-origin requests still sync as before.
  • Tests
    • Added extensive cross-origin handshake test coverage across many referer origins and scenarios.
  • Chores
    • Prepared a patch release entry for the backend package.

Copy link

changeset-bot bot commented Aug 21, 2025

🦋 Changeset detected

Latest commit: 1c7deff

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@clerk/backend Patch
@clerk/agent-toolkit Patch
@clerk/astro Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/remix Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch

Not sure what this means? Click here to learn what changesets are.

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

@brkalow brkalow marked this pull request as ready for review August 21, 2025 16:24
Copy link

vercel bot commented Aug 21, 2025

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

Project Deployment Preview Comments Updated (UTC)
clerk-js-sandbox Ready Ready Preview Comment Aug 22, 2025 9:52pm

Copy link
Contributor

coderabbitai bot commented Aug 21, 2025

📝 Walkthrough

Walkthrough

Adds a changeset file for a patch release. Adds AuthenticateContext.isClerkDomain(), which parses the request referrer and uses shared helpers to detect Clerk-related referrer domains (frontend API/FAPI hosts, current and legacy dev accounts portals, buildAccountsBaseUrl origin, and hosts starting with accounts.). Updates authenticateRequest to skip forcing a primary-domain cross-origin sync when the referrer is a Clerk domain. Adds extensive tests in packages/backend/src/tokens/tests/request.test.ts exercising many referrer origins and handshake conditions. No other production logic or exported API changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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

🧹 Nitpick comments (3)
.changeset/yellow-vans-walk.md (1)

5-5: Hyphenate “cross‑origin” and clarify scope.

Nit: use “cross-origin” and call out document navigations to better reflect the code path gated by sec-fetch-dest === 'document'.

-Fix logic for forcing a session sync on cross origin requests.
+Fix logic for forcing a session sync on cross-origin document requests from non‑Clerk domains.
packages/backend/src/tokens/request.ts (1)

576-587: Name the reason string or reuse a constant to ease log-based triage.

Optional: the literal message 'Cross-origin request from satellite domain requires handshake' could be extracted to a constant near AuthErrorReason.PrimaryDomainCrossOriginSync for consistency and easier grep.

- 'Cross-origin request from satellite domain requires handshake',
+ HANDSHAKE_MESSAGES.PrimaryDomainCrossOriginSync,

Supporting snippet (outside this hunk):

export const HANDSHAKE_MESSAGES = {
  PrimaryDomainCrossOriginSync: 'Cross-origin request from satellite domain requires handshake',
} as const;
packages/backend/src/tokens/__tests__/request.test.ts (1)

1640-1665: Dev accounts portal (current) coverage looks good. Consider adding accountsstage.

Optional: add a staging variant (e.g., accountsstage.<...>) to mirror buildAccountsBaseUrl handling and ensure parity across environments.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c3facf1 and ffde23d.

📒 Files selected for processing (4)
  • .changeset/yellow-vans-walk.md (1 hunks)
  • packages/backend/src/tokens/__tests__/request.test.ts (1 hunks)
  • packages/backend/src/tokens/authenticateContext.ts (2 hunks)
  • packages/backend/src/tokens/request.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
**/*

⚙️ CodeRabbit configuration file

If there are no tests added or modified as part of the PR, please suggest that tests be added to cover the changes.

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
.changeset/**

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Automated releases must use Changesets.

Files:

  • .changeset/yellow-vans-walk.md
packages/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Unit tests should use Jest or Vitest as the test runner.

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/__tests__/**/*.{ts,tsx}: Create type-safe test builders/factories
Use branded types for test isolation
Implement proper mock types that match interfaces

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
🧬 Code graph analysis (2)
packages/backend/src/tokens/__tests__/request.test.ts (1)
packages/backend/src/tokens/authStatus.ts (2)
  • AuthErrorReason (105-122)
  • AuthErrorReason (124-124)
packages/backend/src/tokens/authenticateContext.ts (1)
packages/shared/src/buildAccountsBaseUrl.ts (1)
  • buildAccountsBaseUrl (4-15)
🪛 LanguageTool
.changeset/yellow-vans-walk.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...a session sync on cross origin requests.

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (3)
packages/backend/src/tokens/request.ts (1)

576-583: Correctly exempts Clerk-origin referers from cross-origin handshake.

The additional checks against isCrossOriginReferrer() and !authenticateContext.isClerkDomain() align the handshake trigger with intended flows from Clerk-hosted UIs. Looks good.

packages/backend/src/tokens/authenticateContext.ts (1)

1-2: Imports are appropriate and scoped.

buildAccountsBaseUrl and the dev accounts portal origin helpers are used only by the new referrer classifier. No issues.

packages/backend/src/tokens/__tests__/request.test.ts (1)

1710-1744: Good coverage for FAPI referers (redirect-based auth).

The cases for clerk.<...>/v1/... and https://clerk.<...>/sign-in correctly assert that we do not trigger PrimaryDomainCrossOriginSync. Nice.

Comment on lines +1608 to +1637

test('does not trigger handshake when referer is from production accounts portal', async () => {
const request = mockRequestWithCookies(
{
referer: 'https://accounts.example.com/sign-in',
'sec-fetch-dest': 'document',
'sec-fetch-site': 'cross-site',
},
{
__session: mockJwt,
__client_uat: '12345',
},
'https://primary.com/dashboard',
);

const requestState = await authenticateRequest(request, {
...mockOptions(),
publishableKey: PK_LIVE,
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});

expect(requestState).toBeSignedIn({
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});
});

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Test currently passes due to permissive “accounts.*” fallback; align with real Clerk host.

This case uses https://accounts.example.com/..., which is not derivable from the publishable key in mockOptions(). It only passes because production code whitelists any accounts.* host. If we tighten matching (recommended), this test will fail.

Change the referer to the expected accounts origin derived from the PK used in tests (e.g., accounts.inspired.puma-74.lcl.dev) or compute it from the PK to avoid coupling to the permissive fallback.

-          referer: 'https://accounts.example.com/sign-in',
+          referer: 'https://accounts.inspired.puma-74.lcl.dev/sign-in',

Alternatively, if you want to exercise a truly “prod-style” hostname, derive it from buildAccountsBaseUrl(frontendApi) seeded by the parsed PK rather than hardcoding example.com.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('does not trigger handshake when referer is from production accounts portal', async () => {
const request = mockRequestWithCookies(
{
referer: 'https://accounts.example.com/sign-in',
'sec-fetch-dest': 'document',
'sec-fetch-site': 'cross-site',
},
{
__session: mockJwt,
__client_uat: '12345',
},
'https://primary.com/dashboard',
);
const requestState = await authenticateRequest(request, {
...mockOptions(),
publishableKey: PK_LIVE,
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});
expect(requestState).toBeSignedIn({
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});
});
test('does not trigger handshake when referer is from production accounts portal', async () => {
const request = mockRequestWithCookies(
{
referer: 'https://accounts.inspired.puma-74.lcl.dev/sign-in',
'sec-fetch-dest': 'document',
'sec-fetch-site': 'cross-site',
},
{
__session: mockJwt,
__client_uat: '12345',
},
'https://primary.com/dashboard',
);
const requestState = await authenticateRequest(request, {
...mockOptions(),
publishableKey: PK_LIVE,
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});
expect(requestState).toBeSignedIn({
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});
});
🤖 Prompt for AI Agents
In packages/backend/src/tokens/__tests__/request.test.ts around lines 1608 to
1637, the test hardcodes referer "https://accounts.example.com/..." which only
passes because production code currently allows any accounts.* host; update the
test to use the actual accounts origin derived from the publishable key in
mockOptions() (e.g., call the same helper used in production like
buildAccountsBaseUrl(frontendApi) or construct
"https://accounts.<frontendApi-derived-host>") so the referer matches the PK
used in the test instead of relying on the permissive accounts.* fallback.

Comment on lines +1696 to +1719
test('does not trigger cross-origin handshake when referer is from expected accounts portal derived from frontend API', async () => {
const request = mockRequestWithCookies(
{
referer: 'https://accounts.inspired.puma-74.lcl.dev/sign-in',
'sec-fetch-dest': 'document',
'sec-fetch-site': 'cross-site',
},
{
__session: mockJwt,
__client_uat: '12345',
},
'https://primary.com/dashboard',
);

const requestState = await authenticateRequest(request, {
...mockOptions(),
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});

// Should not trigger the specific cross-origin sync handshake we're trying to prevent
expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add a negative test to prevent regressions: “accounts.attacker.com” must still trigger cross-origin handshake.

To guard against over-broad whitelisting, add a case where the referer is an unrelated accounts.* domain and assert that PrimaryDomainCrossOriginSync is triggered.

@@
   describe('Cross-origin sync', () => {
@@
+    test('triggers handshake when referer is unrelated accounts.* domain', async () => {
+      const request = mockRequestWithCookies(
+        {
+          referer: 'https://accounts.attacker.com/signin',
+          'sec-fetch-dest': 'document',
+          'sec-fetch-site': 'cross-site',
+        },
+        {
+          __session: mockJwt,
+          __client_uat: '12345',
+        },
+        'https://primary.com/dashboard',
+      );
+
+      const requestState = await authenticateRequest(request, {
+        ...mockOptions(),
+        publishableKey: PK_LIVE,
+        domain: 'primary.com',
+        isSatellite: false,
+        signInUrl: 'https://primary.com/sign-in',
+      });
+
+      expect(requestState).toMatchHandshake({
+        reason: AuthErrorReason.PrimaryDomainCrossOriginSync,
+        domain: 'primary.com',
+        signInUrl: 'https://primary.com/sign-in',
+      });
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('does not trigger cross-origin handshake when referer is from expected accounts portal derived from frontend API', async () => {
const request = mockRequestWithCookies(
{
referer: 'https://accounts.inspired.puma-74.lcl.dev/sign-in',
'sec-fetch-dest': 'document',
'sec-fetch-site': 'cross-site',
},
{
__session: mockJwt,
__client_uat: '12345',
},
'https://primary.com/dashboard',
);
const requestState = await authenticateRequest(request, {
...mockOptions(),
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});
// Should not trigger the specific cross-origin sync handshake we're trying to prevent
expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
});
describe('Cross-origin sync', () => {
test('does not trigger cross-origin handshake when referer is from expected accounts portal derived from frontend API', async () => {
const request = mockRequestWithCookies(
{
referer: 'https://accounts.inspired.puma-74.lcl.dev/sign-in',
'sec-fetch-dest': 'document',
'sec-fetch-site': 'cross-site',
},
{
__session: mockJwt,
__client_uat: '12345',
},
'https://primary.com/dashboard',
);
const requestState = await authenticateRequest(request, {
...mockOptions(),
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});
// Should not trigger the specific cross-origin sync handshake we're trying to prevent
expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
});
test('triggers handshake when referer is unrelated accounts.* domain', async () => {
const request = mockRequestWithCookies(
{
referer: 'https://accounts.attacker.com/signin',
'sec-fetch-dest': 'document',
'sec-fetch-site': 'cross-site',
},
{
__session: mockJwt,
__client_uat: '12345',
},
'https://primary.com/dashboard',
);
const requestState = await authenticateRequest(request, {
...mockOptions(),
publishableKey: PK_LIVE,
domain: 'primary.com',
isSatellite: false,
signInUrl: 'https://primary.com/sign-in',
});
expect(requestState).toMatchHandshake({
reason: AuthErrorReason.PrimaryDomainCrossOriginSync,
domain: 'primary.com',
signInUrl: 'https://primary.com/sign-in',
});
});
});
🤖 Prompt for AI Agents
In packages/backend/src/tokens/__tests__/request.test.ts around lines 1696 to
1719, add a negative test case that ensures an unrelated accounts.* origin still
triggers the cross-origin handshake: create a request similar to the existing
test but with referer 'https://accounts.attacker.com/sign-in' (keep
'sec-fetch-site': 'cross-site', cookies same, and origin
'https://primary.com/dashboard'), call authenticateRequest with the same
mockOptions (domain: 'primary.com', isSatellite: false, signInUrl:
'https://primary.com/sign-in'), and assert that requestState.reason ===
AuthErrorReason.PrimaryDomainCrossOriginSync to prevent over-broad whitelisting.

Comment on lines +203 to +252
/**
* Determines if the referrer URL is from a Clerk domain (accounts portal or FAPI).
* This includes both development and production account portal domains, as well as FAPI domains
* used for redirect-based authentication flows.
*
* @returns {boolean} True if the referrer is from a Clerk accounts portal or FAPI domain, false otherwise
*/
public isClerkDomain(): boolean {
if (!this.referrer) {
return false;
}

try {
const referrerOrigin = new URL(this.referrer);
const referrerHost = referrerOrigin.hostname;

// Check if referrer is the FAPI domain itself (redirect-based auth flows)
if (this.frontendApi) {
const fapiHost = this.frontendApi.startsWith('http') ? new URL(this.frontendApi).hostname : this.frontendApi;
if (referrerHost === fapiHost) {
return true;
}
}

// Check for development account portal patterns
if (isLegacyDevAccountPortalOrigin(referrerHost) || isCurrentDevAccountPortalOrigin(referrerHost)) {
return true;
}

// Check for production account portal by comparing with expected accounts URL
const expectedAccountsUrl = buildAccountsBaseUrl(this.frontendApi);
if (expectedAccountsUrl) {
const expectedAccountsOrigin = new URL(expectedAccountsUrl).origin;
if (referrerOrigin.origin === expectedAccountsOrigin) {
return true;
}
}

// Check for generic production accounts patterns (accounts.*)
if (referrerHost.startsWith('accounts.')) {
return true;
}

return false;
} catch {
// Invalid URL format
return false;
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Overly broad whitelist: any “accounts.*” host skips the handshake. Tighten matching and prefer original FAPI.

Two concerns:

  • Security/logic risk: referrerHost.startsWith('accounts.') whitelists arbitrary domains (e.g., accounts.attacker.com), causing the primary-domain cross-origin handshake to be skipped for unrelated sites. This weakens the heuristic from “known Clerk referers” to “any accounts.*”.
  • Proxy correctness: building the expected accounts origin from this.frontendApi can be proxy-influenced; you already track originalFrontendApi for token validation. Use that here, too.

Action: remove the generic accounts. fallback and compute the expected accounts origin from the original FAPI. Also keep the explicit dev-portal helpers.

   public isClerkDomain(): boolean {
     if (!this.referrer) {
       return false;
     }

     try {
-      const referrerOrigin = new URL(this.referrer);
-      const referrerHost = referrerOrigin.hostname;
+      const refUrl = new URL(this.referrer);
+      const refHost = refUrl.hostname;

       // Check if referrer is the FAPI domain itself (redirect-based auth flows)
-      if (this.frontendApi) {
-        const fapiHost = this.frontendApi.startsWith('http') ? new URL(this.frontendApi).hostname : this.frontendApi;
-        if (referrerHost === fapiHost) {
+      const fapiSource = this.originalFrontendApi || this.frontendApi;
+      if (fapiSource) {
+        const fapiHost = fapiSource.startsWith('http') ? new URL(fapiSource).hostname : fapiSource;
+        if (refHost === fapiHost) {
           return true;
         }
       }

       // Check for development account portal patterns
-      if (isLegacyDevAccountPortalOrigin(referrerHost) || isCurrentDevAccountPortalOrigin(referrerHost)) {
+      if (isLegacyDevAccountPortalOrigin(refHost) || isCurrentDevAccountPortalOrigin(refHost)) {
         return true;
       }

       // Check for production account portal by comparing with expected accounts URL
-      const expectedAccountsUrl = buildAccountsBaseUrl(this.frontendApi);
+      const expectedAccountsUrl = buildAccountsBaseUrl(fapiSource);
       if (expectedAccountsUrl) {
-        const expectedAccountsOrigin = new URL(expectedAccountsUrl).origin;
-        if (referrerOrigin.origin === expectedAccountsOrigin) {
+        const expectedAccountsOrigin = new URL(expectedAccountsUrl).origin;
+        if (refUrl.origin === expectedAccountsOrigin) {
           return true;
         }
       }
-
-      // Check for generic production accounts patterns (accounts.*)
-      if (referrerHost.startsWith('accounts.')) {
-        return true;
-      }
-
       return false;
     } catch {
       // Invalid URL format
       return false;
     }
   }

Follow-up: I recommend adding a fast path cache for the parsed referrer (used by both isCrossOriginReferrer and isClerkDomain) if this shows up on hot paths. Low priority.

Copy link
Contributor

coderabbitai bot commented Aug 21, 2025

📝 Walkthrough

Walkthrough

  • Adds a changeset marking @clerk/backend for a patch release.
  • Updates authenticateRequest to avoid forcing the primary-domain cross-origin handshake when the referrer is a Clerk domain by adding an isClerkDomain check to the cross-origin condition.
  • Introduces AuthenticateContext.isClerkDomain() to detect Clerk-owned referrers (FAPI host, dev accounts portals—legacy/current, production accounts, and generic accounts.*), with URL parsing safeguards.
  • Adds initPublishableKeyValues helper and related imports to support domain/referrer checks.
  • Expands tests to cover various accounts/FAPI referrers that should not trigger the handshake and a control case that should.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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: 1

🧹 Nitpick comments (2)
.changeset/yellow-vans-walk.md (1)

5-5: Tighten wording and fix hyphenation in the changeset summary

Recommend using “cross-origin” and clarifying what changed to aid release notes skimming.

Apply:

-Fix logic for forcing a session sync on cross origin requests.
+Fix primary-domain cross-origin handshake logic by skipping the forced session sync when the referrer is a Clerk-owned domain (accounts.* or FAPI).
packages/backend/src/tokens/__tests__/request.test.ts (1)

1696-1769: Strengthen assertions to ensure we remain signed in (not just “not PrimaryDomainCrossOriginSync”)

A few tests only assert that the reason is not PrimaryDomainCrossOriginSync. This could pass even if a different handshake was triggered (e.g., DevBrowserMissing on test instances). Prefer asserting SignedIn when feasible.

Example tweak:

-      // Should not trigger the specific cross-origin sync handshake we're trying to prevent
-      expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
+      // Should remain signed in (no cross-origin sync handshake)
+      expect(requestState).toBeSignedIn({
+        domain: 'primary.com',
+        isSatellite: false,
+        signInUrl: 'https://primary.com/sign-in',
+      });

If you need to avoid dev-browser handshakes, either:

  • use publishableKey: PK_LIVE for those cases, or
  • include a dev browser token in cookies (__clerk_db_jwt) to satisfy the development precondition.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c3facf1 and ffde23d.

📒 Files selected for processing (4)
  • .changeset/yellow-vans-walk.md (1 hunks)
  • packages/backend/src/tokens/__tests__/request.test.ts (1 hunks)
  • packages/backend/src/tokens/authenticateContext.ts (2 hunks)
  • packages/backend/src/tokens/request.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
**/*

⚙️ CodeRabbit configuration file

If there are no tests added or modified as part of the PR, please suggest that tests be added to cover the changes.

Files:

  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/authenticateContext.ts
packages/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Unit tests should use Jest or Vitest as the test runner.

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/__tests__/**/*.{ts,tsx}: Create type-safe test builders/factories
Use branded types for test isolation
Implement proper mock types that match interfaces

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
.changeset/**

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Automated releases must use Changesets.

Files:

  • .changeset/yellow-vans-walk.md
🧬 Code graph analysis (2)
packages/backend/src/tokens/__tests__/request.test.ts (2)
packages/backend/src/tokens/request.ts (1)
  • authenticateRequest (147-805)
packages/backend/src/tokens/authStatus.ts (2)
  • AuthErrorReason (105-122)
  • AuthErrorReason (124-124)
packages/backend/src/tokens/authenticateContext.ts (1)
packages/shared/src/buildAccountsBaseUrl.ts (1)
  • buildAccountsBaseUrl (4-15)
🪛 LanguageTool
.changeset/yellow-vans-walk.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...a session sync on cross origin requests.

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (4)
packages/backend/src/tokens/request.ts (1)

575-581: Correct guard: avoid cross-origin handshake when coming from Clerk-owned referrers

The added !authenticateContext.isClerkDomain() check is the right constraint to prevent unnecessary handshakes when the navigation originated from Clerk-hosted UIs or FAPI. Logic order and predicates look good with the existing sec-fetch-dest === 'document' gate.

If you want to be extra safe, add one regression test to assert no handshake when:

  • primary domain request
  • referer = FAPI host for a proxied instance (PK with proxyUrl),
  • sec-fetch-site: cross-site,
  • sec-fetch-dest: document.
packages/backend/src/tokens/authenticateContext.ts (1)

1-2: New shared imports: confirm tree-shaking and side-effect-free modules

These imports look correct and align with how shared helpers are split. Just ensure @clerk/shared/url exports are side-effect free to keep backend bundle lean.

If size matters for your target, run a quick bundle-size check or confirm these modules don’t pull browser-only code.

packages/backend/src/tokens/__tests__/request.test.ts (2)

1609-1666: Good coverage: production and dev Accounts portals (current/legacy) skip handshake

These tests validate the intended behavior well for accounts.* origins. Nice.


1769-1798: Add a proxy-aware test case

Please add a case where the PK is proxied (set proxyUrl) and the referer is:

  • the unproxied FAPI host, and
  • the derived Accounts origin from that unproxied FAPI.

Both should not trigger the cross-origin sync handshake if you adopt the originalFrontendApi fix.

You can base it on the existing tests; set mockOptions({ proxyUrl: 'https://edge.acme.dev' }) and keep PK pointing to a test instance so the unproxied FAPI/Accounts are predictable from the PK.

Comment on lines +203 to +251
/**
* Determines if the referrer URL is from a Clerk domain (accounts portal or FAPI).
* This includes both development and production account portal domains, as well as FAPI domains
* used for redirect-based authentication flows.
*
* @returns {boolean} True if the referrer is from a Clerk accounts portal or FAPI domain, false otherwise
*/
public isClerkDomain(): boolean {
if (!this.referrer) {
return false;
}

try {
const referrerOrigin = new URL(this.referrer);
const referrerHost = referrerOrigin.hostname;

// Check if referrer is the FAPI domain itself (redirect-based auth flows)
if (this.frontendApi) {
const fapiHost = this.frontendApi.startsWith('http') ? new URL(this.frontendApi).hostname : this.frontendApi;
if (referrerHost === fapiHost) {
return true;
}
}

// Check for development account portal patterns
if (isLegacyDevAccountPortalOrigin(referrerHost) || isCurrentDevAccountPortalOrigin(referrerHost)) {
return true;
}

// Check for production account portal by comparing with expected accounts URL
const expectedAccountsUrl = buildAccountsBaseUrl(this.frontendApi);
if (expectedAccountsUrl) {
const expectedAccountsOrigin = new URL(expectedAccountsUrl).origin;
if (referrerOrigin.origin === expectedAccountsOrigin) {
return true;
}
}

// Check for generic production accounts patterns (accounts.*)
if (referrerHost.startsWith('accounts.')) {
return true;
}

return false;
} catch {
// Invalid URL format
return false;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use originalFrontendApi to improve correctness under proxies + document accounts. tradeoff*

Great addition. Two small improvements will make this rock-solid:

  • When checking the FAPI host and deriving the expected Accounts origin, prefer originalFrontendApi (unproxied) to avoid false negatives when frontendApi has been overridden via proxyUrl.
  • The generic accounts.* allowlist is intentional (tests rely on it), but it’s broad. Consider scoping it behind a flag or documenting that it’s a convenience shortcut that may treat non-Clerk “accounts.*” as Clerk-owned for the sole purpose of skipping the handshake (not auth).

Proposed diff:

   public isClerkDomain(): boolean {
     if (!this.referrer) {
       return false;
     }

     try {
       const referrerOrigin = new URL(this.referrer);
       const referrerHost = referrerOrigin.hostname;

-      // Check if referrer is the FAPI domain itself (redirect-based auth flows)
-      if (this.frontendApi) {
-        const fapiHost = this.frontendApi.startsWith('http') ? new URL(this.frontendApi).hostname : this.frontendApi;
+      // Check if referrer is the FAPI domain itself (redirect-based auth flows).
+      // Prefer the unproxied FAPI for correctness when proxyUrl overrides frontendApi.
+      const fapiSource = this.originalFrontendApi || this.frontendApi;
+      if (fapiSource) {
+        const fapiHost = fapiSource.startsWith('http') ? new URL(fapiSource).hostname : fapiSource;
         if (referrerHost === fapiHost) {
           return true;
         }
       }

       // Check for development account portal patterns
       if (isLegacyDevAccountPortalOrigin(referrerHost) || isCurrentDevAccountPortalOrigin(referrerHost)) {
         return true;
       }

-      // Check for production account portal by comparing with expected accounts URL
-      const expectedAccountsUrl = buildAccountsBaseUrl(this.frontendApi);
+      // Check for production account portal by comparing with expected Accounts URL derived from the unproxied FAPI
+      const expectedAccountsUrl = buildAccountsBaseUrl(this.originalFrontendApi || this.frontendApi);
       if (expectedAccountsUrl) {
         const expectedAccountsOrigin = new URL(expectedAccountsUrl).origin;
         if (referrerOrigin.origin === expectedAccountsOrigin) {
           return true;
         }
       }

       // Check for generic production accounts patterns (accounts.*)
       if (referrerHost.startsWith('accounts.')) {
         return true;
       }

       return false;
     } catch {
       // Invalid URL format
       return false;
     }
   }

Follow-up tests (optional but recommended):

  • Proxied PK scenario: publishableKey + proxyUrl set; referer is the unproxied FAPI or derived Accounts origin; assert no cross-origin handshake.
  • Negative control: a lookalike domain e.g. accounts-not.clerk.tld should not be treated as Clerk unless you explicitly keep the generic accounts.* rule.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Determines if the referrer URL is from a Clerk domain (accounts portal or FAPI).
* This includes both development and production account portal domains, as well as FAPI domains
* used for redirect-based authentication flows.
*
* @returns {boolean} True if the referrer is from a Clerk accounts portal or FAPI domain, false otherwise
*/
public isClerkDomain(): boolean {
if (!this.referrer) {
return false;
}
try {
const referrerOrigin = new URL(this.referrer);
const referrerHost = referrerOrigin.hostname;
// Check if referrer is the FAPI domain itself (redirect-based auth flows)
if (this.frontendApi) {
const fapiHost = this.frontendApi.startsWith('http') ? new URL(this.frontendApi).hostname : this.frontendApi;
if (referrerHost === fapiHost) {
return true;
}
}
// Check for development account portal patterns
if (isLegacyDevAccountPortalOrigin(referrerHost) || isCurrentDevAccountPortalOrigin(referrerHost)) {
return true;
}
// Check for production account portal by comparing with expected accounts URL
const expectedAccountsUrl = buildAccountsBaseUrl(this.frontendApi);
if (expectedAccountsUrl) {
const expectedAccountsOrigin = new URL(expectedAccountsUrl).origin;
if (referrerOrigin.origin === expectedAccountsOrigin) {
return true;
}
}
// Check for generic production accounts patterns (accounts.*)
if (referrerHost.startsWith('accounts.')) {
return true;
}
return false;
} catch {
// Invalid URL format
return false;
}
}
/**
* Determines if the referrer URL is from a Clerk domain (accounts portal or FAPI).
* This includes both development and production account portal domains, as well as FAPI domains
* used for redirect-based authentication flows.
*
* @returns {boolean} True if the referrer is from a Clerk accounts portal or FAPI domain, false otherwise
*/
public isClerkDomain(): boolean {
if (!this.referrer) {
return false;
}
try {
const referrerOrigin = new URL(this.referrer);
const referrerHost = referrerOrigin.hostname;
// Check if referrer is the FAPI domain itself (redirect-based auth flows).
// Prefer the unproxied FAPI for correctness when proxyUrl overrides frontendApi.
const fapiSource = this.originalFrontendApi || this.frontendApi;
if (fapiSource) {
const fapiHost = fapiSource.startsWith('http')
? new URL(fapiSource).hostname
: fapiSource;
if (referrerHost === fapiHost) {
return true;
}
}
// Check for development account portal patterns
if (
isLegacyDevAccountPortalOrigin(referrerHost) ||
isCurrentDevAccountPortalOrigin(referrerHost)
) {
return true;
}
// Check for production account portal by comparing with expected Accounts URL derived from the unproxied FAPI
const expectedAccountsUrl = buildAccountsBaseUrl(
this.originalFrontendApi || this.frontendApi
);
if (expectedAccountsUrl) {
const expectedAccountsOrigin = new URL(expectedAccountsUrl).origin;
if (referrerOrigin.origin === expectedAccountsOrigin) {
return true;
}
}
// Check for generic production accounts patterns (accounts.*)
if (referrerHost.startsWith('accounts.')) {
return true;
}
return false;
} catch {
// Invalid URL format
return false;
}
}

@brkalow brkalow closed this Aug 21, 2025
@brkalow brkalow reopened this Aug 21, 2025
Copy link

pkg-pr-new bot commented Aug 21, 2025

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@6600

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@6600

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@6600

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@6600

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@6600

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@6600

@clerk/elements

npm i https://pkg.pr.new/@clerk/elements@6600

@clerk/clerk-expo

npm i https://pkg.pr.new/@clerk/clerk-expo@6600

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@6600

@clerk/express

npm i https://pkg.pr.new/@clerk/express@6600

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@6600

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@6600

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@6600

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@6600

@clerk/clerk-react

npm i https://pkg.pr.new/@clerk/clerk-react@6600

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@6600

@clerk/remix

npm i https://pkg.pr.new/@clerk/remix@6600

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@6600

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@6600

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@6600

@clerk/themes

npm i https://pkg.pr.new/@clerk/themes@6600

@clerk/types

npm i https://pkg.pr.new/@clerk/types@6600

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@6600

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@6600

commit: 1c7deff

Copy link
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: 0

♻️ Duplicate comments (2)
packages/backend/src/tokens/__tests__/request.test.ts (2)

1609-1636: Avoid relying on permissive accounts.* fallback in this test; bind to the instance’s real accounts origin or make intent explicit.

This case passes only because production code currently treats any accounts.* host as a Clerk domain. That makes the test brittle and potentially masks over-broad matching. Prefer asserting with the PK-derived accounts origin used in this test suite (or clearly label this as a fallback behavior test).

Two options:

Option A (preferred): use the PK-derived accounts origin to decouple from the global accounts.* fallback.

-    test('does not trigger handshake when referer is from production accounts portal', async () => {
+    test('does not trigger handshake when referer is from the instance accounts portal (derived from PK)', async () => {
@@
-          referer: 'https://accounts.example.com/sign-in',
+          referer: 'https://accounts.inspired.puma-74.lcl.dev/sign-in',
@@
-        publishableKey: PK_LIVE,
+        // Keep the publishable key aligned with the derived dev host above
+        publishableKey: PK_TEST,

Option B: if you intentionally want to document the permissive behavior, rename the test to make that explicit.

-    test('does not trigger handshake when referer is from production accounts portal', async () => {
+    test('does not trigger handshake when referer is an accounts.* fallback domain (documents current behavior)', async () => {

Would you like me to follow through and propagate Option A across the suite, replacing any remaining fallback-shaped cases with PK-derived hosts?


1609-1830: Add a negative test for unrelated accounts.* domains (if you plan to tighten matching).

To prevent over-broad whitelisting, add a case where the referer is an unrelated accounts.* domain and assert that PrimaryDomainCrossOriginSync is triggered. This will fail today if production intentionally treats any accounts.* as a Clerk domain; keep it for a follow-up when isClerkDomain is tightened, or mark it todo until then.

@@   describe('Cross-origin sync', () => {
+    test('triggers handshake when referer is unrelated accounts.* domain', async () => {
+      const request = mockRequestWithCookies(
+        {
+          referer: 'https://accounts.attacker.com/sign-in',
+          'sec-fetch-dest': 'document',
+          'sec-fetch-site': 'cross-site',
+        },
+        {
+          __session: mockJwt,
+          __client_uat: '12345',
+        },
+        'https://primary.com/dashboard',
+      );
+
+      const requestState = await authenticateRequest(request, {
+        ...mockOptions(),
+        publishableKey: PK_LIVE,
+        domain: 'primary.com',
+        isSatellite: false,
+        signInUrl: 'https://primary.com/sign-in',
+      });
+
+      expect(requestState).toMatchHandshake({
+        reason: AuthErrorReason.PrimaryDomainCrossOriginSync,
+        domain: 'primary.com',
+        signInUrl: 'https://primary.com/sign-in',
+      });
+    });

If you want, I can open a follow-up to scope isClerkDomain() to PK-derived hosts + vetted allowlist and land this test together with that change.

🧹 Nitpick comments (4)
packages/backend/src/tokens/__tests__/request.test.ts (4)

1717-1719: Strengthen the assertion: assert positive SignedIn state rather than “not a specific reason”.

Checking only that the reason is not PrimaryDomainCrossOriginSync could pass even if another handshake reason fires. Assert the successful path.

-      // Should not trigger the specific cross-origin sync handshake we're trying to prevent
-      expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
+      expect(requestState).toBeSignedIn({
+        domain: 'primary.com',
+        isSatellite: false,
+        signInUrl: 'https://primary.com/sign-in',
+      });
+      expect(requestState.toAuth()).toBeSignedInToAuth();

1721-1744: Apply the same stronger assertion pattern here (FAPI – redirect-based auth).

Ensure we validate the intended SignedIn outcome, not just absence of one specific reason.

-      // Should not trigger the specific cross-origin sync handshake we're trying to prevent
-      expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
+      expect(requestState).toBeSignedIn({
+        domain: 'primary.com',
+        isSatellite: false,
+        signInUrl: 'https://primary.com/sign-in',
+      });
+      expect(requestState.toAuth()).toBeSignedInToAuth();

1746-1769: Same here: assert SignedIn result for FAPI “https prefix” variant.

-      // Should not trigger the specific cross-origin sync handshake we're trying to prevent
-      expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
+      expect(requestState).toBeSignedIn({
+        domain: 'primary.com',
+        isSatellite: false,
+        signInUrl: 'https://primary.com/sign-in',
+      });
+      expect(requestState.toAuth()).toBeSignedInToAuth();

1609-1830: Reduce duplication with a small helper for “primary doc request with cookies”.

Many tests clone the same inputs (cookies, target URL, options). A tiny factory improves readability and maintenance:

// Place near other test helpers
const buildPrimaryDocRequest = (referer: string, extraHeaders: Record<string, string> = {}) =>
  mockRequestWithCookies(
    { referer, 'sec-fetch-dest': 'document', ...extraHeaders },
    { __session: mockJwt, __client_uat: '12345' },
    'https://primary.com/dashboard',
  );

const primaryOptions = () =>
  mockOptions({
    publishableKey: PK_LIVE,
    domain: 'primary.com',
    isSatellite: false,
    signInUrl: 'https://primary.com/sign-in',
  });

// Usage in tests:
// const request = buildPrimaryDocRequest('https://accounts.inspired.puma-74.lcl.dev/sign-in', { 'sec-fetch-site': 'cross-site' });
// const requestState = await authenticateRequest(request, primaryOptions());
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ffde23d and 376b4e4.

📒 Files selected for processing (2)
  • packages/backend/src/tokens/__tests__/request.test.ts (1 hunks)
  • packages/backend/src/tokens/authenticateContext.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/backend/src/tokens/authenticateContext.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
packages/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Unit tests should use Jest or Vitest as the test runner.

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/__tests__/**/*.{ts,tsx}: Create type-safe test builders/factories
Use branded types for test isolation
Implement proper mock types that match interfaces

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/*

⚙️ CodeRabbit configuration file

If there are no tests added or modified as part of the PR, please suggest that tests be added to cover the changes.

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
🧬 Code graph analysis (1)
packages/backend/src/tokens/__tests__/request.test.ts (2)
packages/backend/src/tokens/request.ts (1)
  • authenticateRequest (147-805)
packages/backend/src/tokens/authStatus.ts (2)
  • AuthErrorReason (105-122)
  • AuthErrorReason (124-124)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
packages/backend/src/tokens/__tests__/request.test.ts (3)

1638-1694: LGTM: solid coverage for dev accounts portals (current and legacy).

These two tests exercise both foo-bar-13.accounts.dev and accounts.foo-bar-13.lcl.dev patterns and correctly assert no handshake.


1771-1798: LGTM: verifies handshake still fires for non-accounts cross-origin referers.

Good guardrail ensuring we didn’t suppress legitimate cross-origin syncs.


1800-1829: LGTM: covers redirect-chain quirk with sec-fetch-site: cross-site but same-origin referrer.

Nice edge case; prevents accidental over-triggering of handshakes.

Copy link
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: 0

♻️ Duplicate comments (3)
packages/backend/src/tokens/__tests__/request.test.ts (3)

1634-1662: Avoid relying on permissive “accounts.*” fallback; use a PK-derived accounts origin (or parameterize).

This test passes today because production code accepts any “accounts.*” host. If matching is tightened later, it could fail. Prefer using the accounts origin derived from the publishable key used in these tests (same host you use elsewhere), or parameterize against a helper.

Minimal change (align with the PK-derived host already used elsewhere in this file):

-    test('does not trigger handshake when referer is from production accounts portal', async () => {
+    test('does not trigger handshake when referer is from accounts portal derived from PK', async () => {
@@
-          referer: 'https://accounts.example.com/sign-in',
+          referer: 'https://accounts.inspired.puma-74.lcl.dev/sign-in',

Alternative: derive the referer from the same source the app uses (e.g., buildAccountsBaseUrl(frontendApi) seeded by the PK) to remove hardcoding altogether. I can help wire that into the test if you prefer.


1692-1719: Same as previous: fold legacy/current dev portal cases into one parameterized test.

This block can be consolidated with the one above to avoid duplication and ease adding new domains in the future.


1796-1823: Add a negative test: “accounts.attacker.com” must still trigger handshake.

To guard against over-broad whitelisting of accounts.* domains, add a case where the referer is an unrelated accounts.* host and assert PrimaryDomainCrossOriginSync is triggered.

@@
     test('still triggers handshake for legitimate cross-origin requests from non-accounts domains', async () => {
@@
     });
+
+    test('triggers handshake when referer is unrelated accounts.* domain', async () => {
+      const request = mockRequestWithCookies(
+        {
+          referer: 'https://accounts.attacker.com/sign-in',
+          'sec-fetch-dest': 'document',
+          'sec-fetch-site': 'cross-site',
+        },
+        {
+          __session: mockJwt,
+          __client_uat: '12345',
+        },
+        'https://primary.com/dashboard',
+      );
+
+      const requestState = await authenticateRequest(request, {
+        ...mockOptions(),
+        publishableKey: PK_LIVE,
+        domain: 'primary.com',
+        isSatellite: false,
+        signInUrl: 'https://primary.com/sign-in',
+      });
+
+      expect(requestState).toMatchHandshake({
+        reason: AuthErrorReason.PrimaryDomainCrossOriginSync,
+        domain: 'primary.com',
+        signInUrl: 'https://primary.com/sign-in',
+      });
+    });
🧹 Nitpick comments (5)
packages/backend/src/tokens/__tests__/request.test.ts (5)

1523-1546: Rename duplicate test title and also assert toAuth for stronger coverage.

There is already a prior test named “does not trigger handshake when referer is same origin.” Rename this one to avoid duplicate names and add a toAuth assertion so we also validate the auth payload is populated.

-    test('does not trigger handshake when referer is same origin', async () => {
+    test('does not trigger handshake when referer is same origin (localhost dev)', async () => {
@@
       expect(requestState).toBeSignedIn({
         signInUrl: 'http://localhost:3000/sign-in',
       });
+      expect(requestState.toAuth()).toBeSignedInToAuth();
     });

1663-1691: Reduce duplication with a table-driven test for dev accounts portal variants.

These two dev-accounts cases are nearly identical and can be collapsed into it.each for readability and future maintainability.

Example:

it.each([
  { name: 'current format', ref: 'https://foo-bar-13.accounts.dev/sign-in' },
  { name: 'legacy format', ref: 'https://accounts.foo-bar-13.lcl.dev/sign-in' },
])('does not trigger handshake when referer is from dev accounts portal (%s)', async ({ name, ref }) => {
  const request = mockRequestWithCookies(
    { referer: ref, 'sec-fetch-dest': 'document', 'sec-fetch-site': 'cross-site' },
    { __session: mockJwt, __client_uat: '12345' },
    'https://primary.com/dashboard',
  );
  const requestState = await authenticateRequest(request, {
    ...mockOptions(),
    publishableKey: PK_LIVE,
    domain: 'primary.com',
    isSatellite: false,
    signInUrl: 'https://primary.com/sign-in',
  });
  expect(requestState).toBeSignedIn({
    domain: 'primary.com',
    isSatellite: false,
    signInUrl: 'https://primary.com/sign-in',
  });
});

1721-1744: Strengthen the assertion: verify we’re actually signed in (not just “not this specific handshake”).

Only asserting that the reason is not PrimaryDomainCrossOriginSync leaves room for other unexpected states. Also assert SignedIn and toAuth to make the intent explicit.

-      // Should not trigger the specific cross-origin sync handshake we're trying to prevent
-      expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
+      // Should not trigger cross-origin sync; request should be fully authenticated
+      expect(requestState).not.toMatchHandshake({ reason: AuthErrorReason.PrimaryDomainCrossOriginSync });
+      expect(requestState).toBeSignedIn();
+      expect(requestState.toAuth()).toBeSignedInToAuth();

1746-1769: Apply the same stronger assertion for FAPI referers.

Validate SignedIn and toAuth in addition to the negative handshake assertion.

-      // Should not trigger the specific cross-origin sync handshake we're trying to prevent
-      expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
+      expect(requestState).not.toMatchHandshake({ reason: AuthErrorReason.PrimaryDomainCrossOriginSync });
+      expect(requestState).toBeSignedIn();
+      expect(requestState.toAuth()).toBeSignedInToAuth();

1771-1794: Same strengthening: assert SignedIn + toAuth for the https-prefixed FAPI case.

-      // Should not trigger the specific cross-origin sync handshake we're trying to prevent
-      expect(requestState.reason).not.toBe(AuthErrorReason.PrimaryDomainCrossOriginSync);
+      expect(requestState).not.toMatchHandshake({ reason: AuthErrorReason.PrimaryDomainCrossOriginSync });
+      expect(requestState).toBeSignedIn();
+      expect(requestState.toAuth()).toBeSignedInToAuth();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 376b4e4 and 1c7deff.

📒 Files selected for processing (1)
  • packages/backend/src/tokens/__tests__/request.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
packages/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Unit tests should use Jest or Vitest as the test runner.

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/__tests__/**/*.{ts,tsx}: Create type-safe test builders/factories
Use branded types for test isolation
Implement proper mock types that match interfaces

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
**/*

⚙️ CodeRabbit configuration file

If there are no tests added or modified as part of the PR, please suggest that tests be added to cover the changes.

Files:

  • packages/backend/src/tokens/__tests__/request.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1825-1854: Nice edge-case coverage: same-origin referrer despite sec-fetch-site=cross-site.

This captures the redirect-chain quirk well and protects against regressions in the gating logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants