senn-techsenn-tech
Security
Security2026-09-18· By Franz Senn

Two critical gaps in Next.js, a fix train, and our site had the endpoint open

One command, two seconds, one line of response. On September 17, 2026, our own production domain answered https://senn-tech.com/_next/image?url=%2Fbrand%2Fmark.png&w=640&q=75 with HTTP 200 and content-type: image/png. The Image Optimization Endpoint is open and serves images. The running container on the serving host reported next 16.2.7, while the package.json of the same deployment repo declares 16.2.12. Both versions are in the same zone: >= 16.0.0, < 16.3.3. Two alerts, two independent root causes, both critical, both fixed in 16.3.3. We are writing this not as news, but as a finding for ourselves and for everyone running Next.js.

What we measured

Measurement on 2026-09-17Result
GET on /_next/image with custom image200, image/png
Version in the running container (node_modules/next/package.json)16.2.7
Declared version in package.json16.2.12
Request with foreign absolute URL as url400
next/image in the component treenot used
output in next.config.tsstandalone, images.unoptimized not set

The 400 is good news, not an all-clear. The allowlist works: the route does not fetch every image from the open web, it only accepts what is configured. For everything the allowlist passes through, the AVIF path remains open. And the more critical of the two bugs sits exactly on this path. The reverse proxy in front of the application (Caddy as a reverse proxy) did nothing wrong: /_next/image is a standard path without authentication, nothing about it is rejected by principle. You have to build a behavioral rule yourself.

Two gaps, one fix train

GHSA-2xp9-vwfh-vxw4CVE-2026-75604
ScoreCVSS 4.0: 9.5CVSS: 9.0
CVE assignednoyes
PlatformallWindows only
Affected next-Stände>= 10.0.0, < 15.5.24, >= 16.0.0, < 16.3.313.4 bis 15.5.23, 16.0 bis 16.3.2
Fixed in15.5.24 and 16.3.315.5.24 and 16.3.3
Causelibheif, AVIF parserCharacter class without backslash
WorkaroundDisable AVIF optimizationnone
Two critical alerts, one differenceGHSA-2xp9-vwfh-vxw49.5 · AVIF/libheif, no CVECVE-2026-756049 · Cache traversal, Windows only010
Half a point apart, two completely different causes. The higher score doesn't even have a CVE number. (Quelle: Own analysis of the two advisories, as of September 17, 2026)

The CVSS vector for 9.5 is clear: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H. Reachable over the network, no authentication, no user interaction, full impact on confidentiality, integrity, and availability, on the component and the underlying system. The listed weakness class is CWE-1395, "Dependency on Vulnerable Third-Party Component". This single entry explains more about the case than the entire advisory.

Next.js brand mark
The image optimizer ships with the framework and is never switched on. The path /_next/image is part of every project of this version. (Quelle: Next.js)

The bug is deeper

The root cause is not Next.js code. Next.js optimizes images via sharp, and sharp calls libheif, a C library for HEIF and AVIF files. The actual bug belongs to libheif and is reported there as GHSA-g89c-p67h-r497. The version table in the Next.js advisory is therefore not a statement about Next.js source code; it is derived from the dependency. Sending an AVIF file to the route lets a C parser process foreign input, and for a parser in C, a parse error is a memory issue, not a log entry. The advisory states that AVIF optimization remains disabled during the transition period until the fix is delivered. Release notes show when it returned: AVIF was active again with v16.3.4, and the current line is v16.3.5 from September 11, 2026, a pure backport change where next/image skips entries with 0 bytes in the disk cache.

From request to C parserRequesturl, w, q, unauthenticatedOptimizer-Routeis included with the server buildsharpNode binding for image processinglibheifC library, AVIF pathAVIF-ParserThis is where the 9.5 sits
Five steps between an open HTTP path and a C library. Next.js is the middleman in this chain, not the cause. (Quelle: GitHub Advisory GHSA-2xp9-vwfh-vxw4 and libheif Advisory GHSA-g89c-p67h-r497)

