A Next.js OG-image bug can run code on your server. Patch to 16.3.6.

A Next.js OG-image bug can run code on your server. Patch to 16.3.6.

Vercel shipped an out-of-band Next.js release on September 22, and if you generate Open Graph images from your app, read this before anything else today. The update, versions 16.3.6 and 15.5.26, closes a critical remote code execution flaw in next/og, tracked as CVE-2026-94545 (advisory GHSA-vcvr-r3jv-pc5j). The CVSS score is 9.5. That is about as bad as a framework bug gets.

The flaw lives in the Node.js version of ImageResponse, the helper most teams use to build dynamic share cards: the little title-and-thumbnail previews that pop up when someone drops your link in Slack, LinkedIn, or a group chat. Under the hood, ImageResponse hands your JSX to Satori, which turns it into SVG, then renders that SVG into a PNG. The root cause sits one layer down in Satori, which has its own advisory (GHSA-wx4j-mvgx-mqwp): it did not escape certain values before writing them into the SVG. So a value you thought was plain text could break out and be read as live SVG markup. When the renderer then processes that markup, attacker-controlled input can end up executing on your server.

Are you actually exposed?

The condition that makes you vulnerable is specific, and worth being honest about. You are exposed if you run the Node.js ImageResponse (the App Router default when a route does not opt into Edge) and you push a value a visitor controls into the image. A word pulled from the URL. A profile name. A query-string parameter. That is not an exotic pattern. It is the exact recipe everyone uses to make one share card per article work. If your OG route only ever renders hard-coded titles or values from a trusted database, you are not in the blast radius. The Edge implementation of ImageResponse is not affected either.

Affected versions are Next.js 16.2.0 through 16.3.5. The fix is 16.3.6. Vercel also cut 15.5.26 the same day, but that release is hardening only; the 15.x line was not vulnerable to the RCE.

The part that trips people up

Here is where we spend a lot of our time during pentesting engagements: patching the framework is necessary but not always sufficient. npm install next@16.3.6 upgrades the Satori that Next.js bundles. It does nothing for a satori or @vercel/og you added to your own package.json. We see this constantly. A team bumps the headline dependency, the scanner still lights up, and nobody can work out why. If you use Satori directly anywhere, upgrade it to 0.33.5 or later on its own, and check what your lockfile actually resolves rather than trusting the top-level version.

What to do this week

Start by grepping your codebase for next/og, opengraph-image, and twitter-image. If nothing comes back, you can breathe and schedule the upgrade on your normal cadence. If something does come back, treat every one of those routes as reachable until you have looked at it. Ask one question per route: can a visitor influence any value that reaches the image? Then upgrade to 16.3.6 (or 15.5.26 on the 15 line), reinstall so the lockfile updates, rebuild, and redeploy. Confirm the resolved Satori version in the built output, not just in package.json.

If you genuinely cannot ship today, Vercel's stated workaround is blunt but it holds: stop feeding untrusted input into the image. Swap the dynamic value for a safe placeholder, or move the route to the Edge runtime if it runs there without changes. Neither is a long-term answer. Both buy you a day.

The bigger lesson

There is a pattern here, and it is the same one Log4Shell and the recent axios prototype-pollution bug taught us. The dangerous vulnerabilities are rarely in your own code. They are one or two layers down, in a widely trusted library that a framework pulls in for you. When we build cloud-native apps for enterprise clients, this is why we insist on a real software bill of materials and automated dependency scanning wired into CI, not a quarterly audit. You want the tooling to tell you "you ship Satori 0.31, it is now vulnerable" the morning the advisory drops, not three weeks later when a customer forwards you a scan.

The injection-to-RCE shape is also a useful reminder for how you think about any renderer, on the backend or inside a mobile app. Anything that takes structured input and produces a document (SVG, HTML, a PDF, a WebView payload) is a place where "just text" can quietly become "executable." In our native iOS and Android projects we apply the same rule to anything that renders server-driven content: escape at the boundary, and never assume the layer below did it for you. Satori's fix was exactly that, escaping text and attribute values before they reach the SVG. The framework had trusted the library to do something the library was not doing.

If you want a second pair of eyes on which of your next/og routes a visitor can actually reach, that is routine work for us. See how we approach web application security. And if a critical advisory landing on a Friday afternoon with nobody sure which routes are exposed sounds familiar, let's talk.

expert-analysisnextjssecuritytech-newsvulnerabilityweb-development