npm audit says you have zero vulnerabilities, and the critical RCE in your image pipeline is still there
On August 25, 2026 Next.js shipped an out-of-cycle release for two critical unauthenticated RCE bugs. One is a Windows-only path traversal, CVE-2026-75604, CVSS 9.0. The other is not a Next.js bug at all: a heap buffer overflow in libheif, CVSS 9.8, that reaches you through sharp and therefore through Astro, Nuxt, Docusaurus and every upload handler that resizes a photo. We checked what the standard tooling reports. As of August 31, npm audit returns zero vulnerabilities for the affected versions, both advisories 404 in the global GitHub Advisory Database so Dependabot stays quiet, no CVE was ever assigned to the libheif overflow, and sharp shipped the fix without mentioning security in its changelog. Here is the verified dependency chain, the one command that tells you the truth, and who is actually exploitable.
Most security advice for a small business with a website ends at the same instruction: keep your dependencies patched, and let the tooling tell you when something is wrong. Turn on Dependabot. Run npm audit in CI. Fix what it flags.
That is good advice and it is what we tell clients. This week it produced a wrong answer, and the way it produced a wrong answer is worth more of your attention than the vulnerability itself.
On August 25, 2026, the Next.js team published an out-of-cycle security release covering two critical severity vulnerabilities, both leading to unauthenticated remote code execution. We went and checked what the ordinary tooling says about the affected versions. Six days later, on August 31, here is what it says:
| Check | What it reports |
|---|---|
npm audit on next@16.3.2 (vulnerable) |
0 vulnerabilities |
npm audit on next@15.5.23 (vulnerable) |
0 vulnerabilities |
npm audit on sharp@0.35.3 (ships vulnerable libheif) |
0 vulnerabilities |
| npm advisory API for all three | empty object, {} |
github.com/advisories/GHSA-p293-qw3h-jr36 |
HTTP 404 |
github.com/advisories/GHSA-2xp9-vwfh-vxw4 |
HTTP 404 |
| CVE assigned to the libheif overflow | none |
sharp changelog entry mentioning the fix |
none |
Every one of those is a real result from a real command, run while writing this. The vulnerabilities are real too. Both things are true at once, and that gap is the actual story.
The two Next.js bugs, briefly
CVE-2026-75604, Windows path traversal, CVSS 9.0. Applications using both the Pages Router and the App Router without Cache Components, running on a Windows filesystem, can be pushed into unauthenticated remote code execution. Affected: Next.js 13.4 through 15.5.23, and 16.0 through 16.3.2. Patched in 15.5.24 and 16.3.3. Vercel’s advisory is blunt about the options: “There is no known workaround for affected windows-hosted applications.” Linux and macOS are not affected.
If you host Next.js on IIS, on a Windows VM, or on a Windows container because that is what your line-of-business stack runs on, this is your Monday morning. If you are on Linux, which is nearly everyone, skip it.
The AVIF one, no CVE, CVSS 9.5 in Next.js terms and 9.8 upstream. This is the one that matters more, because it is not a Next.js bug. Next.js uses the sharp image processing package. sharp uses libvips. libvips uses libheif to decode HEIF, HEIC and AVIF files. The bug is in libheif, and Next.js is simply one of thousands of things that call it.
The patched Next.js releases do not fix it. They disable AVIF optimisation entirely as a stopgap, in the maintainers’ words, “until an upstream fix is propagated.”
What the libheif bug actually is
Tracked as GHSA-g89c-p67h-r497, affecting libheif up to and including v1.23.1, fixed in v1.23.2. CVSS 9.8. No CVE assigned.
It is a heap buffer overflow, an out-of-bounds write, in HeifPixelImage::scale_nearest_neighbor(). The mechanism is a four-step failure that is worth reading if you ever write code that parses attacker-supplied file formats, because none of the four steps is obviously a bug on its own:
transfer_channel_from_image_as()appends source planes without rejecting a channel that already exists on the destination. The upstream source carries an unresolved// TODOat exactly that spot.find_storage_for_channel()returns only the first match, so the functions that report bit depth and memory size describe only the first alpha plane even when duplicates exist at different bit depths.- The scaler therefore allocates a destination alpha plane sized for the first entry, typically 8 bits per sample, then iterates every source entry. Reaching a 10 or 12-bit duplicate, it casts the 8-bit buffer to
uint16_t*and writes two bytes per sample into a one-byte-per-sample allocation. - Nested identity-derivation (
iden) and auxiliary (auxl) item references are what create those duplicates, andcheck_decoded_image_size()returns success unconditionally, so the dimension mismatch that forces the scaling path is never rejected.
A crafted file with five items produces an allocation of 16,384 bytes and a write of 32,768 bytes. The overflow length and the bytes written are both attacker-controlled through the container structure and the HEVC bitstream.
The upstream advisory is explicit about the blast radius: any application calling heif_decode_image() is affected, with no special API options or unusual calling patterns required. That last clause is the one to hold on to.
A correction to the coverage
Some reporting on this said the AVIF issue is “only enabled when sites explicitly configure image/avif in next.config.js.” That is worth pulling apart, because it is the difference between “not my problem” and “my problem.”
The formats setting in Next.js is real and the default really is formats: ['image/webp'], with AVIF opt-in. But formats controls the output format that the optimiser produces. The libheif bug fires on decode. If an AVIF or HEIC file reaches the optimiser as input, libheif parses it regardless of what you asked the output to be. That is what “any application calling heif_decode_image()” means.
So the safer question is not “did I turn on AVIF output” but “can anyone hand my server an image file whose bytes they chose.” We flag this because the two questions have very different answers for a site with user uploads.
A second correction: at least one outlet reported on August 27 that libheif v1.23.2 had not been published. It had. The release atom feed timestamps it 2026-08-25T11:39:19Z, the same day as the Next.js release.
The dependency chain, verified
This is the part we could not find written down anywhere, so we tested it. Two throwaway installs, one command each:
npm i sharp@0.35.3 && node -e "console.log(require('sharp').versions.heif)"
# 1.23.1 <- vulnerable
npm i sharp@0.35.4 && node -e "console.log(require('sharp').versions.heif)"
# 1.23.2 <- patched
So the fix does exist for everyone downstream of sharp, and it landed fast:
| Date | Release | libheif inside |
|---|---|---|
| Aug 25, 2026 | libheif v1.23.2 | 1.23.2 |
| Aug 25, 2026 | Next.js 16.3.3 / 15.5.24 | AVIF path disabled |
| Aug 26, 2026 | sharp-libvips v1.3.3 | 1.23.2 |
| Aug 26, 2026 | sharp v0.35.4 | 1.23.2 |
sharp 0.35.4 is the fix. Now read its changelog entry for that release. It lists six items: bounding resize dimensions to a coordinate limit, bounding composite left and top, rounding palette bit depth for png and gif colours, a tiff subifd input option, correcting info.pages, and stream handling. It links the sharp-libvips v1.3.3 release. It does not contain the words security, libheif, AVIF, or overflow.
Nothing improper happened there. sharp’s changelog documents sharp’s changes, and this was a prebuilt-binary dependency bump. But if you are the person who reads release notes before upgrading, and you read that entry, you would reasonably conclude 0.35.4 is a routine patch you can leave until next sprint.
Why nothing told you
Four independent gaps line up, and any one of them alone would have been survivable.
No CVE. The security bulletin covering the libheif 1.23.2 release lists eight vulnerabilities in versions 1.0.0 through 1.23.1, including the CVSS 9.3 heap overflow and two more out-of-bounds writes rated 8.4 and 8.3. Every one is listed with no CVE ID assigned. If your vulnerability management process keys on CVE numbers, and most do, there is nothing to match against.
Not in the global GitHub Advisory Database. Both Next.js advisories exist, but as repository advisories on vercel/next.js. Fetch either GHSA from github.com/advisories/ and you get a 404, verified on August 31. That matters mechanically, not just cosmetically: GitHub’s own documentation states that only advisories reviewed by GitHub trigger Dependabot alerts, and that Dependabot does not create alerts for unreviewed advisories. A repository advisory that has not been promoted to the global database is not going to page you.
Not in the npm advisory database. The endpoint npm audit calls returns an empty object for next@16.3.2, next@15.5.23 and sharp@0.35.3. We control-tested the same endpoint against lodash@4.17.15 and it returned the expected advisories immediately, so the query works and the silence is real. There is even precedent for what a fix would look like: npm already carries advisories titled “sharp vulnerability in libwebp dependency CVE-2023-4863” and “sharp inherited vulnerabilities in libvips.” The mechanism exists for exactly this situation. It just has not been used here yet.
Silent in the changelog, as above.
The result is a critical unauthenticated RCE in a dependency that around 45 million weekly downloads pass through, where every automated check a normal team runs reports green.
We have made this argument before in a different key, when we wrote about CISA moving to risk-based patching and about npm supply chain attacks. The through-line is the same: automated dependency scanning is a floor, not a ceiling. It catches the things that made it into a database. It is structurally blind to the window between disclosure and cataloguing, and that window is exactly when exploitation is cheapest.
Are you actually exploitable
Being on a vulnerable version and being exploitable are different states. The gate is simple: can an unauthenticated stranger choose the bytes of an image your server decodes?
Ordered roughly by how worried to be:
High. You accept image uploads. Profile photos, product images from vendors, attachments on a contact form, anything that hits a resize pipeline. This is the case the bug was written for. The attacker picks the file.
High. Your Next.js images.remotePatterns or images.domains includes a wildcard, a user-supplied hostname, or a third party you do not control. The optimiser will fetch and decode whatever is at that URL.
Medium. You proxy or hotlink images from a CMS, a marketplace feed, a syndication partner, or any source where someone other than you can replace a file. A WordPress media library with contributor accounts counts.
Low. Every image is a local file committed to your repository, remotePatterns is empty, and there is no upload path anywhere. The vulnerable code is present and will never be handed hostile input by an outsider. Patch it on your normal cadence, but do not lose an evening.
Separate and unrelated to all of the above: if you are hosting Next.js on Windows, CVE-2026-75604 does not care about any of this, and there is no workaround. Upgrade.
Where you host changes the picture too, and both major platforms published clear statements. Vercel disabled AVIF optimisation across its managed image service when the bug was identified and its Next.js runtime is Linux, so it says applications hosted there need no upgrades, configuration changes or redeploys. Netlify rewrites /_next/image requests to its own Image CDN at the edge, so the Next.js optimisation API is never invoked, and its functions run on Linux. Netlify still recommends upgrading as good practice. If you self-host on your own VM or container, neither of those mitigations applies to you and you own the whole problem.
What to do this week
1. Find out what libheif you are actually running. Not what your lockfile says about sharp, what the binary reports:
node -e "const s=require('sharp'); console.log('sharp', s.versions.sharp, '| libvips', s.versions.vips, '| libheif', s.versions.heif)"
Anything below 1.23.2 is the vulnerable library. Run this in every project that processes images, not just the ones you think of as “the website.”
2. Upgrade sharp, and upgrade Next.js if you use it.
npm install sharp@^0.35.4
npm install next@16.3.3 # or next@15.5.24 on the maintenance line
Then re-run the version check above. On pnpm or yarn workspaces, confirm the transitive @img/sharp-libvips-* packages actually moved to 1.3.3, because a stale lockfile will happily keep the old prebuilt binary. The version check is the proof; the lockfile is the claim.
3. Remember that sharp is not only a Next.js dependency. It is the default image processor for Astro, it sits under Nuxt Image, Docusaurus, Gatsby, Remix setups, next-optimized-images, and a very large number of hand-rolled upload handlers that call sharp(buffer).resize().toBuffer(). Neither Astro nor most of those projects has published an advisory for this, which is not negligence; there is nothing for them to advise about beyond “upgrade sharp,” which you can do yourself today. The comparison we drew in Astro vs Next.js vs WordPress holds here in an uncomfortable way: the frameworks differ enormously at the top of the stack and converge on exactly the same C library at the bottom.
4. If you cannot upgrade immediately, reject HEIF-family input before it reaches sharp. Check the container brand rather than trusting the file extension or the client’s Content-Type. In an ISOBMFF file, bytes 4 through 8 are the literal string ftyp and bytes 8 through 12 are the brand:
function isHeifFamily(buf) {
if (buf.length < 12) return false;
if (buf.toString('latin1', 4, 8) !== 'ftyp') return false;
const brand = buf.toString('latin1', 8, 12);
return ['avif', 'avis', 'heic', 'heix', 'hevc', 'hevx', 'mif1', 'msf1'].includes(brand);
}
This is a stopgap, not a fix. It reduces the surface while you schedule the upgrade properly. Do the upgrade.
5. Check your distro packages separately. libheif is not only an npm concern. It is linked into ImageMagick’s HEIF delegate, GNOME thumbnailers, Nextcloud previews and a long tail of desktop and server software. Ubuntu’s USN-8649-1 on August 19 fixed two denial-of-service issues, CVE-2026-62289 and CVE-2026-62292, shipping 1.21.2-3ubuntu0.4 on 26.04 LTS and 1.17.6-1ubuntu4.7 on 24.04 LTS. Both of those base versions predate 1.23.2, and the critical overflow carries no CVE for a distro tracker to key on. Check your own vendor’s tracker rather than assuming a recent update covered it.
6. Write down who is responsible for image processing. This is the boring one and it is the one that pays. Every business we have helped clean up after a dependency incident had the same gap: the image pipeline was set up once, by someone, during a project, and nobody owned it afterwards. It is not in the asset inventory. It has no release notifications going anywhere. The fix is an afternoon, once.
What we did on our own site
The site you are reading is Astro, and it uses sharp. We are not going to write this post and skip our own check, so here is what it returned before we published.
Our lockfile had sharp@0.35.3, with @img/sharp-libvips-*@1.3.2 underneath it. Running the version command against the installed binary:
BEFORE -> sharp 0.35.3 | libvips 8.18.3 | libheif 1.23.1
Vulnerable library, present in our build. npm audit on that same tree returned zero vulnerabilities.
Exploitable? No, and the reason is worth being precise about. Our astro.config.mjs sets image.domains: [] and image.remotePatterns: [], every image on this site is a local file committed to the repository, and there is no upload path anywhere on it. An outsider has no way to hand our build a file they wrote. That is the “Low” row in the table above, and it is low because of a configuration decision made months ago for unrelated reasons, not because anything warned us.
We upgraded anyway, in the same commit as this post:
AFTER -> sharp 0.35.4 | libvips 8.18.6 | libheif 1.23.2
Which is the point of the whole exercise. We were not exploitable, and we still had a critical heap overflow sitting in a linked C library in our own dependency tree, six days after the fix shipped, with every automated check green. We only knew because we went and read a changelog that does not mention it, in a project three levels below the one that made the news.
The uncomfortable general lesson
The industry has spent five years telling small businesses that dependency scanning is the answer to supply chain risk, and it is a genuinely good answer for the steady state. But it is a lagging indicator by construction. A scanner can only report what a human has already written into a database, in a format the scanner can match, against a package name the scanner knows about.
This bug failed all three conditions simultaneously. It had no CVE for a matcher to match. It sat in a repository advisory the global database had not ingested. And it lived in a C library that is not an npm package at all, reached through two layers of prebuilt binaries that no manifest names.
None of that is anyone’s fault in particular. Advisories take time to review. Maintainers ship the fix before the paperwork. But it means the honest version of the advice is longer than “turn on Dependabot”:
- Know what your software actually links against, not just what your
package.jsondeclares.sharp.versionstook one command and told us more than any scanner did. - Treat a dependency bump in a security-adjacent library as security-relevant even when the changelog is silent about it.
- When a framework you use ships an out-of-cycle release, read the upstream advisory it points at, not the summary. The upstream advisory here was the one that said “any application calling
heif_decode_image().” - Accept that for roughly a week after any disclosure, your tooling is going to tell you that you are fine.
If you are not sure what your site’s image pipeline links against, or who owns it, or whether anyone would notice a release like this one, get in touch. Running the check is a fifteen-minute job. It is a much shorter job than the alternative.
Sources
- August 2026 Security Release, Next.js blog, August 25, 2026, for both vulnerabilities, the patched versions and the AVIF stopgap wording
- Update: August Next.js Security Release, Next.js blog, August 25, 2026, for the release being moved forward
- GHSA-p293-qw3h-jr36, Vercel repository advisory, for CVE-2026-75604, CVSS 9.0, the affected version ranges and the absence of a workaround
- GHSA-2xp9-vwfh-vxw4, Vercel repository advisory, for the AVIF issue, CVSS 9.5 and the affected ranges
- GHSA-g89c-p67h-r497, libheif upstream advisory, for the
scale_nearest_neighbor()mechanism, the four-step root cause chain, the 16,384 byte overflow figure and theheif_decode_image()scope statement - libheif releases, for v1.23.2 and its publication timestamp of 2026-08-25T11:39:19Z
- sharp v0.35.4 changelog, for the six documented changes and the sharp-libvips v1.3.3 link, and for what is not mentioned
- sharp-libvips v1.3.3, for the bundled heif 1.23.2, against v1.3.2’s heif 1.23.1
- next/image formats, Next.js documentation, for
formats: ['image/webp']being the default and AVIF being opt-in - Vercel applications are protected from Next.js August 2026 security vulnerabilities, Vercel changelog
- Next.js security release (August 2026): what to know, Netlify changelog, for the
/_next/imageedge rewrite - Multiple vulnerabilities in libheif, SB2026082574, August 25, 2026, for the eight issues in 1.0.0 through 1.23.1 and their unassigned CVE status
- USN-8649-1: libheif vulnerabilities, Ubuntu, August 19, 2026, for the shipped package versions on 24.04 and 26.04
- About Dependabot alerts, GitHub documentation, for only GitHub-reviewed advisories triggering alerts
- Next.js Patches Critical AVIF and Windows Flaws Enabling Unauthenticated RCE, The Hacker News, August 2026, for the download-volume figure and as the source of the two claims corrected above
- Direct verification, August 31, 2026:
npm auditand the npm bulk advisory endpoint againstnext@16.3.2,next@15.5.23,sharp@0.35.3andsharp@0.35.4;sharp.versions.heifon installed 0.35.3 and 0.35.4; HTTP status of both GHSA pages ongithub.com/advisories/