Our take: Your version is always part of a chain. The fix release for 16.3.3 carried two critical bugs that were unrelated, one with a CVE ID, the other without. Relying only on CVE IDs misses half the picture; relying only on your own version number misses the other half. We therefore tied the version check to our patch plan for SMBs: Check the advisory database, don't count news reports.

Why only Windows

CVE-2026-75604 affects the Next.js Incremental Cache. A single unescaped backslash in a path is enough to let an unauthenticated request escape the cache directory. On Linux this is harmless because the backslash is a valid character in a filename. On Windows it is the separator itself, turning a filename into a path and a path into traversal. The root cause is a character class for path separators that need escaping, which listed /: but not \. One character in a class, half a point difference in the score, and one platform is exploitable while the other is not. Fortbridge fully reconstructed the chain and published on September 4, 2026: file read, file write, and command execution depending on the application type. Vercel's advisory is clear: no workaround, the path is structurally present on Windows. The fix is an upgrade, and because server action payloads could be forged via traversal, an extra step is required: rotate NEXT_SERVER_ACTIONS_ENCRYPTION_KEY.

That is the lesson that bothered us the most. We run Linux, so this vulnerability did not exist for us. We never made this decision and never wrote it down. It happened as a side effect of the platform.

Checklist for your own booth

The status applies per deployment, not per company. Six points, the first takes 60 seconds per system. This is an afternoon run-through, not a project, not even for the small IT teams we have everywhere in the Tyrolean Unterland.

  1. Measure endpoints, on every Next instance that is publicly accessible:
curl -s -o /dev/null -w "%{http_code} %{content_type}\n" \
  "https://ihre-domain/_next/image?url=%2Flogo.png&w=640&q=75"

200 with image/... means: the route delivers. 404 means: it is not there. The second test, using a foreign absolute URL as url, is part of this. It separates an open route from an open route with an allowlist.

  1. Read the version on the instance, not in the repo: node_modules/next/package.json, field version. In our case, the measured value and the declared value differed, 16.2.7 vs 16.2.12, and both were affected. If you have multiple deployments, record the version per deployment. We do this in Forge on the service.
  2. Set images.unoptimized: true if you deliberately do not want to use the optimizer. This is a configuration switch, nothing more. Then you must re-run the curl command from step 1. What the configuration promises is not a finding.
  3. Upgrade to >= 16.3.3, in the 15.x line to 15.5.24. The intermediate step "AVIF is off anyway" is not a strategy, but a state that the framework itself expires.
  4. After a Windows incident: rotate NEXT_SERVER_ACTIONS_ENCRYPTION_KEY, do not upgrade alone.
  5. Write down assumptions, including the uncomfortable ones. A platform assumption that is not documented anywhere is no protection. And an endpoint that nobody watches is only noticed when someone talks to it; monitoring that only answers or does not answer will not detect such things. How to fit this into a night shift is described in the DNS SERVFAIL monitoring.

To be honest, because this is our own case: at the time of measurement, our page was in the affected area, the route was open, and the version was too low. We upgraded to 16.3.5; 16.3.3 is the fix. The rollout to the delivery host is the next step. Nothing was exploited, and we won't write "this never affected us," because that isn't true after this measurement.

Further Reading

Questions?
Does the endpoint disappear if we don't use next/image at all?+

No, and that is where most people go wrong. The optimizer route belongs to the server build, not to the component, and with `output: standalone` it is included automatically. We had zero `next/image` usage in the component tree and still got HTTP 200 on the route. If you do not need the function, set `images.unoptimized: true` in `next.config.ts` and measure again.

Where does it really say which Next version we are running?+

Check the version field in node_modules/next/package.json on the instance that serves the content. The package.json in the repository is an intention, not a report of the live state. In our case, the two values differed by one version line: measured 16.2.7, declared 16.2.12. Both values appeared in the affected area.

Does the Windows vulnerability also affect our Linux servers?+

According to the available data, no: the path separator bug only affects places where a backslash is interpreted as a separator, meaning Windows. On Linux, the character is a normal part of a filename. Do not rely on this assumption without checking, as this exact assumption was nowhere documented for us. For Windows: there is no workaround, only an upgrade and then a rotation of NEXT_SERVER_ACTIONS_ENCRYPTION_KEY.