Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: vercel/next.js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v15.2.3
Choose a base ref
...
head repository: vercel/next.js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v15.2.4
Choose a head ref
  • 6 commits
  • 32 files changed
  • 3 contributors

Commits on Mar 24, 2025

  1. switch development origin verification to be opt-in rather than opt-o…

    …ut (#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
    ztanner authored and ijjk committed Mar 24, 2025
    Copy the full SHA
    f847302 View commit details
  2. remove direct ip/port bypass in dev origin check (#77414)

    It's potentially unsafe to allow any sort of origin bypass if `allowedDevOrigins` is configured as it's trivial to stand up a remote server on the same port. This removes the case that would bypass the origin check.
    ztanner authored and ijjk committed Mar 24, 2025
    Copy the full SHA
    cfeaa86 View commit details
  3. ensure /__next middleware URLs are included in the origin check (#77416)

    We have special development endpoints that are also prefixed under `/__nextjs`. This updates the origin checking logic to account for those in addition to `/_next`, and adds a test.
    ztanner authored and ijjk committed Mar 24, 2025
    Copy the full SHA
    d9bcb83 View commit details
  4. exclude images and static media from dev origin check (#77417)

    Excludes `/_next/image` and `/_next/static/media` as they don't contain sensitive information and prevents complications loading them in cases where they are inlined in CSS, as they'll be requested with `sec-fetch-mode: no-cors`. 
    
    x-ref: #77344
    ztanner authored and ijjk committed Mar 24, 2025
    Copy the full SHA
    25f810b View commit details
  5. Match subrequest handling for edge and node (#77474)

    This aligns our subrequest handling between edge and node runtimes as we
    did not carry over this handling for node and as such we want to remove
    this from edge as well.
    ijjk committed Mar 24, 2025
    Copy the full SHA
    ecb72ee View commit details
  6. v15.2.4

    vercel-release-bot committed Mar 24, 2025
    Copy the full SHA
    804aa35 View commit details
Showing with 462 additions and 292 deletions.
  1. +2 −2 docs/01-app/04-api-reference/05-config/01-next-config-js/allowedDevOrigins.mdx
  2. +1 −1 lerna.json
  3. +1 −1 packages/create-next-app/package.json
  4. +2 −2 packages/eslint-config-next/package.json
  5. +1 −1 packages/eslint-plugin-next/package.json
  6. +1 −1 packages/font/package.json
  7. +1 −1 packages/next-bundle-analyzer/package.json
  8. +1 −1 packages/next-codemod/package.json
  9. +1 −1 packages/next-env/package.json
  10. +1 −1 packages/next-mdx/package.json
  11. +1 −1 packages/next-plugin-rspack/package.json
  12. +1 −1 packages/next-plugin-storybook/package.json
  13. +1 −1 packages/next-polyfill-module/package.json
  14. +1 −1 packages/next-polyfill-nomodule/package.json
  15. +1 −1 packages/next-swc/package.json
  16. +7 −7 packages/next/package.json
  17. +1 −1 packages/next/src/server/config-shared.ts
  18. +4 −17 packages/next/src/server/lib/router-server.ts
  19. +67 −31 packages/next/src/server/lib/router-utils/block-cross-site.ts
  20. +0 −11 packages/next/src/server/lib/server-ipc/utils.ts
  21. +0 −21 packages/next/src/server/web/sandbox/context.ts
  22. +0 −19 packages/next/src/server/web/sandbox/sandbox.ts
  23. +1 −1 packages/react-refresh-utils/package.json
  24. +2 −2 packages/third-parties/package.json
  25. +8 −8 pnpm-lock.yaml
  26. +354 −0 test/development/basic/allowed-dev-origins.test.ts
  27. +1 −103 test/development/basic/misc.test.ts
  28. BIN test/development/basic/misc/public/image.png
  29. +0 −20 test/e2e/app-dir/app-routes-subrequests/app-routes-subrequests.test.ts
  30. +0 −11 test/e2e/app-dir/app-routes-subrequests/app/route.ts
  31. +0 −10 test/e2e/app-dir/app-routes-subrequests/next.config.js
  32. +0 −13 test/e2e/middleware-general/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -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:
@@ -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 lerna.json
Original file line number Diff line number Diff line change
@@ -16,5 +16,5 @@
"registry": "https://registry.npmjs.org/"
}
},
"version": "15.2.3"
"version": "15.2.4"
}
2 changes: 1 addition & 1 deletion packages/create-next-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
"version": "15.2.3",
"version": "15.2.4",
"keywords": [
"react",
"next",
4 changes: 2 additions & 2 deletions packages/eslint-config-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
"version": "15.2.3",
"version": "15.2.4",
"description": "ESLint configuration used by Next.js.",
"main": "index.js",
"license": "MIT",
@@ -10,7 +10,7 @@
},
"homepage": "https://nextjs.org/docs/app/api-reference/config/eslint",
"dependencies": {
"@next/eslint-plugin-next": "15.2.3",
"@next/eslint-plugin-next": "15.2.4",
"@rushstack/eslint-patch": "^1.10.3",
"@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
2 changes: 1 addition & 1 deletion packages/eslint-plugin-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
"version": "15.2.3",
"version": "15.2.4",
"description": "ESLint plugin for Next.js.",
"main": "dist/index.js",
"license": "MIT",
2 changes: 1 addition & 1 deletion packages/font/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@next/font",
"private": true,
"version": "15.2.3",
"version": "15.2.4",
"repository": {
"url": "vercel/next.js",
"directory": "packages/font"
2 changes: 1 addition & 1 deletion packages/next-bundle-analyzer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
"version": "15.2.3",
"version": "15.2.4",
"main": "index.js",
"types": "index.d.ts",
"license": "MIT",
2 changes: 1 addition & 1 deletion packages/next-codemod/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
"version": "15.2.3",
"version": "15.2.4",
"license": "MIT",
"repository": {
"type": "git",
2 changes: 1 addition & 1 deletion packages/next-env/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/env",
"version": "15.2.3",
"version": "15.2.4",
"keywords": [
"react",
"next",
2 changes: 1 addition & 1 deletion packages/next-mdx/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
"version": "15.2.3",
"version": "15.2.4",
"main": "index.js",
"license": "MIT",
"repository": {
2 changes: 1 addition & 1 deletion packages/next-plugin-rspack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-rspack",
"version": "15.2.3",
"version": "15.2.4",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-rspack"
2 changes: 1 addition & 1 deletion packages/next-plugin-storybook/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
"version": "15.2.3",
"version": "15.2.4",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
2 changes: 1 addition & 1 deletion packages/next-polyfill-module/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
"version": "15.2.3",
"version": "15.2.4",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
2 changes: 1 addition & 1 deletion packages/next-polyfill-nomodule/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
"version": "15.2.3",
"version": "15.2.4",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
2 changes: 1 addition & 1 deletion packages/next-swc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/swc",
"version": "15.2.3",
"version": "15.2.4",
"private": true,
"scripts": {
"clean": "node ../../scripts/rm.mjs native",
14 changes: 7 additions & 7 deletions packages/next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next",
"version": "15.2.3",
"version": "15.2.4",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
@@ -100,7 +100,7 @@
]
},
"dependencies": {
"@next/env": "15.2.3",
"@next/env": "15.2.4",
"@swc/counter": "0.1.3",
"@swc/helpers": "0.5.15",
"busboy": "1.6.0",
@@ -164,11 +164,11 @@
"@jest/types": "29.5.0",
"@mswjs/interceptors": "0.23.0",
"@napi-rs/triples": "1.2.0",
"@next/font": "15.2.3",
"@next/polyfill-module": "15.2.3",
"@next/polyfill-nomodule": "15.2.3",
"@next/react-refresh-utils": "15.2.3",
"@next/swc": "15.2.3",
"@next/font": "15.2.4",
"@next/polyfill-module": "15.2.4",
"@next/polyfill-nomodule": "15.2.4",
"@next/react-refresh-utils": "15.2.4",
"@next/swc": "15.2.4",
"@opentelemetry/api": "1.6.0",
"@playwright/test": "1.41.2",
"@storybook/addon-a11y": "8.6.0",
2 changes: 1 addition & 1 deletion packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
@@ -1136,7 +1136,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: {
allowedDevOrigins: [],
nodeMiddleware: false,
21 changes: 4 additions & 17 deletions packages/next/src/server/lib/router-server.ts
Original file line number Diff line number Diff line change
@@ -166,21 +166,6 @@ export async function initialize(opts: {
renderServer.instance =
require('./render-server') as typeof import('./render-server')

const randomBytes = new Uint8Array(8)
crypto.getRandomValues(randomBytes)
const middlewareSubrequestId = Buffer.from(randomBytes).toString('hex')
;(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) {
@@ -332,7 +317,7 @@ 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)) {
return
}
const origUrl = req.url || '/'
@@ -698,7 +683,9 @@ 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)
) {
return
}
const { basePath, assetPrefix } = config
98 changes: 67 additions & 31 deletions packages/next/src/server/lib/router-utils/block-cross-site.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,76 @@
import type { Duplex } from 'stream'
import type { IncomingMessage, ServerResponse } from 'webpack-dev-server'
import { parseUrl } from '../../../lib/url'
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
}

function isInternalDevEndpoint(req: IncomingMessage): boolean {
if (!req.url) return false

try {
// TODO: We should standardize on a single prefix for this
const isMiddlewareRequest = req.url.includes('/__nextjs')
const isInternalAsset = req.url.includes('/_next')
// Static media requests are excluded, as they might be loaded via CSS and would fail
// CORS checks.
const isIgnoredRequest =
req.url.includes('/_next/image') ||
req.url.includes('/_next/static/media')

return !isIgnoredRequest && (isInternalAsset || isMiddlewareRequest)
} catch (err) {
return false
}
}

export const blockCrossSite = (
req: IncomingMessage,
res: ServerResponse | Duplex,
allowedOrigins: string[],
activePort: string
allowedDevOrigins: string[] | undefined,
hostname: string | undefined
): boolean => {
// only process _next URLs
if (!req.url?.includes('/_next')) {
// 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 internal URLs/middleware
if (!isInternalDevEndpoint(req)) {
return false
}
// block non-cors request from cross-site e.g. script tag on
@@ -21,14 +79,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
@@ -39,24 +90,9 @@ export const blockCrossSite = (

if (parsedOrigin) {
const originLowerCase = parsedOrigin.hostname.toLowerCase()
const isMatchingPort = parsedOrigin.port === activePort
const isIpRequest =
net.isIPv4(originLowerCase) || net.isIPv6(originLowerCase)

if (
// allow requests if direct IP and matching port and
// allow if any of the allowed origins match
!(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

if (!isCsrfOriginAllowed(originLowerCase, allowedOrigins)) {
return warnOrBlockRequest(res, originLowerCase, mode)
}
}
}
11 changes: 0 additions & 11 deletions packages/next/src/server/lib/server-ipc/utils.ts
Original file line number Diff line number Diff line change
@@ -57,16 +57,5 @@ export const filterInternalHeaders = (
if (INTERNAL_HEADERS.includes(header)) {
delete headers[header]
}

// If this request didn't origin from this session we filter
// out the "x-middleware-subrequest" header so we don't skip
// middleware incorrectly
if (
header === 'x-middleware-subrequest' &&
headers['x-middleware-subrequest-id'] !==
(globalThis as any)[Symbol.for('@next/middleware-subrequest-id')]
) {
delete headers['x-middleware-subrequest']
}
}
}
21 changes: 0 additions & 21 deletions packages/next/src/server/web/sandbox/context.ts
Original file line number Diff line number Diff line change
@@ -362,27 +362,6 @@ Learn More: https://nextjs.org/docs/messages/edge-dynamic-code-evaluation`),

init.headers = new Headers(init.headers ?? {})

// Forward subrequest header from incoming request to outgoing request
const store = requestStore.getStore()
if (
store?.headers.has('x-middleware-subrequest') &&
!init.headers.has('x-middleware-subrequest')
) {
init.headers.set(
'x-middleware-subrequest',
store.headers.get('x-middleware-subrequest') ?? ''
)
}
init.headers.set(
'x-middleware-subrequest-id',
(globalThis as any)[Symbol.for('@next/middleware-subrequest-id')]
)

const prevs =
init.headers.get(`x-middleware-subrequest`)?.split(':') || []
const value = prevs.concat(options.moduleName).join(':')
init.headers.set('x-middleware-subrequest', value)

if (!init.headers.has('user-agent')) {
init.headers.set(`user-agent`, `Next.js Middleware`)
}
Loading