Skip to content

Commit

Permalink
Add SRI support for Node.js Runtime (vercel#73891)
Browse files Browse the repository at this point in the history
> [!NOTE]  
> This PR is best reviewed with hidden whitespace changes.

Support for setting [Subresource
Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
attributes on app router scripts was added in
vercel#39729. But this only covered
pages using the Edge Runtime.

With this PR, we're adding support for app pages using the Node.js
Runtime. The only change that's needed for that, and which was probably
just an oversight in the original PR, is reading the generated manifest
file in `loadComponents`.

In a follow-up we should also add support for adding the `integrity`
attribute to client component chunks that are injected into the head
during server-side rendering, but that needs a change in React first.

fixes vercel#66901
  • Loading branch information
unstubbable authored Dec 17, 2024
1 parent 98dc385 commit 56ea9e9
Show file tree
Hide file tree
Showing 17 changed files with 246 additions and 252 deletions.
6 changes: 6 additions & 0 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1864,6 +1864,7 @@ export default async function build(
const { configFileName, publicRuntimeConfig, serverRuntimeConfig } =
config
const runtimeEnvConfig = { publicRuntimeConfig, serverRuntimeConfig }
const sriEnabled = Boolean(config.experimental.sri?.algorithm)

const nonStaticErrorPageSpan = staticCheckSpan.traceChild(
'check-static-error-page'
Expand All @@ -1877,6 +1878,7 @@ export default async function build(
distDir,
runtimeEnvConfig,
checkingApp: false,
sriEnabled,
}))
)

Expand All @@ -1898,6 +1900,7 @@ export default async function build(
pprConfig: config.experimental.ppr,
cacheLifeProfiles: config.experimental.cacheLife,
buildId,
sriEnabled,
})
)

Expand All @@ -1909,13 +1912,15 @@ export default async function build(
distDir,
runtimeEnvConfig,
checkingApp: true,
sriEnabled,
}
)

const namedExportsPromise = worker.getDefinedNamedExports({
page: appPageToCheck,
distDir,
runtimeEnvConfig,
sriEnabled,
})

