Skip to content

Commit f847302

Browse files
ztannerijjk
authored andcommitted
switch development origin verification to be opt-in rather than opt-out (#77395)
To avoid breaking local development proxies and more complex setups, this ensures that we only block cross-origin development requests when opting into the configuration. In a future major release, this will not be opt-in, and will require explicitly providing the allowed origins that can access the special `/_next` endpoints. This adds a warning when a cross origin request is detected that would be blocked without explicit configuration. Fixes #77073 Fixes #77253 Fixes #77344
1 parent 535e26d commit f847302

File tree

6 files changed

+344
-135
lines changed

6 files changed

+344
-135
lines changed

docs/01-app/04-api-reference/05-config/01-next-config-js/allowedDevOrigins.mdx

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ description: Use `allowedDevOrigins` to configure additional origins that can re
55

66
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
77

8+
Next.js does not automatically block cross-origin requests during development, but will block by default in a future major version of Next.js to prevent unauthorized requesting of internal assets/endpoints that are available in development mode.
9+
810
To configure a Next.js application to allow requests from origins other than the hostname the server was initialized with (`localhost` by default) you can use the `allowedDevOrigins` config option.
911

1012
`allowedDevOrigins` allows you to set additional origins that can be used in development mode. For example, to use `local-origin.dev` instead of only `localhost`, open `next.config.js` and add the `allowedDevOrigins` config:
@@ -14,5 +16,3 @@ module.exports = {
1416
allowedDevOrigins: ['local-origin.dev', '*.local-origin.dev'],
1517
}
1618
```
17-
18-
Cross-origin requests are blocked by default to prevent unauthorized requesting of internal assets/endpoints which are available in development mode. This behavior is similar to other dev servers like `webpack-dev-middleware` to ensure the same protection.

packages/next/src/server/config-shared.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1136,7 +1136,7 @@ export const defaultConfig: NextConfig = {
11361136
output: !!process.env.NEXT_PRIVATE_STANDALONE ? 'standalone' : undefined,
11371137
modularizeImports: undefined,
11381138
outputFileTracingRoot: process.env.NEXT_PRIVATE_OUTPUT_TRACE_ROOT || '',
1139-
allowedDevOrigins: [],
1139+
allowedDevOrigins: undefined,
11401140
experimental: {
11411141
allowedDevOrigins: [],
11421142
nodeMiddleware: false,

packages/next/src/server/lib/router-server.ts

+18-11
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,6 @@ export async function initialize(opts: {
172172
;(globalThis as any)[Symbol.for('@next/middleware-subrequest-id')] =
173173
middlewareSubrequestId
174174

175-
const allowedOrigins = [
176-
'*.localhost',
177-
'localhost',
178-
...(config.allowedDevOrigins || []),
179-
]
180-
if (opts.hostname) {
181-
allowedOrigins.push(opts.hostname)
182-
}
183-
184175
const requestHandlerImpl: WorkerRequestHandler = async (req, res) => {
185176
// internal headers should not be honored by the request handler
186177
if (!process.env.NEXT_PRIVATE_TEST_HEADERS) {
@@ -332,7 +323,15 @@ export async function initialize(opts: {
332323

333324
// handle hot-reloader first
334325
if (developmentBundler) {
335-
if (blockCrossSite(req, res, allowedOrigins, `${opts.port}`)) {
326+
if (
327+
blockCrossSite(
328+
req,
329+
res,
330+
config.allowedDevOrigins,
331+
opts.hostname,
332+
`${opts.port}`
333+
)
334+
) {
336335
return
337336
}
338337
const origUrl = req.url || '/'
@@ -698,7 +697,15 @@ export async function initialize(opts: {
698697
})
699698

700699
if (opts.dev && developmentBundler && req.url) {
701-
if (blockCrossSite(req, socket, allowedOrigins, `${opts.port}`)) {
700+
if (
701+
blockCrossSite(
702+
req,
703+
socket,
704+
config.allowedDevOrigins,
705+
opts.hostname,
706+
`${opts.port}`
707+
)
708+
) {
702709
return
703710
}
704711
const { basePath, assetPrefix } = config

packages/next/src/server/lib/router-utils/block-cross-site.ts

+45-18
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,54 @@ import net from 'net'
55
import { warnOnce } from '../../../build/output/log'
66
import { isCsrfOriginAllowed } from '../../app-render/csrf-protection'
77

8+
function warnOrBlockRequest(
9+
res: ServerResponse | Duplex,
10+
origin: string | undefined,
11+
mode: 'warn' | 'block'
12+
): boolean {
13+
const originString = origin ? `from ${origin}` : ''
14+
if (mode === 'warn') {
15+
warnOnce(
16+
`Cross origin request detected ${originString} to /_next/* resource. In a future major version of Next.js, you will need to explicitly configure "allowedDevOrigins" in next.config to allow this.\nRead more: https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins`
17+
)
18+
19+
return false
20+
}
21+
22+
warnOnce(
23+
`Blocked cross-origin request ${originString} to /_next/* resource. To allow this, configure "allowedDevOrigins" in next.config\nRead more: https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins`
24+
)
25+
26+
if ('statusCode' in res) {
27+
res.statusCode = 403
28+
}
29+
30+
res.end('Unauthorized')
31+
32+
return true
33+
}
34+
835
export const blockCrossSite = (
936
req: IncomingMessage,
1037
res: ServerResponse | Duplex,
11-
allowedOrigins: string[],
38+
allowedDevOrigins: string[] | undefined,
39+
hostname: string | undefined,
1240
activePort: string
1341
): boolean => {
14-
// only process _next URLs
42+
// in the future, these will be blocked by default when allowed origins aren't configured.
43+
// for now, we warn when allowed origins aren't configured
44+
const mode = typeof allowedDevOrigins === 'undefined' ? 'warn' : 'block'
45+
46+
const allowedOrigins = [
47+
'*.localhost',
48+
'localhost',
49+
...(allowedDevOrigins || []),
50+
]
51+
if (hostname) {
52+
allowedOrigins.push(hostname)
53+
}
54+
55+
// only process _next URLs when
1556
if (!req.url?.includes('/_next')) {
1657
return false
1758
}
@@ -21,14 +62,7 @@ export const blockCrossSite = (
2162
req.headers['sec-fetch-mode'] === 'no-cors' &&
2263
req.headers['sec-fetch-site'] === 'cross-site'
2364
) {
24-
if ('statusCode' in res) {
25-
res.statusCode = 403
26-
}
27-
res.end('Unauthorized')
28-
warnOnce(
29-
`Blocked cross-origin request to /_next/*. Cross-site requests are blocked in "no-cors" mode.`
30-
)
31-
return true
65+
return warnOrBlockRequest(res, undefined, mode)
3266
}
3367

3468
// ensure websocket requests from allowed origin
@@ -49,14 +83,7 @@ export const blockCrossSite = (
4983
!(isIpRequest && isMatchingPort) &&
5084
!isCsrfOriginAllowed(originLowerCase, allowedOrigins)
5185
) {
52-
if ('statusCode' in res) {
53-
res.statusCode = 403
54-
}
55-
res.end('Unauthorized')
56-
warnOnce(
57-
`Blocked cross-origin request from ${originLowerCase}. To allow this, configure "allowedDevOrigins" in next.config\nRead more: https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins`
58-
)
59-
return true
86+
return warnOrBlockRequest(res, originLowerCase, mode)
6087
}
6188
}
6289
}

0 commit comments

Comments
 (0)