-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
[feat] add CSP nonces to script
/style
tags
#2394
Conversation
🦋 Changeset detectedLatest commit: cf26271 The changes in this PR will be included in the next version bump. This PR includes changesets to release 5 packages
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 |
Hmm, looking at the tests I may have overestimated support for nodeJS builtins in serverless environments. We might get somewhere using the Web Crypto API instead? I know Cloudflare supports it, though that's only experimental in Node (and only in node 16). Perhaps this can be injected somehow from the adapters? So If there is no reasonable way to generate a random value across platforms then we might just have to leave that up to the developer. An alternative then is to take in the nonce as |
I like this a lot more than the placeholder approach. Is it allowed to reuse nonce values for multiple scripts or should each one have a separate one? Would it also be posssible to calculate hashes instead of just a random nonce? Edit: on randomness... is the network io guaranteeing enough entropy for randomBytes(32) ? |
We could probably calculate hashes, but that requires data flow in the other direction instead (or we're back to regexes). I'm not sure why you want hashes for this? SSR like this is basically the poster child for CSP nonces. Using the same nonce for every resource on a single page request is (as far as I can tell) accepted practice. There shouldn't be any security degradation from that (still impossible to guess, and all nonces after initial page load are disregarded anyway), and it avoids some unpleasant complexity for us since we don't need to know ahead of time how many nonces to generate. The randomness is a problem in general (see my above comment on platform support). But the current version (which only works on Node) uses Node's |
My concern in regards to random was that in a scenario with a high request volume and low entropy on the server, random might become blocking/adding to the response time https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback The advantage of hashes is that they can be precalculated on build time (for anything non-dynamic at least) and reused from a manifest file, so less work at runtime and no need for a source of randomness. |
I see your concern now, thank you for elaborating. I don't know? I haven't measured response times, but I kind of consider hashing a separate feature to be honest, though I absolutely support it. As discussed in #93 some parts of the init script is unique to the request under SSR so hashing can't be done ahead-of-time for dynamic environments. However, it would be a nice inclusion for static environments or on a per-request basis if entropy is indeed a problem. I imagine hashing within the render and then providing the hash value as Could we require (or encourage) adapters to shim a |
Marked as draft because of aforementioned platform issues. |
crazy idea, feel free to put me on blast for this. What if you did extract all inline items into virtual files served from memory <body>
<script>
alert('inline');
</script>
</body> would become <body>
<script src="some-virtual-script-uri" />
</body> As the generated html is served to the client in the very next step the client is going to request the virtual files, the memory cache holding them could evict them after first response, hopefully without blowing up the server requirements. Yes, extra roundtrips occur, and it would most certainly not be cool for stuff like critical inlined css (but that should be static and could be hashed instead). Also i am very happy to see your efforts here, please don't take my questions or suggestions as a detriment. I want this as much as you, so if nonces turn out to be a good first step into offering baseline usable csp support for kit with SSR, i'm all for it. |
That would require server state would it not? I like the idea, but I'm not sure it would work well on serverless platforms? Or if running several parallel instances? I am developing a SvelteKit application for production (living on the edge here, one of the reasons I'm keen on this feature), and in our setup ( A similar-ish crazy idea is to set up a special endpoint Either way I think we should still want the option of nonces since it enables |
I've (not without a little bit of trouble) changed this to use a The lint CI job evidently doesn't like the way I did this; any suggestions @dominikg @benmccann ? EDIT: I think I got it? EDIT: also I did confirm that this actually works for |
I think this might actually be ready now, though I don't know that I have tested the adapters well enough. I am open to suggestions for naming things better 😄 Third-party adapters will need to update to implement this, but users will only notice that it's missing if they turn on |
So to clarify the final state of this PR: A new config option Kit relies on the adapters to shim a nonce generator, Implementation varies a little between the adapters. Most of them have access to Node's Nonce generation does not run when prerendering. This PR has absolutely no effect on static sites (we'll need hashing or init script externalisation for that). |
4f59d56
to
c8de01c
Compare
I've made a change to the adapter API proposed in this PR, in order to be a little bit more generic. Instead of |
Testing has revealed that (in addition to not having implemented it correctly in the first place), noncing only works very inconsistently in EDIT: I think requiring the user to disable prerendering is a reasonable tradeoff for this feature (it's not like you can use nonces with static files anyway). I think I'm just going to add a note in the docs that this will only work on pages that are not prerendered. I don't think it should automatically disable prerendering, both because that's a somewhat opaque behaviour and because there might be situations where you'd want a different CSP for different pages. |
Instead of having svelte generate the nonce, could we delegate it to the adapter. Ie having svelte render function take a nonce , which would be added to all scripts, styles, and etc. and added to the returned headers by render. const rendered = await render({
host: request_url.host,
path: request_url.pathname,
query: request_url.searchParams,
rawBody: await read(request),
headers: Object.fromEntries(request.headers),
method: request.method,
nonce: "rAnd0m",
});
return new Response(rendered.body, {
status: rendered.status,
headers: makeHeaders(rendered.headers)
}); |
@Defman I really liked that, so I did it! The adapter generates a nonce in it's own way and simply supplies it as At a later date we probably want to offer hashing as well, which has some similar problems (platform-dependent api to access hash functions, for example) with the added complexity that hashes must be computed inside Kit as they cannot be precomputed. A similar non-global-shim solution for that (on the adapter side) would be something like this (example is adapter-cloudflare, so Web Crypto API): const rendered = await render(
{
host: request_url.host,
path: request_url.pathname,
query: request_url.searchParams,
rawBody: await read(request),
headers: Object.fromEntries(request.headers),
method: request.method,
nonce: generateNonces && btoa(crypto.getRandomValues(new Uint32Array(2)))
},
{},
generateHashes && async (content) => btoa(await crypto.subtle.digest('SHA-256', content))
); |
I don't think we have any issue supporting prerendered pages, we would just have to supply a nonce at pregeneration and store it when later serving the pregenerated pages. The nonce would stay the same, but since the content of the pages can't change it would not matter. |
The CSP Spec does not agree:
https://www.w3.org/TR/CSP3/#security-nonces "Each time it transmits a policy" is very clear, and does not allow storing nonces generated ahead of time. I would strongly urge we don't break with spec over this (though some developers might hack their way into a system that does). If you are generating the policy ahead of time (say, in a static environment), you should use hashes instead. There is more discussion around this topic in general at #93, but my intention is to also implement hashing, especially for use in prerendering. We can return the hash lists as part of the ServerResponse or something, and the adapter/hooks can take it from there. |
Like render takes a const rendered = await render(
{
host: request_url.host,
path: request_url.pathname,
query: request_url.searchParams,
rawBody: await read(request),
headers: Object.fromEntries(request.headers),
method: request.method,
hooks: { script: ({}) => { nonce: "rAnd0m" }, style: script: ({}) => { nonce: "rAnd0m" }}
}
); And for prerender let hashes = [];
const rendered = await render(
{
host: request_url.host,
path: request_url.pathname,
query: request_url.searchParams,
rawBody: await read(request),
headers: Object.fromEntries(request.headers),
method: request.method,
hooks: { script: async ({content}) => { hashes.push(await crypto.subtle.digest('SHA-256', content));, ... }
}
); Just a suggestion, this way we delegate all the work to the adapter. We can ofcs, provide some utilities to make it easier and simpler. |
Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
1325b55
to
51a0ba1
Compare
Oof. I'm terribly sorry we haven't gotten this PR in yet. I just came back to take a look and I'm afraid there are quite a few conflicts now. I hope it won't be too hard to rebase |
❌ Deploy Preview for kit-demo failed. 🔨 Explore the source changes: cf26271 🔍 Inspect the deploy log: https://app.netlify.com/sites/kit-demo/deploys/61d90fbe6fd1bc000893a009 |
Hmm, the adapter API overhaul (the Builder and stuff) breaks the way I did this. I need to look more closely at it to find a good way to tell the adapter runtime whether it should pass nonces or not. |
Closing in favour of #3499 |
Related to #93
Alternative to #2391 (don't merge them both).
This PR adds an option,
kit.cspNonce
, which, when enabled, causes Kit to generate CSP nonces and embed them in pages when it renders them. It then makes the nonce value available to asrequest.locals.nonce
, from where it can be used in a hook to be added into the Content Security Policy of the page, either using HTTP headers or HTML<meta>
tags.I made this because it felt a lot cleaner than doing a regex text replace, which is the alternative presented in #2391 (in fact there aren't any string searches). It also removes more of the burden from the developer.
I'm not completely confident that this will work in all environments.
node:crypto
is now an external runtime dependency, which should be fine for all environments actually running in node.Again, this obviously doesn't work for
adapter-static
. A different strategy (externalising the init script) is required there.The nonce is added to all scripts, even those not inline, in order to support
strict-dynamic
. They are also added to stylesheets because it can't hurt (and even allowsstyle-src: 'none'
in certain cases).Before submitting the PR, please make sure you do the following
Tests
pnpm test
and lint the project withpnpm lint
andpnpm check
Changesets
pnpx changeset
and following the prompts. All changesets should bepatch
until SvelteKit 1.0