Skip to content

Commit 75ec072

Browse files
authored
fix(nextjs): allow Clerk protection hosts on all ports in connect-src (#9458)
1 parent 2c56a9b commit 75ec072

3 files changed

Lines changed: 21 additions & 9 deletions

File tree

.changeset/dull-donkeys-warn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/nextjs': patch
3+
---
4+
5+
Allow Clerk's abuse and fraud protection hosts on all ports in the generated `connect-src` directive. The `contentSecurityPolicy` option previously emitted `https://*.protect.clerk.com`, which matches port 443 only, so requests to those hosts on other ports were blocked by the resulting policy.

packages/nextjs/src/server/__tests__/content-security-policy.test.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('CSP Header Utils', () => {
3131

3232
expect(directives).toContainEqual("default-src 'self'");
3333
expect(directives).toContainEqual(
34-
"connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com clerk.example.com",
34+
"connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com:* clerk.example.com",
3535
);
3636
expect(directives).toContainEqual("form-action 'self'");
3737
expect(directives).toContainEqual(
@@ -83,9 +83,14 @@ describe('CSP Header Utils', () => {
8383
const result = createContentSecurityPolicyHeaders(testHost, { strict });
8484
const directives = result.headers[0][1].split('; ');
8585

86-
for (const directiveName of ['script-src', 'connect-src', 'frame-src']) {
86+
// connect-src carries the port wildcard: those hosts are also requested on non-443 ports.
87+
for (const [directiveName, expectedSource] of [
88+
['script-src', 'https://*.protect.clerk.com'],
89+
['connect-src', 'https://*.protect.clerk.com:*'],
90+
['frame-src', 'https://*.protect.clerk.com'],
91+
]) {
8792
const directiveSources = directives.find(d => d.startsWith(directiveName))?.split(' ');
88-
expect(directiveSources).toContain('https://*.protect.clerk.com');
93+
expect(directiveSources).toContain(expectedSource);
8994
expect(directiveSources).not.toContain('https://*.clerk.com');
9095
expect(directiveSources).not.toContain('https://*.client.protect.clerk.com');
9196
}
@@ -111,7 +116,7 @@ describe('CSP Header Utils', () => {
111116
const directives = headerValue.split('; ');
112117
expect(directives).toContainEqual("default-src 'self'");
113118
expect(directives).toContainEqual(
114-
"connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com clerk.example.com",
119+
"connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com:* clerk.example.com",
115120
);
116121
expect(directives).toContainEqual("form-action 'self'");
117122
expect(directives).toContainEqual(
@@ -260,7 +265,7 @@ describe('CSP Header Utils', () => {
260265

261266
const directives = result.headers[0][1].split('; ');
262267
expect(directives).toContainEqual(
263-
`connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com clerk.example.com https://api.example.com`,
268+
`connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com:* clerk.example.com https://api.example.com`,
264269
);
265270

266271
const imgSrcDirective = directives.find(d => d.startsWith('img-src')) || '';
@@ -280,7 +285,7 @@ describe('CSP Header Utils', () => {
280285
const directives = result.headers[0][1].split('; ');
281286

282287
expect(directives).toContainEqual(
283-
"connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com clerk.example.com",
288+
"connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com:* clerk.example.com",
284289
);
285290
expect(directives).toContainEqual("default-src 'self'");
286291
expect(directives).toContainEqual("form-action 'self'");
@@ -326,7 +331,7 @@ describe('CSP Header Utils', () => {
326331
const directives = result.headers[0][1].split('; ');
327332

328333
expect(directives).toContainEqual(
329-
`connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com clerk.example.com`,
334+
`connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com:* clerk.example.com`,
330335
);
331336
expect(directives).toContainEqual(`img-src 'self' https://img.clerk.com`);
332337
expect(directives).toContainEqual(
@@ -392,7 +397,7 @@ describe('CSP Header Utils', () => {
392397
const directives = result.headers[0][1].split('; ');
393398

394399
expect(directives).toContainEqual(
395-
"connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com clerk.example.com",
400+
"connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://images.clerkstage.dev https://*.protect.clerk.com:* clerk.example.com",
396401
);
397402
expect(directives).toContainEqual("default-src 'self'");
398403
expect(directives).toContainEqual("form-action 'self'");

packages/nextjs/src/server/content-security-policy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { constants } from '@clerk/backend/internal';
22

33
const clerkProtectionOrigin = 'https://*.protect.clerk.com';
4+
// A source with no port matches port 443 only, and these hosts are also requested on other ports.
5+
const clerkProtectionConnectOrigin = `${clerkProtectionOrigin}:*`;
46

57
/**
68
* Valid CSP directives according to the CSP Level 3 specification
@@ -105,7 +107,7 @@ class ContentSecurityPolicyDirectiveManager {
105107
'https://maps.googleapis.com',
106108
'https://img.clerk.com',
107109
'https://images.clerkstage.dev',
108-
clerkProtectionOrigin,
110+
clerkProtectionConnectOrigin,
109111
],
110112
'default-src': ['self'],
111113
'form-action': ['self'],

0 commit comments

Comments
 (0)