Next.js image optimization has Denial of Service condition
Description
Next.js is a React Framework for the Web. Cersions on the 10.x, 11.x, 12.x, 13.x, and 14.x branches before version 14.2.7 contain a vulnerability in the image optimization feature which allows for a potential Denial of Service (DoS) condition which could lead to excessive CPU consumption. Neither the next.config.js file that is configured with images.unoptimized set to true or images.loader set to a non-default value nor the Next.js application that is hosted on Vercel are affected. This issue was fully patched in Next.js 14.2.7. As a workaround, ensure that the next.config.js file has either images.unoptimized, images.loader or images.loaderFile assigned.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
nextnpm | >= 10.0.0, < 14.2.7 | 14.2.7 |
Affected products
1Patches
1d11cbc9ff0b1Reject next image urls in image optimizer (#68628)
4 files changed · +66 −8
packages/next/src/lib/url.ts+8 −0 modified@@ -11,3 +11,11 @@ export function getPathname(url: string) { export function isFullStringUrl(url: string) { return /https?:\/\//.test(url) } + +export function parseUrl(url: string): URL | undefined { + let parsed = undefined + try { + parsed = new URL(url, DUMMY_ORIGIN) + } catch {} + return parsed +}
packages/next/src/server/image-optimizer.ts+8 −3 modified@@ -28,6 +28,7 @@ import type { import { sendEtagResponse } from './send-payload' import { getContentType, getExtension } from './serve-static' import * as Log from '../build/output/log' +import { parseUrl } from '../lib/url' type XCacheHeader = 'MISS' | 'HIT' | 'STALE' @@ -197,9 +198,13 @@ export class ImageOptimizerCache { } } - if (url.startsWith('/_next/image')) { - return { - errorMessage: '"url" parameter cannot be recursive', + const parsedUrl = parseUrl(url) + if (parsedUrl) { + const decodedPathname = decodeURIComponent(parsedUrl.pathname) + if (/\/_next\/image($|\/)/.test(decodedPathname)) { + return { + errorMessage: '"url" parameter cannot be recursive', + } } }
test/integration/image-optimizer/test/util.ts+41 −5 modified@@ -8,6 +8,7 @@ import { fetchViaHTTP, File, findPort, + getFetchUrl, killApp, launchApp, nextBuild, @@ -879,11 +880,46 @@ export function runTests(ctx) { ) }) - it('should fail when url is recursive', async () => { - const query = { url: `/_next/image?url=test.pngw=1&q=1`, w: ctx.w, q: 1 } - const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {}) - expect(res.status).toBe(400) - expect(await res.text()).toBe(`"url" parameter cannot be recursive`) + describe('recursive url is not allowed', () => { + it('should fail with relative next image url', async () => { + const query = { url: `/_next/image?url=test.pngw=1&q=1`, w: ctx.w, q: 1 } + const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {}) + expect(res.status).toBe(400) + expect(await res.text()).toBe(`"url" parameter cannot be recursive`) + }) + + it('should fail with encoded relative image url', async () => { + const query = { + url: '%2F_next%2Fimage%3Furl%3Dtest.pngw%3D1%26q%3D1', + w: ctx.w, + q: 1, + } + const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {}) + expect(res.status).toBe(400) + expect(await res.text()).toBe(`"url" parameter is invalid`) + }) + + it('should fail with absolute next image url', async () => { + const fullUrl = getFetchUrl( + ctx.appPort, + '/_next/image?url=test.pngw=1&q=1' + ) + const query = { url: fullUrl, w: ctx.w, q: 1 } + const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {}) + expect(res.status).toBe(400) + expect(await res.text()).toBe(`"url" parameter cannot be recursive`) + }) + + it('should fail with relative image url with assetPrefix', async () => { + const fullUrl = getFetchUrl( + ctx.appPort, + `/assets/_next/image?url=test.pngw=1&q=1` + ) + const query = { url: fullUrl, w: ctx.w, q: 1 } + const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {}) + expect(res.status).toBe(400) + expect(await res.text()).toBe(`"url" parameter cannot be recursive`) + }) }) it('should fail when internal url is not an image', async () => {
test/lib/next-test-utils.ts+9 −0 modified@@ -147,6 +147,15 @@ export function withQuery( return `${pathname}?${querystring}` } +export function getFetchUrl( + appPort: string | number, + pathname: string, + query?: Record<string, any> | string | null | undefined +) { + const url = query ? withQuery(pathname, query) : pathname + return getFullUrl(appPort, url) +} + export function fetchViaHTTP( appPort: string | number, pathname: string,
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-g77x-44xx-532mghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-47831ghsaADVISORY
- github.com/vercel/next.js/commit/d11cbc9ff0b1aaefabcba9afe1e562e0b1fde65aghsax_refsource_MISCWEB
- github.com/vercel/next.js/security/advisories/GHSA-g77x-44xx-532mghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.