From e14499a3259f352142986ac0f4ff1df757ce8005 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Wed, 23 Sep 2026 17:54:51 +0100 Subject: [PATCH 1/2] fix(nextjs): remove internal staging host from the default connect-src --- .changeset/olive-rings-repeat.md | 5 +++++ .../__tests__/content-security-policy.test.ts | 18 ++++++++++++------ .../src/server/content-security-policy.ts | 1 - 3 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 .changeset/olive-rings-repeat.md diff --git a/.changeset/olive-rings-repeat.md b/.changeset/olive-rings-repeat.md new file mode 100644 index 00000000000..891a7760cba --- /dev/null +++ b/.changeset/olive-rings-repeat.md @@ -0,0 +1,5 @@ +--- +'@clerk/nextjs': patch +--- + +Remove `https://images.clerkstage.dev` from the default `connect-src` Content Security Policy directive. It is a Clerk-internal storage host that no application connects to. Organization logos are served from `https://img.clerk.com`, which stays in the default. diff --git a/packages/nextjs/src/server/__tests__/content-security-policy.test.ts b/packages/nextjs/src/server/__tests__/content-security-policy.test.ts index 6952292cf76..e284218c84e 100644 --- a/packages/nextjs/src/server/__tests__/content-security-policy.test.ts +++ b/packages/nextjs/src/server/__tests__/content-security-policy.test.ts @@ -31,7 +31,7 @@ describe('CSP Header Utils', () => { expect(directives).toContainEqual("default-src 'self'"); expect(directives).toContainEqual( - "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", + "connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://*.protect.clerk.com:* clerk.example.com", ); expect(directives).toContainEqual("form-action 'self'"); expect(directives).toContainEqual( @@ -116,7 +116,7 @@ describe('CSP Header Utils', () => { const directives = headerValue.split('; '); expect(directives).toContainEqual("default-src 'self'"); expect(directives).toContainEqual( - "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", + "connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://*.protect.clerk.com:* clerk.example.com", ); expect(directives).toContainEqual("form-action 'self'"); expect(directives).toContainEqual( @@ -265,7 +265,7 @@ describe('CSP Header Utils', () => { const directives = result.headers[0][1].split('; '); expect(directives).toContainEqual( - `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`, + `connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://*.protect.clerk.com:* clerk.example.com https://api.example.com`, ); const imgSrcDirective = directives.find(d => d.startsWith('img-src')) || ''; @@ -285,7 +285,7 @@ describe('CSP Header Utils', () => { const directives = result.headers[0][1].split('; '); expect(directives).toContainEqual( - "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", + "connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://*.protect.clerk.com:* clerk.example.com", ); expect(directives).toContainEqual("default-src 'self'"); expect(directives).toContainEqual("form-action 'self'"); @@ -331,7 +331,7 @@ describe('CSP Header Utils', () => { const directives = result.headers[0][1].split('; '); expect(directives).toContainEqual( - `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`, + `connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://*.protect.clerk.com:* clerk.example.com`, ); expect(directives).toContainEqual(`img-src 'self' https://img.clerk.com`); expect(directives).toContainEqual( @@ -397,7 +397,7 @@ describe('CSP Header Utils', () => { const directives = result.headers[0][1].split('; '); expect(directives).toContainEqual( - "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", + "connect-src 'self' https://clerk-telemetry.com https://*.clerk-telemetry.com https://api.stripe.com https://maps.googleapis.com https://img.clerk.com https://*.protect.clerk.com:* clerk.example.com", ); expect(directives).toContainEqual("default-src 'self'"); expect(directives).toContainEqual("form-action 'self'"); @@ -450,5 +450,11 @@ describe('CSP Header Utils', () => { expect(manifestSrcDirective).toContain('value3'); expect(manifestSrcDirective).toContain('value4'); }); + + it('should not allow any Clerk staging or local development host by default', () => { + const result = createContentSecurityPolicyHeaders(testHost, {}); + + expect(result.headers[0][1]).not.toMatch(/clerkstage\.dev|lclclerk\.com|accountsstage\.dev/); + }); }); }); diff --git a/packages/nextjs/src/server/content-security-policy.ts b/packages/nextjs/src/server/content-security-policy.ts index 88b120b2caf..82363698d07 100644 --- a/packages/nextjs/src/server/content-security-policy.ts +++ b/packages/nextjs/src/server/content-security-policy.ts @@ -106,7 +106,6 @@ class ContentSecurityPolicyDirectiveManager { 'https://api.stripe.com', 'https://maps.googleapis.com', 'https://img.clerk.com', - 'https://images.clerkstage.dev', clerkProtectionConnectOrigin, ], 'default-src': ['self'], From 19a8643ec6bbd4d98ac02f47e0c4d35120a09cb9 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Thu, 1 Oct 2026 21:35:23 +0100 Subject: [PATCH 2/2] test(nextjs): drop redundant staging-host assertion --- .../src/server/__tests__/content-security-policy.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/nextjs/src/server/__tests__/content-security-policy.test.ts b/packages/nextjs/src/server/__tests__/content-security-policy.test.ts index e284218c84e..e4a23504f35 100644 --- a/packages/nextjs/src/server/__tests__/content-security-policy.test.ts +++ b/packages/nextjs/src/server/__tests__/content-security-policy.test.ts @@ -450,11 +450,5 @@ describe('CSP Header Utils', () => { expect(manifestSrcDirective).toContain('value3'); expect(manifestSrcDirective).toContain('value4'); }); - - it('should not allow any Clerk staging or local development host by default', () => { - const result = createContentSecurityPolicyHeaders(testHost, {}); - - expect(result.headers[0][1]).not.toMatch(/clerkstage\.dev|lclclerk\.com|accountsstage\.dev/); - }); }); });