Skip to content

switch development origin verification to be opt-in rather than opt-out #77395

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ description: Use `allowedDevOrigins` to configure additional origins that can re

{/* 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. */}

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.

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.

`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:
Expand All @@ -14,5 +16,3 @@ module.exports = {
allowedDevOrigins: ['local-origin.dev', '*.local-origin.dev'],
}
```

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.
2 changes: 1 addition & 1 deletion packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,7 @@ export const defaultConfig: NextConfig = {
output: !!process.env.NEXT_PRIVATE_STANDALONE ? 'standalone' : undefined,
modularizeImports: undefined,
outputFileTracingRoot: process.env.NEXT_PRIVATE_OUTPUT_TRACE_ROOT || '',
allowedDevOrigins: [],
allowedDevOrigins: undefined,
experimental: {
nodeMiddleware: false,
cacheLife: {
Expand Down
29 changes: 18 additions & 11 deletions packages/next/src/server/lib/router-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,6 @@ export async function initialize(opts: {
;(globalThis as any)[Symbol.for('@next/middleware-subrequest-id')] =
middlewareSubrequestId

const allowedOrigins = [
'*.localhost',
'localhost',
...(config.allowedDevOrigins || []),
]
if (opts.hostname) {
allowedOrigins.push(opts.hostname)
}

const requestHandlerImpl: WorkerRequestHandler = async (req, res) => {
// internal headers should not be honored by the request handler
if (!process.env.NEXT_PRIVATE_TEST_HEADERS) {
Expand Down Expand Up @@ -332,7 +323,15 @@ export async function initialize(opts: {

// handle hot-reloader first
if (developmentBundler) {
if (blockCrossSite(req, res, allowedOrigins, `${opts.port}`)) {
if (
blockCrossSite(
req,
res,
config.allowedDevOrigins,
opts.hostname,
`${opts.port}`
)
) {
return
}
const origUrl = req.url || '/'
Expand Down Expand Up @@ -698,7 +697,15 @@ export async function initialize(opts: {
})

if (opts.dev && developmentBundler && req.url) {
if (blockCrossSite(req, socket, allowedOrigins, `${opts.port}`)) {
if (
blockCrossSite(
req,
socket,
config.allowedDevOrigins,
opts.hostname,
`${opts.port}`
)
) {
return
}
const { basePath, assetPrefix } = config
Expand Down
63 changes: 45 additions & 18 deletions packages/next/src/server/lib/router-utils/block-cross-site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,54 @@ import net from 'net'
import { warnOnce } from '../../../build/output/log'
import { isCsrfOriginAllowed } from '../../app-render/csrf-protection'

function warnOrBlockRequest(
res: ServerResponse | Duplex,
origin: string | undefined,
mode: 'warn' | 'block'
): boolean {
const originString = origin ? `from ${origin}` : ''
if (mode === 'warn') {
warnOnce(
`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`
)

return false
}

warnOnce(
`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`
)

if ('statusCode' in res) {
res.statusCode = 403
}

res.end('Unauthorized')

return true
}

export const blockCrossSite = (
req: IncomingMessage,
res: ServerResponse | Duplex,
allowedOrigins: string[],
allowedDevOrigins: string[] | undefined,
hostname: string | undefined,
activePort: string
): boolean => {
// only process _next URLs
// in the future, these will be blocked by default when allowed origins aren't configured.
// for now, we warn when allowed origins aren't configured
const mode = typeof allowedDevOrigins === 'undefined' ? 'warn' : 'block'

const allowedOrigins = [
'*.localhost',
'localhost',
...(allowedDevOrigins || []),
]
if (hostname) {
allowedOrigins.push(hostname)
}

// only process _next URLs when
if (!req.url?.includes('/_next')) {
return false
}
Expand All @@ -21,14 +62,7 @@ export const blockCrossSite = (
req.headers['sec-fetch-mode'] === 'no-cors' &&
req.headers['sec-fetch-site'] === 'cross-site'
) {
if ('statusCode' in res) {
res.statusCode = 403
}
res.end('Unauthorized')
warnOnce(
`Blocked cross-origin request to /_next/*. Cross-site requests are blocked in "no-cors" mode.`
)
return true
return warnOrBlockRequest(res, undefined, mode)
}

// ensure websocket requests from allowed origin
Expand All @@ -49,14 +83,7 @@ export const blockCrossSite = (
!(isIpRequest && isMatchingPort) &&
!isCsrfOriginAllowed(originLowerCase, allowedOrigins)
) {
if ('statusCode' in res) {
res.statusCode = 403
}
res.end('Unauthorized')
warnOnce(
`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`
)
return true
return warnOrBlockRequest(res, originLowerCase, mode)
}
}
}
Expand Down
Loading
Loading