Next.js self-hosted applications vulnerable to DoS via Image Optimizer remotePatterns configuration
Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.
Affected versions
Details
A DoS vulnerability exists in self-hosted Next.js applications that have `remotePatterns` configured for the Image Optimizer. The image optimization endpoint (`/_next/image`) loads external images entirely into memory without enforcing a maximum size limit, allowing an attacker to cause out-of-memory conditions by requesting optimization of arbitrarily large images. This vulnerability requires that `remotePatterns` is configured to allow image optimization from external domains and that the attacker can serve or control a large image on an allowed domain. Strongly consider upgrading to 15.5.10 and 16.1.5 to reduce risk and prevent availability issues in Next applications.
The fix
fetch(next/image): reduce maximumResponseBody from 300MB to
docs/01-app/03-api-reference/02-components/image.mdx+4 −4
@@ -839,22 +839,22 @@ module.exports = {#### `maximumResponseBody`-The default image optimization loader will fetch source images up to 300 MB in size.+The default image optimization loader will fetch source images up to 50 MB in size.```js filename="next.config.js"module.exports = {images: {-maximumResponseBody: 300_000_000,+maximumResponseBody: 50_000_000,},}```-If you know all your source images are small, you can protect memory constrained servers by reducing this to a smaller value such as 50 MB.+If you know all your source images are small, you can protect memory constrained servers by reducing this to a smaller value such as 5 MB.```js filename="next.config.js"module.exports = {images: {-maximumResponseBody: 50_000_000,+maximumResponseBody: 5_000_000,},}```
packages/next/src/shared/lib/image-config.ts+1 −1
@@ -150,7 +150,7 @@ export const imageConfigDefault: ImageConfigComplete = {minimumCacheTTL: 14400, // 4 hoursformats: ['image/webp'],maximumRedirects: 3,-maximumResponseBody: 300_000_000, // 300MB+maximumResponseBody: 50_000_000, // 50 MBdangerouslyAllowLocalIP: false,dangerouslyAllowSVG: false,contentSecurityPolicy: `script-src 'none'; frame-src 'none'; sandbox;`,
test/integration/next-image-new/app-dir-localpatterns/test/index.test.ts+1 −1
@@ -98,7 +98,7 @@ function runTests(mode: 'dev' | 'server') {},],maximumRedirects: 3,-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 14400,path: '/_next/image',qualities: [75],
test/integration/next-image-new/app-dir-qualities/test/index.test.ts+1 −1
@@ -110,7 +110,7 @@ function runTests(mode: 'dev' | 'server') {},],maximumRedirects: 3,-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 14400,path: '/_next/image',qualities: [42, 69, 88],
test/integration/next-image-new/app-dir/test/index.test.ts+1 −1
@@ -1796,7 +1796,7 @@ function runTests(mode: 'dev' | 'server') {},],maximumRedirects: 3,-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 14400,path: '/_next/image',qualities: [75],
test/integration/next-image-new/unicode/test/index.test.ts+1 −1
@@ -103,7 +103,7 @@ function runTests(mode: 'server' | 'dev') {},],maximumRedirects: 3,-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 14400,path: '/_next/image',qualities: [75],
test/integration/next-image-new/unoptimized/test/index.test.ts+1 −1
@@ -118,7 +118,7 @@ function runTests(url: string, mode: 'dev' | 'server') {},],maximumRedirects: 3,-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 14400,path: '/_next/image',qualities: [75],
test/unit/image-optimizer/fetch-external-image.test.ts+1 −1
@@ -19,7 +19,7 @@ describe('fetchExternalImage', () => {const error = await fetchExternalImage('http://example.com/no-body.jpg',false,-300_000_000+50_000_000).catch((e) => e)expect(error).toBeInstanceOf(ImageError)
fetch(next/image): reduce maximumResponseBody from 300MB to
docs/01-app/03-api-reference/02-components/image.mdx+4 −4
@@ -776,22 +776,22 @@ module.exports = {#### `maximumResponseBody`-The default image optimization loader will fetch source images up to 300 MB in size.+The default image optimization loader will fetch source images up to 50 MB in size.```js filename="next.config.js"module.exports = {images: {-maximumResponseBody: 300_000_000,+maximumResponseBody: 50_000_000,},}```-If you know all your source images are small, you can protect memory constrained servers by reducing this to a smaller value such as 50 MB.+If you know all your source images are small, you can protect memory constrained servers by reducing this to a smaller value such as 5 MB.```js filename="next.config.js"module.exports = {images: {-maximumResponseBody: 50_000_000,+maximumResponseBody: 5_000_000,},}```
packages/next/src/shared/lib/image-config.ts+1 −1
@@ -140,7 +140,7 @@ export const imageConfigDefault: ImageConfigComplete = {disableStaticImages: false,minimumCacheTTL: 60,formats: ['image/webp'],-maximumResponseBody: 300_000_000, // 300MB+maximumResponseBody: 50_000_000, // 50 MBdangerouslyAllowSVG: false,contentSecurityPolicy: `script-src 'none'; frame-src 'none'; sandbox;`,contentDispositionType: 'attachment',
test/integration/next-image-new/app-dir-localpatterns/test/index.test.ts+1 −1
@@ -96,7 +96,7 @@ function runTests(mode: 'dev' | 'server') {search: '',},],-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 60,path: '/_next/image',qualities: undefined,
test/integration/next-image-new/app-dir-qualities/test/index.test.ts+1 −1
@@ -107,7 +107,7 @@ function runTests(mode: 'dev' | 'server') {loaderFile: '',remotePatterns: [],localPatterns: undefined,-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 60,path: '/_next/image',qualities: [42, 69, 88],
test/integration/next-image-new/app-dir/test/index.test.ts+1 −1
@@ -1634,7 +1634,7 @@ function runTests(mode: 'dev' | 'server') {loaderFile: '',remotePatterns: [],localPatterns: undefined,-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 60,path: '/_next/image',qualities: undefined,
test/integration/next-image-new/unicode/test/index.test.ts+1 −1
@@ -94,7 +94,7 @@ function runTests(mode: 'server' | 'dev') {search: '',},],-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 60,path: '/_next/image',sizes: [
test/integration/next-image-new/unoptimized/test/index.test.ts+1 −1
@@ -110,7 +110,7 @@ function runTests(url: string, mode: 'dev' | 'server') {loaderFile: '',remotePatterns: [],localPatterns: undefined,-maximumResponseBody: 300000000,+maximumResponseBody: 50000000,minimumCacheTTL: 60,path: '/_next/image',qualities: undefined,
test/unit/image-optimizer/fetch-external-image.test.ts+1 −1
@@ -18,7 +18,7 @@ describe('fetchExternalImage', () => {const error = await fetchExternalImage('http://example.com/no-body.jpg',-300_000_000+50_000_000).catch((e) => e)expect(error).toBeInstanceOf(ImageError)
References
- WEBhttps://github.com/vercel/next.js/security/advisories/GHSA-9g9p-9gw9-jx7f
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2025-59471
- WEBhttps://github.com/vercel/next.js/commit/500ec83743639addceaede95e95913398975156c
- WEBhttps://github.com/vercel/next.js/commit/e5b834d208fe0edf64aa26b5d76dcf6a176500ec
- PACKAGEhttps://github.com/vercel/next.js
- WEBhttps://github.com/vercel/next.js/releases/tag/v15.5.10
- WEBhttps://github.com/vercel/next.js/releases/tag/v16.1.5