Next.js missing cache-control header may lead to CDN caching empty reply
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
0.9.9 → fixed in 13.4.20-canary.13
Details
Next.js before 13.4.20-canary.13 lacks a cache-control header and thus empty prefetch responses may sometimes be cached by a CDN, causing a denial of service to all users requesting the same URL via that CDN. Cloudflare considers these requests cacheable assets.
The fix
add cache control header
packages/next/src/server/base-server.ts+4 −0
@@ -1614,6 +1614,10 @@ export default abstract class Server<ServerOptions extends Options = Options> {!(is404Page || pathname === '/_error')) {res.setHeader('x-middleware-skip', '1')+res.setHeader(+'cache-control',+'private, no-cache, no-store, max-age=0, must-revalidate'+)res.body('{}').send()return null}
Add cache control header for prefetch empty responses
packages/next/src/server/base-server.ts+4 −0
@@ -1614,6 +1614,10 @@ export default abstract class Server<ServerOptions extends Options = Options> {!(is404Page || pathname === '/_error')) {res.setHeader('x-middleware-skip', '1')+res.setHeader(+'cache-control',+'private, no-cache, no-store, max-age=0, must-revalidate'+)res.body('{}').send()return null}
Export RedirectType from next/navigation (#54729)
packages/next/src/client/components/navigation.ts+1 −1
@@ -238,5 +238,5 @@ export function useSelectedLayoutSegment(return selectedLayoutSegments[0]}-export { redirect, permanentRedirect } from './redirect'+export { redirect, permanentRedirect, RedirectType } from './redirect'export { notFound } from './not-found'
packages/next/src/client/components/redirect-boundary.tsx+0 −1
@@ -26,7 +26,6 @@ function HandleRedirect({const router = useRouter()useEffect(() => {-// @ts-ignore startTransition existsReact.startTransition(() => {if (redirectType === RedirectType.push) {router.push(redirect, {})
packages/third-parties/scripts/update-third-parties.js+0 −3
@@ -23,10 +23,7 @@ function generateComponent(thirdParty) {let props = ''if (stylesheets?.length > 0) {-// TODO: Remove ts-ignore after new <Script> component is published-// New <Script> accepts stylesheet as a param. Otherwise it throws errorprops += `-// @ts-ignorestylesheets={${JSON.stringify(stylesheets)}}`}
packages/third-parties/src/google/index.tsx+0 −1
@@ -30,7 +30,6 @@ export function YoutubeEmbed(args: Types.YoutubeEmbed) {<Scriptsrc={`https://cdn.jsdelivr.net/gh/paulirish/lite-youtube-embed@master/src/lite-yt-embed.js`}strategy="lazyOnload"-// @ts-ignorestylesheets={['https://cdn.jsdelivr.net/gh/paulirish/lite-youtube-embed@master/src/lite-yt-embed.css',]}
test/development/acceptance-app/fixtures/app-hmr-changes/app/(post)/components/tweet.tsx+0 −1
@@ -22,7 +22,6 @@ export async function Tweet({ id, caption }: TweetArgs) {return (<div className="my-6"><div className="flex justify-center">-{/* @ts-ignore */}<ReactTweet id={id} components={components} /></div>{caption && <Caption>{caption}</Caption>}
test/e2e/app-dir/app-prefetch/prefetching.test.ts+0 −1
@@ -1,7 +1,6 @@import { createNextDescribe } from 'e2e-utils'import { check, waitFor } from 'next-test-utils'-// @ts-ignoreimport { NEXT_RSC_UNION_QUERY } from 'next/dist/client/components/app-router-headers'const browserConfigWithFixedTime = {
test/e2e/app-dir/metadata-dynamic-routes/app/twitter-image.tsx+0 −1
@@ -1,5 +1,4 @@import { ImageResponse } from 'next/server'-// @ts-ignoreimport { ImageResponse as ImageResponse2 } from '@vercel/og'// Node.js: Using @vercel/og external package, and should be aliased to "next/server" ImageResponse
test/e2e/app-dir/metadata/app/basic-edge/client.tsx+0 −1
@@ -4,7 +4,6 @@ import ReactDOM from 'react-dom'// NOTE: atm preload/prefetch/prefetchDNS are only supported in the SSR and client, not in RSC yetexport default function Client() {-// @ts-ignoreReactDOM.preload('/api/preload', { as: 'script' })// @ts-ignoreReactDOM.preconnect('/preconnect-url', { crossOrigin: 'use-credentials' })
More files changed — see the full commit.
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2023-46298
- WEBhttps://github.com/vercel/next.js/issues/45301
- WEBhttps://github.com/vercel/next.js/pull/54732
- WEBhttps://github.com/vercel/next.js/commit/20d05958ff853e9c9e42139ffec294336881c648
- PACKAGEhttps://github.com/vercel/next.js
- WEBhttps://github.com/vercel/next.js/compare/v13.4.20-canary.12...v13.4.20-canary.13