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-17 | Result |
|---|---|
GET on /_next/image with custom image | 200, image/png |
Version in the running container (node_modules/next/package.json) | 16.2.7 |
Declared version in package.json | 16.2.12 |
Request with foreign absolute URL as url | 400 |
next/image in the component tree | not used |
output in next.config.ts | standalone, 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-vxw4 | CVE-2026-75604 | |
|---|---|---|
| Score | CVSS 4.0: 9.5 | CVSS: 9.0 |
| CVE assigned | no | yes |
| Platform | all | Windows only |
Affected next-Stände | >= 10.0.0, < 15.5.24, >= 16.0.0, < 16.3.3 | 13.4 bis 15.5.23, 16.0 bis 16.3.2 |
| Fixed in | 15.5.24 and 16.3.3 | 15.5.24 and 16.3.3 |
| Cause | libheif, AVIF parser | Character class without backslash |
| Workaround | Disable AVIF optimization | none |
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.
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.
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.
- 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.
- Read the version on the instance, not in the repo:
node_modules/next/package.json, fieldversion. 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. - Set
images.unoptimized: trueif 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. - Upgrade to
>= 16.3.3, in the 15.x line to15.5.24. The intermediate step "AVIF is off anyway" is not a strategy, but a state that the framework itself expires. - After a Windows incident: rotate
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY, do not upgrade alone. - 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
- GitHub Advisory GHSA-2xp9-vwfh-vxw4, published 2026-09-08, reviewed, CVSS 4.0 9.5, no CVE
- libheif Advisory GHSA-g89c-p67h-r497, the upstream cause behind
sharp - Next.js Release v16.3.3, the fix release for both reports
- Next.js Release v16.3.5, current line from September 11, 2026
- Fortbridge: Next.js Path Traversal to RCE, CVE-2026-75604 reproduction, published 09/04/2026
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.
senn-tech