fix: add integrity attributes to inline flight scripts when experimental.sri is enabled#95696
Open
ashusnapx wants to merge 2 commits into
Open
fix: add integrity attributes to inline flight scripts when experimental.sri is enabled#95696ashusnapx wants to merge 2 commits into
ashusnapx wants to merge 2 commits into
Conversation
…tal.sri is enabled When experimental.sri is configured, the SubresourceIntegrityPlugin adds integrity hashes to external <script src> tags, but inline flight scripts (self.__next_f.push) emitted during SSR streaming had no integrity attributes. This made it impossible to use strict CSP without 'unsafe-inline'. Thread sriAlgorithm through the entire SSR streaming pipeline: - Compute SHA-256/384/512 content hashes of each inline script at emit time - Use Web Crypto API (crypto.subtle.digest) for edge/runtime compatibility - Use Node.js crypto.createHash for the sync node stream path - Map SRI algorithm names to Web Crypto format (sha256 -> SHA-256) Fix the config threading that was missing in build templates: - app-page.ts: the production next-start build template constructs its own renderOpts.experimental from nextConfig, bypassing base-server.ts - edge-ssr-app.ts: same issue for edge runtime app pages Add comprehensive test coverage verifying integrity attributes on inline flight scripts for node, edge, and pages runtimes. Fixes vercel#95354
Author
|
cc @timneutkens @unstubbable @gnoff @eps1lon @lubieowoce @ztanner — touching the core app-render pipeline, SRI, flight streaming, and build templates. Would appreciate a review! |
…forget in ReadableStream.start() ReadableStream.start() is synchronous and cannot await async operations. When sriAlgorithm is enabled, writeInitialInstructions calls computeInlineScriptIntegrity which uses crypto.subtle.digest (async). Previously this was fire-and-forget in start(), swallowing errors and allowing flight data scripts to be enqueued before the bootstrap. Move initial instructions to the first pull() call which is async, ensuring proper ordering and error propagation.
Author
|
Good catch! Fixed — moved
All 21 tests still pass after this change. |
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.
What
When
experimental.sriis configured, theSubresourceIntegrityPluginadds integrity hashes to external<script src>tags, but inline flight scripts (self.__next_f.push) emitted during SSR streaming had nointegrityattributes. This made it impossible to use strict CSP without'unsafe-inline'.This PR threads the SRI algorithm through the entire SSR streaming pipeline so that every inline
<script>tag containing Flight hydration data receives a content-addressedintegrityattribute.Why
With strict CSP (
script-src 'self'without'unsafe-inline'), browsers block inline scripts unless they carry a validintegrityhash. The existingexperimental.srifeature only handled external scripts — all inline Flight data scripts were blocked, breaking hydration entirely under strict CSP.Fixes #95354
How
1. Core implementation — compute integrity at emit time
Inline Flight scripts are generated at request time during SSR streaming (not at build time), so their content is unknown until render. We compute the SHA hash of each script's text content at the moment it is emitted:
use-flight-response.tsx— AddedcomputeInlineScriptIntegrity()using the Web Crypto API (crypto.subtle.digest), available in both Edge and Node.js runtimes. This avoids bundling the Node.jscryptomodule which is unavailable in Edge/ESM builds. UpdatedcreateInlinedDataReadableStream,writeInitialInstructions, andwriteFlightDataInstructionto acceptsriAlgorithmand emitintegrityattributes.stream-ops.node.ts— AddedcomputeInlineScriptIntegritySync()using Node.jscrypto.createHashfor the synchronous Node.js stream path. UpdatedcreateNodeInlinedDataStream,createWebInlinedDataStream, andpullFlightDatato accept and forwardsriAlgorithm.stream-ops.web.ts— Updated stream function signatures to acceptsriAlgorithm.app-render.tsx— ExtractedsriAlgorithmfromrenderOpts.experimentaland threaded it to all 13+createInlinedDataStreamcall sites across the dynamic render, PPR, static prerender, and error recovery paths.2. Config threading — the critical fix
This was the hardest bug to find. We initially added
sriAlgorithmtobase-server.ts'srenderOpts.experimental(line 602) and saw it correctly threaded throughapp-render.tsx. However, the tests still failed withscriptsWithIntegrity === 0.Root cause: In production (
next start), app pages are NOT served viabase-server.ts'srenderOpts. Instead, the request flows through:NextNodeServer.renderPageComponent()→base-server.ts:renderPageComponent()→NextNodeServer.renderHTML()→lazyRenderAppPage()handler()function in theapp-page.tsbuild template, which constructs its ownrenderOpts.experimentalfrom scratch — completely bypassingbase-server.ts's setting.The same issue exists for edge runtime pages via
edge-ssr-app.ts.Fix: Added
sriAlgorithm: nextConfig.experimental.sri?.algorithmto theexperimentalobject in:packages/next/src/build/templates/app-page.ts— productionnext startfor Node.js app pagespackages/next/src/build/templates/edge-ssr-app.ts— productionnext startfor Edge runtime app pages3. Web Crypto API algorithm name mapping
Edge runtime's
crypto.subtle.digest()requires the Web Crypto algorithm format (SHA-256,SHA-384,SHA-512), but the SRI config uses lowercase without hyphens (sha256,sha384,sha512). Added a mapping step:4. Test coverage
Added a new test case
includes integrity attributes on inline flight scriptstosubresource-integrity.test.tsthat:<script>tags (withoutsrc)integrityattribute in the formatsha256-<base64>Final test results: 21/21 passing (7 per runtime × 3 runtimes).
Files changed
packages/next/src/server/app-render/use-flight-response.tsxcomputeInlineScriptIntegrity()+ updated stream functionspackages/next/src/server/app-render/stream-ops.node.tscomputeInlineScriptIntegritySync()+ updated node stream functionspackages/next/src/server/app-render/stream-ops.web.tspackages/next/src/server/app-render/app-render.tsxsriAlgorithmto all inline data stream creation sitespackages/next/src/server/app-render/types.tssriAlgorithm?toRenderOptsPartial.experimentalpackages/next/src/server/base-server.tssriAlgorithmtothis.renderOpts.experimentalpackages/next/src/build/templates/app-page.tssriAlgorithmto build template'sexperimentalpackages/next/src/build/templates/edge-ssr-app.tssriAlgorithmto edge build template'sexperimentaltest/production/app-dir/subresource-integrity/subresource-integrity.test.tsChallenges encountered during development
require('crypto')breaks Edge/ESM builds — The Node.jscryptomodule cannot berequire()'d inuse-flight-response.tsxbecause it is bundled for Edge/ESM paths wherecryptodoes not exist. Fixed by using the Web Crypto API exclusively.Optional
sriAlgorithm?type — Initially addedsriAlgorithmas required, which caused TypeScript errors inapp-page.ts,edge-ssr-app.ts, andexport/index.tswhere not all experimental fields were being passed. Fixed by making it optional (sriAlgorithm?).base-server.tsis a dead path for app pages — The most time-consuming issue.base-server.ts:602correctly setssriAlgorithmonthis.renderOpts.experimental, but in production, theapp-page.tsbuild template constructs its ownrenderOptsfromnextConfigdirectly, never readingbase-server.ts's value. Same foredge-ssr-app.tsand edge runtime pages. This required deep tracing through the entire request pipeline:NextNodeServer.renderPageComponent()→base-server.ts:renderPageComponent()→renderToResponseWithComponents()→renderHTML()→lazyRenderAppPage()→handler()in the build template.Edge runtime
crypto.subtle.digestalgorithm format — The Web Crypto API requiresSHA-256format while SRI usessha256. The edge runtime threwUnrecognized algorithm nameon the first pass. Fixed with a mapping regex.app-route.tsandexport/index.tsare not needed — App routes (API routes) do not render HTML with inline Flight scripts, andexport/index.tsis used for static export which does not generate inline scripts at request time. These do not needsriAlgorithm.