Skip to content

fix: allow the CSP nonce of app.html scripts when a page has csr = false - #17437

Open
xyrolle wants to merge 4 commits into
sveltejs:mainfrom
xyrolle:fix/csp-nonce-without-csr
Open

xyrolle wants to merge 4 commits into
sveltejs:mainfrom
xyrolle:fix/csp-nonce-without-csr

Conversation

@xyrolle

@xyrolle xyrolle commented Oct 14, 2026 •

Copy link
Copy Markdown

The docs say %sveltekit.nonce% in app.html is "a CSP nonce for manually included links and scripts". With the default csp.mode (auto) or nonce, a page with export const csr = false gets the nonce in its HTML, but the content-security-policy header doesn't list it. So under a restrictive script policy such as script-src 'self', a <script nonce="%sveltekit.nonce%"> in app.html is blocked.

The nonce only gets into script-src as a side effect of adding SvelteKit's own inline script: render_response calls csp.add_script(init_app), which adds 'nonce-…' in nonce mode, and that only happens when csr is on.

This adds the nonce to the script directives when Csp is created for a template that contains %sveltekit.nonce% (the existing app_template_contains_nonce option) and the policy uses nonces, whether or not the page adds a script of its own. It goes through the providers' add_script, so it lands where SvelteKit's own nonce would: script-src and/or script-src-elem if they need one, and not when 'unsafe-inline' makes a nonce unnecessary. Hash mode is unchanged: there the template's nonce isn't in the header with or without csr, and a prerendered page with %sveltekit.nonce% still fails the build as before.

Reproduction

// vite.config.js
sveltekit({
	csp: { mode: 'nonce', directives: { 'script-src': ['self'] } }
});
<!-- src/app.html -->
<head>
	<meta charset="utf-8" />
	<script nonce="%sveltekit.nonce%">
		document.documentElement.dataset.inlineScriptRan = 'yes';
	</script>
	%sveltekit.head%
</head>
// src/routes/csr-off/+page.js
export const csr = false;

/csr-on (any other page) and /csr-off, production build served with vite preview, Chromium 153:

content-security-policy app.html script runs
Kit 3.0.1, /csr-on script-src 'self' 'nonce-…' yes
Kit 3.0.1, /csr-off script-src 'self' no: "Executing inline script violates the following Content Security Policy directive 'script-src 'self''"
Kit 3.0.1 patched with this PR, /csr-off, mode: 'nonce' or 'auto' script-src 'self' 'nonce-…' yes
Kit 3.0.1, and patched, mode: 'hash' the same on both no, on both pages, with and without the patch

Tests

  • csp.spec.js: the template nonce is added to script-src in nonce and auto mode, in the policy and the report-only policy, without any script of the page's own; it isn't added when the template has no %sveltekit.nonce%; and it isn't added in hash mode. The first fails without the change to csp.js, the second fails if the new code ignores whether the template has the placeholder.
  • test/apps/no-csr (the existing app whose pages all have csr = false, with script-src 'self'): its template now has a script with %sveltekit.nonce% that sets an attribute on <html>, and a test with JavaScript enabled checks that the script ran. Without the change to csp.js, the browser blocks it and the test fails. The app's other tests still run only without JavaScript, as before.

With main (d955d1b) merged in, which now runs the CSPs in prod block (#17436), pnpm lint, pnpm check and pnpm test:kit:unit pass (75 files, 1118 passed, 1 skipped), and so does the no-csr app's build suite (test:build: 5 passed, 5 skipped). I didn't run the other integration suites. The table comes from the reproduction above, run against Kit 3.0.1 and against Kit 3.0.1 patched with this change.


Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

@pkg-svelte-dev

pkg-svelte-dev Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Install the latest version of @sveltejs/kit from 0cbe5c4:

pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/0cbe5c4ec4430b016b01351e04b9d21dde1055fd

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/kit/pr/17437

Note

This PR is from a fork. A maintainer must approve approve each commit before it can be built and installed.

@changeset-bot

changeset-bot Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0cbe5c4

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

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit 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

@vercel

vercel Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

@xyrolle is attempting to deploy a commit to the vercelliott Team on Vercel.

A member of the Team first needs to authorize it.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant