One bug, two scores: the Next.js ImageResponse flaw

The same escaping bug is moderate in Satori and critical in Next.js. What changed is where an attacker's value can travel. That is the lesson for any product.

Illustration: One bug, two scores: the Next.js ImageResponse flaw

On 22 September 2026, Vercel shipped an out-of-band Next.js release for a flaw in ImageResponse, the function that draws social-preview images. The same bug carries two severity scores. In Satori, the library that renders the image, it is rated 5.3, moderate. In Next.js it is rated 9.5, critical. Nothing about the bug changed between the two ratings. What changed is where an attacker's value can travel once it gets in.

What happened

ImageResponse takes markup, draws it with Satori and returns a PNG. Satori did not escape certain values before writing them into its SVG output, so a crafted value could be read as SVG markup, not text. On its own that is a moderate injection. Inside Next.js on the Node.js runtime, that markup can reach other libraries the framework depends on, and Vercel's advisory ↗ says plainly that this can lead to remote code execution. The fix is Next.js 16.3.6 ↗, released on 22 September. Versions 16.2.0 to 16.3.5 are affected; Next.js 15 and the Edge runtime are not. Two researchers reported it through Vercel's bug bounty ↗. As of 23 September, The Hacker News ↗ found no public exploit and no report of attacks.

One bug, two scores

The scoring gap is the whole story. Satori's advisory ↗ rates the bug by what it does in Satori: crafted values are read as SVG markup. Vercel rates it by what that markup can reach inside a Next.js server. The same unescaped string is a broken image in one place and a shell on your server in another, and no line of code tells you which. You only learn it by following the value from the request to everything it touches. That is not a scanner's job. It is the product team's.

A library rates a bug by what it does in the library. A product has to rate it by what the value can reach.MATT Research

How the value gets in

The vulnerable shape is ordinary. Vercel's own example reads a value from the request URL and places it inside the image's SVG title. Preview images are a growth feature: teams put the page title, a campaign name or a user's handle into them because that is what makes a shared link look good. The value is attacker-controlled the moment it comes from a URL, and it is rendered by a component nobody thinks of as an input handler. The risk came in through the marketing stack, not the login page. It is also the shape of code that gets waved through review, short and working, dangerous only in what the value reaches: the checks reviewers skip.

Your scanner did not see it

On the day of disclosure, npm audit did not flag Next.js 16.3.5 and the CVE was not yet in GitHub's advisory database, according to The Hacker News. Dependency scanners report a bug after someone files it. They cannot see the pattern before it has a number. A rule can. Image generation never renders a value an outsider controls: that sentence was true and checkable on 21 September, the day before the CVE existed.

Patching main is not patching your surface

Netlify's guidance ↗ adds a line most write-ups skip: preview and branch deployments keep running the version they were built with until someone deletes them. A team can upgrade production on Monday and still have a dozen preview URLs on the old release, each reachable, each rendering images from a query string. The surface is every deployment that answers, not the one on the domain.

The rule and the check

Turned into ExploitOps, this disclosure is one rule and one check. Declare: no value an outsider controls reaches the SVG content, attributes or styles of a generated image. Observe: list every route that builds an image and trace where each value comes from. Prove: send a crafted value to each route, confirm it is escaped or rejected, and run that on every release. Fix: upgrade to 16.3.6, and where the upgrade has to wait, escape the value as XML or drop it from the image, as Netlify advises. Remember: keep the check, because the next renderer will have the same bug under a different name. The full pattern, with the signals a reviewer can look for, is in the attack library, one of the rules we write down in plain English. MATT's own site does not use Next.js; its preview images are pre-made, not rendered from request values, which is one more way to hold the rule.

Questions

Is my app affected by CVE‑2026‑94545?

Only if three things are true: you run Next.js 16.2.0 to 16.3.5, you use ImageResponse from next/og on the Node.js runtime (the default), and you pass a value an outsider controls, such as a query parameter, into the image's SVG content, attributes or styles. Next.js 15 and the Edge runtime are not affected. Upgrade to 16.3.6 either way.

Why is the same bug rated 5.3 by Satori and 9.5 by Next.js?

Satori scores what the bug does in Satori: a crafted value can be read as SVG markup. Next.js scores what that markup can reach inside a Next.js server, other dependencies with their own flaws, which Vercel says can lead to remote code execution. Severity depends on where a value can travel, not on the line that failed to escape it.

What if I cannot upgrade right away?

Vercel's workaround is to keep attacker-controlled values out of the SVG content, attributes and styles that the Node.js ImageResponse renders. Netlify suggests escaping untrusted input as XML before rendering, or leaving it out of the image. Treat both as temporary. Next.js 16.3.6 is the fix.

Do old preview deployments matter?

Yes. A preview or branch deployment keeps the Next.js version it was built with. Netlify recommends deleting vulnerable previews and branch deploys, because automatic cleanup can take time. Count every deployment that still answers as part of your surface.

Has it been exploited?

As of 23 September 2026, The Hacker News found no public exploit code and no reports of attacks using the flaw. That can change quickly once a patch shows attackers where to look, which is why the fix and the check both matter.

Know where you stand.

Book a discovery call→