feat: resolve the CSP nonce per request with start.nonce - #389
Closed
everton-dgn wants to merge 1 commit into
Closed
everton-dgn wants to merge 1 commit into
everton-dgn wants to merge 1 commit into
Conversation
Generated entries rendered with a fixed { manifest }, so the hydration
bootstrap, the streamed data and swap scripts and the modulepreload links
never carried a nonce, and nothing inside the app could supply one.
start.nonce names a module resolved after the middleware chain; the
handler passes its result (or handleRequest's nonce, which wins) to the
generated renderToStream, the client-entry tag, the post-flush redirect
fallback and, in dev, the injected head tags. Authored entries receive it
as context.nonce. The { script, style } form no longer throws in the
client-entry transform, and invalid values are rejected.
🦋 Changeset detectedLatest commit: 100fb61 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #388.
New
start.nonceoption: the path of a server-only module default-exporting(event) => CSPNonce | undefined | Promise<…>. The handler resolves it per request after the middleware chain, next toresolveRenderMode. A middleware can therefore generate the nonce, set theContent-Security-Policyheader and hand the value over throughevent.locals.The resolved nonce now reaches the generated entry's
renderToStream: the_$HYbootstrap, the streamed data and swap scripts and themodulepreloadlinks carry it. It also reaches:scriptNonce;csp-noncemeta for the styles the Vite client injects.A strict
script-src 'nonce-…' 'strict-dynamic'policy works with generated entries, in dev and production, including hosts that dispatch through the default Fetchable (Nitro).handleRequest(request, { nonce })keeps precedence over the module and now reaches the render too. Authored entries receive the value ascontext.nonce.The object form
{ script, style }no longer throwsTypeError: value.replace is not a functionin the client-entry transform. An invalid nonce is rejected with an error naming its source. That covers a primitive other than a string, an array, or an object with keys other thanscript/style.Cause
The generated entry rendered with a fixed
{ manifest }and ignoredcontext(src/ssr/index.tsat e4cdee4, L932 and L1394).options.nonceonly reachedcreateSSRResponseand the entry tag (L1279, L1428). Nothing inside the app could supply the nonce:(request, next);@solidjs/webreads the nonce only from the render options.The only way out was hand-written
entry-server/entry-clientfiles, which also drop the generated error boundary.Change
src/ssr/index.ts:StartOptions.nonce, with JSDoc in therenderModestyle.resolveNonceModulevalidates at config time: path only, and it must exist. It is server mode only, likesetupandrenderMode.assertNonce/resolveNonceapply the precedenceoptions.nonce, then the module.entry.render(request, { clientEntry, nonce, ...options.context }).nonce: context && context.noncetorenderToStream, including thestart.setuppath and the client-mode shell.createSSRResponsetakescriptNonce(nonce).devHead(nonceAttr, styleAttr)plusdevStyles(...).virtual-solid-manifest.d.ts:nonceon thehandleRequestoptions, typed asCSPNonce.examples/start-ssr:src/nonce.ts, anSSR_NONCEknob,src/middleware.tsstoringx-csp-nonceonlocals, and a newnoncemode intest/run.mjs.Verification
TypeErroraborts the override block)The
noncemode run againstorigin/next'ssrc/ssr/index.tsfails the new assertions: scripts andmodulepreloadlinks without the nonce, theTypeErrorfor the object form, and no module import.