// eslint-disable-next-line @typescript-eslint/no-shadow
Expand Down Expand Up @@ -2125,6 +2130,7 @@ export default async function build(
pprConfig: config.experimental.ppr,
cacheLifeProfiles: config.experimental.cacheLife,
buildId,
sriEnabled,
})
}
)
Expand Down
9 changes: 9 additions & 0 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@ export async function isPageStatic({
cacheLifeProfiles,
pprConfig,
buildId,
sriEnabled,
}: {
dir: string
page: string
Expand All @@ -998,6 +999,7 @@ export async function isPageStatic({
nextConfigOutput: 'standalone' | 'export' | undefined
pprConfig: ExperimentalPPRConfig | undefined
buildId: string
sriEnabled: boolean
}): Promise<PageIsStaticResult> {
await createIncrementalCache({
cacheHandler,
Expand Down Expand Up @@ -1069,6 +1071,7 @@ export async function isPageStatic({
page: originalAppPath || page,
isAppPath: pageType === 'app',
isDev: false,
sriEnabled,
})
}
const Comp = componentsResult.Component as NextComponentType | undefined
Expand Down Expand Up @@ -1316,11 +1319,13 @@ export async function hasCustomGetInitialProps({
distDir,
runtimeEnvConfig,
checkingApp,
sriEnabled,
}: {
page: string
distDir: string
runtimeEnvConfig: any
checkingApp: boolean
sriEnabled: boolean
}): Promise<boolean> {
require('../shared/lib/runtime-config.external').setConfig(runtimeEnvConfig)

Expand All @@ -1329,6 +1334,7 @@ export async function hasCustomGetInitialProps({
page: page,
isAppPath: false,
isDev: false,
sriEnabled,
})
let mod = components.ComponentMod

Expand All @@ -1345,17 +1351,20 @@ export async function getDefinedNamedExports({
page,
distDir,
runtimeEnvConfig,
sriEnabled,
}: {
page: string
distDir: string
runtimeEnvConfig: any
sriEnabled: boolean
}): Promise<ReadonlyArray<string>> {
require('../shared/lib/runtime-config.external').setConfig(runtimeEnvConfig)
const components = await loadComponents({
distDir,
page: page,
isAppPath: false,
isDev: false,
sriEnabled,
})

return Object.keys(components.ComponentMod).filter((key) => {
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/export/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface ExportPageInput {
debugOutput?: boolean
nextConfigOutput?: NextConfigComplete['output']
enableExperimentalReact?: boolean
sriEnabled: boolean
}

export type ExportedPageFile = {
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/export/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ async function exportPageImpl(
enableExperimentalReact,
ampValidatorPath,
trailingSlash,
sriEnabled,
} = input

if (enableExperimentalReact) {
Expand Down Expand Up @@ -234,6 +235,7 @@ async function exportPageImpl(
page,
isAppPath: isAppDir,
isDev: false,
sriEnabled,
})

// Handle App Routes.
Expand Down Expand Up @@ -396,6 +398,7 @@ export async function exportPages(
httpAgentOptions: nextConfig.httpAgentOptions,
debugOutput: options.debugOutput,
enableExperimentalReact: needsExperimentalReact(nextConfig),
sriEnabled: Boolean(nextConfig.experimental.sri?.algorithm),
}),
// If exporting the page takes longer than the timeout, reject the promise.
new Promise((_, reject) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/server/dev/next-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,8 @@ export default class DevServer extends Server {
maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
nextConfigOutput: this.nextConfig.output,
buildId: this.renderOpts.buildId,
authInterrupts: !!this.nextConfig.experimental.authInterrupts,
authInterrupts: Boolean(this.nextConfig.experimental.authInterrupts),
sriEnabled: Boolean(this.nextConfig.experimental.sri?.algorithm),
})
return pathsResult
} finally {
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/server/dev/static-paths-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export async function loadStaticPaths({
nextConfigOutput,
buildId,
authInterrupts,
sriEnabled,
}: {
dir: string
distDir: string
Expand All @@ -70,6 +71,7 @@ export async function loadStaticPaths({
nextConfigOutput: 'standalone' | 'export' | undefined
buildId: string
authInterrupts: boolean
sriEnabled: boolean
}): Promise<Partial<StaticPathsResult>> {
// update work memory runtime-config
require('../../shared/lib/runtime-config.external').setConfig(config)
Expand All @@ -83,6 +85,7 @@ export async function loadStaticPaths({
page: page || pathname,
isAppPath,
isDev: true,
sriEnabled,
})

if (isAppPath) {
Expand Down
10 changes: 10 additions & 0 deletions packages/next/src/server/load-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CLIENT_REFERENCE_MANIFEST,
SERVER_REFERENCE_MANIFEST,
DYNAMIC_CSS_MANIFEST,
SUBRESOURCE_INTEGRITY_MANIFEST,
} from '../shared/lib/constants'
import { join } from 'path'
import { requirePage } from './require'
Expand Down Expand Up @@ -136,11 +137,13 @@ async function loadComponentsImpl<N = any>({
page,
isAppPath,
isDev,
sriEnabled,
}: {
distDir: string
page: string
isAppPath: boolean
isDev: boolean
sriEnabled: boolean
}): Promise<LoadComponentsReturnType<N>> {
let DocumentMod = {}
let AppMod = {}
Expand All @@ -167,6 +170,7 @@ async function loadComponentsImpl<N = any>({
dynamicCssManifest,
clientReferenceManifest,
serverActionsManifest,
subresourceIntegrityManifest,
] = await Promise.all([
loadManifestWithRetries<BuildManifest>(
join(distDir, BUILD_MANIFEST),
Expand Down Expand Up @@ -201,6 +205,11 @@ async function loadComponentsImpl<N = any>({
manifestLoadAttempts
).catch(() => null)
: null,
sriEnabled
? loadManifestWithRetries<DeepReadonly<Record<string, string>>>(
join(distDir, 'server', SUBRESOURCE_INTEGRITY_MANIFEST + '.json')
).catch(() => undefined)
: undefined,
])

// Before requiring the actual page module, we have to set the reference
Expand Down Expand Up @@ -231,6 +240,7 @@ async function loadComponentsImpl<N = any>({
Document,
Component,
buildManifest,
subresourceIntegrityManifest,
reactLoadableManifest,
dynamicCssManifest,
pageConfig: ComponentMod.config || {},
Expand Down
7 changes: 7 additions & 0 deletions packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,14 @@ export default class NextNodeServer extends BaseServer<
protected cleanupListeners = new AsyncCallbackSet()
protected internalWaitUntil: WaitUntil | undefined
private isDev: boolean
private sriEnabled: boolean

constructor(options: Options) {
// Initialize super class
super(options)

this.isDev = options.dev ?? false
this.sriEnabled = Boolean(options.conf.experimental?.sri?.algorithm)

/**
* This sets environment variable to be used at the time of SSR by head.tsx.
Expand Down Expand Up @@ -218,12 +220,14 @@ export default class NextNodeServer extends BaseServer<
page: '/_document',
isAppPath: false,
isDev: this.isDev,
sriEnabled: this.sriEnabled,
}).catch(() => {})
loadComponents({
distDir: this.distDir,
page: '/_app',
isAppPath: false,
isDev: this.isDev,
sriEnabled: this.sriEnabled,
}).catch(() => {})
}

Expand Down Expand Up @@ -285,6 +289,7 @@ export default class NextNodeServer extends BaseServer<
page,
isAppPath: false,
isDev: this.isDev,
sriEnabled: this.sriEnabled,
}).catch(() => {})
}

Expand All @@ -294,6 +299,7 @@ export default class NextNodeServer extends BaseServer<
page,
isAppPath: true,
isDev: this.isDev,
sriEnabled: this.sriEnabled,
})
.then(async ({ ComponentMod }) => {
// we need to ensure fetch is patched before we require the page,
Expand Down Expand Up @@ -805,6 +811,7 @@ export default class NextNodeServer extends BaseServer<
page: pagePath,
isAppPath,
isDev: this.isDev,
sriEnabled: this.sriEnabled,
})

if (
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default } from '../node/page'

export const runtime = 'edge'
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const dynamic = 'force-dynamic'

export default function Page() {
return <p>hello world</p>
}
Loading

0 comments on commit 56ea9e9

Please sign in to comment.