Skip to content

Clerk runtime inline scripts break strict CSP on SvelteKit #414

Description

@mongeaumichel

Description

When using svelte-clerk (or any non-Next.js Clerk integration) with a nonce-based Content Security Policy, clerk.browser.js creates inline <script> elements at runtime that don't carry the nonce attribute. These scripts are blocked by the browser, preventing Clerk from initializing.

Setup:

SvelteKit app with svelte-clerk v1.1.0 (@clerk/shared v4.3.0)
Nonce generated per request, passed to
CSP header: script-src 'nonce-{uuid}' 'strict-dynamic'

What works:

The initial clerk.browser.js and ui.browser.js script tags load correctly — the nonce prop is applied via loadClerkJSScript in @clerk/shared
What breaks:

After clerk.browser.js loads, it creates inline <script> elements at runtime (visible at (index):60 and (index):61 in the browser)
These inline scripts don't have the nonce attribute
strict-dynamic only propagates trust to dynamically-loaded scripts with src attributes, not inline scripts (per CSP spec)
Result: Clerk fails to initialize, auth state is unavailable on the client
Browser console output:
Executing inline script violates the following Content Security Policy directive 'script-src 'nonce-xxx' 'strict-dynamic''. Either the 'unsafe-inline' keyword, a hash, or a nonce is required to enable inline execution. The action has been blocked.

Expected behavior:

clerk.browser.js should read the nonce from its own <script> element (via document.currentScript.nonce or the nonce attribute set by @clerk/shared) and apply it to any inline <script> elements it creates at runtime. This would make nonce-based strict CSP work across all frameworks, not just Next.js.

Current workaround:

Using script-src 'self' 'unsafe-inline' https://{fapi-domain} — which defeats the purpose of nonce-based CSP and is flagged in enterprise security audits (SOC 2, PCI DSS).

Context:

The Next.js integration solves this via the dynamic prop on ClerkProvider and automatic nonce threading through clerkMiddleware. Non-Next.js frameworks (SvelteKit, Remix, Nuxt, etc.) don't have this plumbing and rely on manually passing the nonce prop, which only reaches the initial script load — not the runtime inline scripts created by clerk.browser.js.

Suggested fix:

In packages/clerk-js, when creating inline script elements at runtime, propagate the nonce from the parent script element. The nonce is already available on the <script> tag that loaded clerk.browser.js.

Environment

System:
  OS: Linux 6.6 Ubuntu 24.04.3 LTS 24.04.3 LTS (Noble Numbat)
  CPU: (16) x64 AMD Ryzen 7 9800X3D 8-Core Processor
  Memory: 24.08 GB / 30.14 GB
  Container: Yes
  Shell: 5.2.21 - /bin/bash
Binaries:
  Node: 22.22.0 - /home/monge/.nvm/versions/node/v22.22.0/bin/node
  npm: 10.9.4 - /home/monge/.nvm/versions/node/v22.22.0/bin/npm
Browsers:
  Chromium: 146.0.7680.80
npmPackages:
  @sveltejs/adapter-auto: ^7.0.1 => 7.0.1
  @sveltejs/kit: ^2.55.0 => 2.55.0
  @sveltejs/vite-plugin-svelte: ^7.0.0 => 7.0.0
  svelte: ^5.55.0 => 5.55.0
  svelte-clerk: ^1.1.1 => 1.1.1
  typescript: ^5.9.3 => 5.9.3
  vite: ^8.0.2 => 8.0.2

Reproduction

https://github.com/mongeaumichel/sveltekit-clerk

Publishable key

pk_test_YmVjb21pbmctd2FsbGV5ZS03My5jbGVyay5hY2NvdW50cy5kZXYk

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions