Skip to content

Commit 8303bbd

Browse files
authored
Handle cross-page client reference contamination in development (#86591)
1 parent 2307d46 commit 8303bbd

31 files changed

+495
-402
lines changed

packages/next/errors.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,5 +947,9 @@
947947
"946": "Failed to deserialize errors.",
948948
"947": "Expected `sendErrorsToBrowser` to be defined in renderOpts.",
949949
"948": "Failed to serialize errors.",
950-
"949": "Route %s errored during %s. These errors are normally ignored and may not prevent the route from prerendering but are logged here because build debugging is enabled.\n \nOriginal Error: %s"
950+
"949": "Route %s errored during %s. These errors are normally ignored and may not prevent the route from prerendering but are logged here because build debugging is enabled.\n \nOriginal Error: %s",
951+
"950": "The manifests singleton was not initialized.",
952+
"951": "The client reference manifest for route \"%s\" does not exist.",
953+
"952": "Cannot access \"%s\" without a work store.",
954+
"953": "This is a proxied client reference manifest. The property \"%s\" is not handled."
951955
}

packages/next/src/build/templates/app-page.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@ import {
2121
createOpaqueFallbackRouteParams,
2222
type OpaqueFallbackRouteParams,
2323
} from '../../server/request/fallback-params'
24-
import { setReferenceManifestsSingleton } from '../../server/app-render/encryption-utils'
24+
import { setManifestsSingleton } from '../../server/app-render/manifests-singleton'
2525
import {
2626
isHtmlBotRequest,
2727
shouldServeStreamingMetadata,
2828
} from '../../server/lib/streaming-metadata'
29-
import { createServerModuleMap } from '../../server/app-render/action-utils'
3029
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'
3130
import { getIsPossibleServerAction } from '../../server/lib/server-action-request-meta'
3231
import {
@@ -400,13 +399,10 @@ export async function handler(
400399
// set the reference manifests to our global store so Server Action's
401400
// encryption util can access to them at the top level of the page module.
402401
if (serverActionsManifest && clientReferenceManifest) {
403-
setReferenceManifestsSingleton({
402+
setManifestsSingleton({
404403
page: srcPage,
405404
clientReferenceManifest,
406405
serverActionsManifest,
407-
serverModuleMap: createServerModuleMap({
408-
serverActionsManifest,
409-
}),
410406
})
411407
}
412408

@@ -540,8 +536,6 @@ export async function handler(
540536
nextFontManifest,
541537
reactLoadableManifest,
542538
subresourceIntegrityManifest,
543-
serverActionsManifest,
544-
clientReferenceManifest,
545539
setCacheStatus: routerServerContext?.setCacheStatus,
546540
setIsrStatus: routerServerContext?.setIsrStatus,
547541
setReactDebugChannel: routerServerContext?.setReactDebugChannel,

packages/next/src/build/templates/app-route.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import { patchFetch as _patchFetch } from '../../server/lib/patch-fetch'
88
import type { IncomingMessage, ServerResponse } from 'node:http'
99
import { addRequestMeta, getRequestMeta } from '../../server/request-meta'
1010
import { getTracer, type Span, SpanKind } from '../../server/lib/trace/tracer'
11-
import { setReferenceManifestsSingleton } from '../../server/app-render/encryption-utils'
12-
import { createServerModuleMap } from '../../server/app-render/action-utils'
11+
import { setManifestsSingleton } from '../../server/app-render/manifests-singleton'
1312
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'
1413
import { NodeNextRequest, NodeNextResponse } from '../../server/base-http/node'
1514
import {
@@ -185,13 +184,10 @@ export async function handler(
185184
// set the reference manifests to our global store so Server Action's
186185
// encryption util can access to them at the top level of the page module.
187186
if (serverActionsManifest && clientReferenceManifest) {
188-
setReferenceManifestsSingleton({
187+
setManifestsSingleton({
189188
page: srcPage,
190189
clientReferenceManifest,
191190
serverActionsManifest,
192-
serverModuleMap: createServerModuleMap({
193-
serverActionsManifest,
194-
}),
195191
})
196192
}
197193

packages/next/src/build/templates/edge-app-route.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { createServerModuleMap } from '../../server/app-render/action-utils'
2-
import { setReferenceManifestsSingleton } from '../../server/app-render/encryption-utils'
1+
import { setManifestsSingleton } from '../../server/app-render/manifests-singleton'
32
import type { NextConfigComplete } from '../../server/config-shared'
43
import { EdgeRouteModuleWrapper } from '../../server/web/edge-route-module-wrapper'
54

@@ -16,13 +15,10 @@ const rscManifest = self.__RSC_MANIFEST?.['VAR_PAGE']
1615
const rscServerManifest = maybeJSONParse(self.__RSC_SERVER_MANIFEST)
1716

1817
if (rscManifest && rscServerManifest) {
19-
setReferenceManifestsSingleton({
18+
setManifestsSingleton({
2019
page: 'VAR_PAGE',
2120
clientReferenceManifest: rscManifest,
2221
serverActionsManifest: rscServerManifest,
23-
serverModuleMap: createServerModuleMap({
24-
serverActionsManifest: rscServerManifest,
25-
}),
2622
})
2723
}
2824

packages/next/src/build/templates/edge-ssr-app.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import * as pageMod from 'VAR_USERLAND'
66

77
import type { RequestData } from '../../server/web/types'
88
import type { NextConfigComplete } from '../../server/config-shared'
9-
import { setReferenceManifestsSingleton } from '../../server/app-render/encryption-utils'
10-
import { createServerModuleMap } from '../../server/app-render/action-utils'
9+
import { setManifestsSingleton } from '../../server/app-render/manifests-singleton'
1110
import { initializeCacheHandlers } from '../../server/use-cache/handlers'
1211
import { BaseServerSpan } from '../../server/lib/trace/constants'
1312
import { getTracer, SpanKind, type Span } from '../../server/lib/trace/tracer'
@@ -40,13 +39,10 @@ const rscManifest = self.__RSC_MANIFEST?.['VAR_PAGE']
4039
const rscServerManifest = maybeJSONParse(self.__RSC_SERVER_MANIFEST)
4140

4241
if (rscManifest && rscServerManifest) {
43-
setReferenceManifestsSingleton({
42+
setManifestsSingleton({
4443
page: 'VAR_PAGE',
4544
clientReferenceManifest: rscManifest,
4645
serverActionsManifest: rscServerManifest,
47-
serverModuleMap: createServerModuleMap({
48-
serverActionsManifest: rscServerManifest,
49-
}),
5046
})
5147
}
5248

@@ -81,12 +77,10 @@ async function requestHandler(
8177
buildManifest,
8278
prerenderManifest,
8379
reactLoadableManifest,
84-
clientReferenceManifest,
8580
subresourceIntegrityManifest,
8681
dynamicCssManifest,
8782
nextFontManifest,
8883
resolvedPathname,
89-
serverActionsManifest,
9084
interceptionRoutePatterns,
9185
routerServerContext,
9286
} = prepareResult
@@ -129,8 +123,6 @@ async function requestHandler(
129123
reactLoadableManifest,
130124
subresourceIntegrityManifest,
131125
dynamicCssManifest,
132-
serverActionsManifest,
133-
clientReferenceManifest,
134126
setIsrStatus: routerServerContext?.setIsrStatus,
135127

136128
dir: pageRouteModule.relativeProjectDir,

packages/next/src/build/webpack/loaders/next-app-loader/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,14 @@ const nextAppLoader: AppLoader = async function nextAppLoader() {
682682

683683
const buildInfo = getModuleBuildInfo((this as any)._module)
684684
const collectedDeclarations: [string, string][] = []
685-
const page = name.replace(/^app/, '')
685+
686+
// Use the page from loaderOptions directly instead of deriving it from name.
687+
// The name (bundlePath) may have been normalized with normalizePagePath()
688+
// which is designed for Pages Router and incorrectly duplicates /index paths
689+
// (e.g., /index/page -> /index/index/page). The page parameter contains the
690+
// correct unnormalized value.
691+
const page = loaderOptions.page
692+
686693
const middlewareConfig: ProxyConfig = JSON.parse(
687694
Buffer.from(middlewareConfigBase64, 'base64').toString()
688695
)

packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ interface Options {
4141
type ModuleId = string | number /*| null*/
4242

4343
// double indexed chunkId, filename
44-
export type ManifestChunks = Array<string>
44+
export type ManifestChunks = ReadonlyArray<string>
4545

4646
const pluginState = getProxiedPluginState({
4747
ssrModules: {} as { [ssrModuleId: string]: ModuleInfo },

packages/next/src/export/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -385,11 +385,6 @@ async function exportAppImpl(
385385
join(distDir, 'server', `${NEXT_FONT_MANIFEST}.json`)
386386
),
387387
images: nextConfig.images,
388-
...(enabledDirectories.app
389-
? {
390-
serverActionsManifest,
391-
}
392-
: {}),
393388
deploymentId: nextConfig.deploymentId,
394389
htmlLimitedBots: nextConfig.htmlLimitedBots.source,
395390
experimental: {

packages/next/src/server/app-render/action-handler.ts

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import { warn } from '../../build/output/log'
4949
import { RequestCookies, ResponseCookies } from '../web/spec-extension/cookies'
5050
import { HeadersAdapter } from '../web/spec-extension/adapters/headers'
5151
import { fromNodeOutgoingHttpHeaders } from '../web/utils'
52-
import { selectWorkerForForwarding } from './action-utils'
52+
import { selectWorkerForForwarding, type ServerModuleMap } from './action-utils'
5353
import { isNodeNextRequest, isWebNextRequest } from '../base-http/helpers'
5454
import { RedirectStatusCode } from '../../client/components/redirect-status-code'
5555
import { synchronizeMutableCookies } from '../async-storage/request-store'
@@ -59,7 +59,7 @@ import { InvariantError } from '../../shared/lib/invariant-error'
5959
import { executeRevalidates } from '../revalidation-utils'
6060
import { getRequestMeta } from '../request-meta'
6161
import { setCacheBustingSearchParam } from '../../client/components/router-reducer/set-cache-busting-search-param'
62-
import { getServerModuleMap } from './encryption-utils'
62+
import { getServerModuleMap } from './manifests-singleton'
6363

6464
function formDataFromSearchQueryString(query: string) {
6565
const searchParams = new URLSearchParams(query)
@@ -473,15 +473,6 @@ export function parseHostHeader(
473473
: undefined
474474
}
475475

476-
type ServerModuleMap = Record<
477-
string,
478-
{
479-
id: string
480-
chunks: string[]
481-
name: string
482-
}
483-
>
484-
485476
type ServerActionsConfig = {
486477
bodySizeLimit?: SizeLimit
487478
allowedOrigins?: string[]
@@ -522,7 +513,7 @@ export async function handleAction({
522513
metadata: AppPageRenderResultMetadata
523514
}): Promise<HandleActionResult> {
524515
const contentType = req.headers['content-type']
525-
const { serverActionsManifest, page } = ctx.renderOpts
516+
const { page } = ctx.renderOpts
526517
const serverModuleMap = getServerModuleMap()
527518

528519
const {
@@ -640,11 +631,7 @@ export async function handleAction({
640631
const actionWasForwarded = Boolean(req.headers['x-action-forwarded'])
641632

642633
if (actionId) {
643-
const forwardedWorker = selectWorkerForForwarding(
644-
actionId,
645-
page,
646-
serverActionsManifest
647-
)
634+
const forwardedWorker = selectWorkerForForwarding(actionId, page)
648635

649636
// If forwardedWorker is truthy, it means there isn't a worker for the action
650637
// in the current handler, so we forward the request to a worker that has the action.
@@ -685,7 +672,7 @@ export async function handleAction({
685672
{ isAction: true },
686673
async (): Promise<HandleActionResult> => {
687674
// We only use these for fetch actions -- MPA actions handle them inside `decodeAction`.
688-
let actionModId: string | undefined
675+
let actionModId: string | number | undefined
689676
let boundActionArguments: unknown[] = []
690677

691678
if (
@@ -1214,7 +1201,7 @@ async function executeActionAndPrepareForRender<
12141201
function getActionModIdOrError(
12151202
actionId: string | null,
12161203
serverModuleMap: ServerModuleMap
1217-
): string {
1204+
): string | number {
12181205
// if we're missing the action ID header, we can't do any further processing
12191206
if (!actionId) {
12201207
throw new InvariantError("Missing 'next-action' header.")

packages/next/src/server/app-render/action-utils.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,18 @@ import type { ActionManifest } from '../../build/webpack/plugins/flight-client-e
22
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'
33
import { pathHasPrefix } from '../../shared/lib/router/utils/path-has-prefix'
44
import { removePathPrefix } from '../../shared/lib/router/utils/remove-path-prefix'
5+
import { getServerActionsManifest } from './manifests-singleton'
56
import { workAsyncStorage } from './work-async-storage.external'
67

8+
export interface ServerModuleMap {
9+
readonly [name: string]: {
10+
readonly id: string | number
11+
readonly name: string
12+
readonly chunks: Readonly<Array<string>> // currently not used
13+
readonly async?: boolean
14+
}
15+
}
16+
717
// This function creates a Flight-acceptable server module map proxy from our
818
// Server Reference Manifest similar to our client module map.
919
// This is because our manifest contains a lot of internal Next.js data that
@@ -12,7 +22,7 @@ export function createServerModuleMap({
1222
serverActionsManifest,
1323
}: {
1424
serverActionsManifest: ActionManifest
15-
}) {
25+
}): ServerModuleMap {
1626
return new Proxy(
1727
{},
1828
{
@@ -61,11 +71,8 @@ export function createServerModuleMap({
6171
* Checks if the requested action has a worker for the current page.
6272
* If not, it returns the first worker that has a handler for the action.
6373
*/
64-
export function selectWorkerForForwarding(
65-
actionId: string,
66-
pageName: string,
67-
serverActionsManifest: ActionManifest
68-
) {
74+
export function selectWorkerForForwarding(actionId: string, pageName: string) {
75+
const serverActionsManifest = getServerActionsManifest()
6976
const workers =
7077
serverActionsManifest[
7178
process.env.NEXT_RUNTIME === 'edge' ? 'edge' : 'node'

0 commit comments

Comments
 (0)