Skip to main content
SecuritySeptember 24, 20264 min read

Next.js 16.3.6 Patches an RCE in next/og ImageResponse

Get technical support

Next.js on Kubernetes, done properly

Next.js 16.3.6 fixes a critical remote code execution bug in next/og ImageResponse. One thing your OG routes do decides whether you were exposed, and two days after release npm audit still had no record of it.

What Shipped on September 22

On September 22, 2026, Vercel released Next.js 16.3.6 together with security advisory GHSA-vcvr-r3jv-pc5j, a remote code execution bug in the ImageResponse class from next/og. The advisory rates it critical, 9.5 under CVSS 4.0, and names it CVE-2026-94545.

Every release from 16.2.0 up to but not including 16.3.6 is affected. The 16.3.6 release notes list this fix and nothing else, so the upgrade carries no unrelated changes. Coming from 16.3.4 you also pick up 16.3.5, which holds only backported bug fixes.

Vercel describes the Node.js implementation as "affected by an upstream vulnerability", so the flaw sits in a library that next/og depends on rather than in Next.js itself. The advisory does not name the library. The report is credited to RaghavMaheshwari124 and rafabd1.

The Condition That Decides Exposure

Being inside the version range is not enough on its own. Two things have to be true for a route to be exposed.

It has to use the Node.js ImageResponse. The Edge implementation is not affected. App Router routes run on Node.js unless they export runtime = "edge", so most opengraph-image files and image route handlers qualify.

And it has to pass attacker-controlled values into the SVG content, attributes or styles it renders. The advisory does not narrow that further, so the safe reading is that any value an outsider can influence counts once it reaches the image. That includes a query string or a header, and it also includes what users wrote and you stored: a display name, a product title from a seller, a comment.

A route like this one is exposed, because it prints whatever arrives in the URL:

// app/api/og/route.tsx
import { ImageResponse } from "next/og";

export async function GET(request: Request) {
  const title = new URL(request.url).searchParams.get("title") ?? "";
  return new ImageResponse(
    <svg width="1200" height="630">
      <text x="60" y="320" fontSize="64">{title}</text>
    </svg>
  );
}

A route that uses the request only as a lookup key, and renders what it finds in your own data, is not:

// app/articles/[slug]/opengraph-image.tsx
export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = getPostBySlug(slug);
  return renderOgImage({ title: post ? post.title : "Articles" });
}

An unknown slug there renders the constant fallback, and the slug itself never reaches the image.

Why Your Scanner Stayed Green

npm audit does not read vendor repositories. It sends your dependency versions to the npm registry, which answers from the GitHub Advisory Database, and an advisory a project publishes on its own repository reaches that database only after GitHub reviews it.

On September 24, two days after the release, we asked the registry's advisory endpoint about next 16.3.4 and got nothing back. The same query for 16.2.12 returned the two critical advisories from earlier in September, so the endpoint was answering normally and simply had no record of this one. OSV had no entry either, and CVE-2026-94545 did not yet exist on CVE.org or in NVD.

Dependabot, osv-scanner and every dashboard fed by those databases would have raised nothing for it for as long as that lasted. It will correct itself once the advisory is reviewed. The lesson outlasts it: when a framework you run announces a security release, compare the advisory's version range with your lockfile yourself instead of waiting for a red badge.

Checking Your App

Find the version. npm ls next shows what is actually installed. Anything from 16.2.0 to 16.3.5 is in range.

Find every image route. grep -rn "next/og" app src lists each file that builds an image.

Follow every value. For each of those files, trace what ends up in the rendered JSX back to where it came from. If any of it started outside your own data, the route is exposed until you upgrade.

Prove it. Request the image twice with two different nonsense values. If the two files are byte-identical, the input never reached the render:

curl -s https://example.com/articles/zz-check-one/opengraph-image -o one.png
curl -s https://example.com/articles/zz-check-two/opengraph-image -o two.png
cmp one.png two.png && echo "input does not reach the image"

Upgrade anyway. Run npm install next@16.3.6. A route that is safe today is one pull request away from printing a ?title= parameter.

If You Cannot Upgrade Today

The advisory's only workaround is to stop passing attacker-controlled values into what ImageResponse renders. In practice that means rendering from a fixed set: look the title up by slug and fall back to a constant when nothing matches, rather than printing whatever arrived.

The Edge implementation is not affected, but moving a route to the Edge runtime takes away Node.js APIs such as fs, so treat that as a bigger change than the upgrade it would replace.

If you run Next.js on your own servers, Deploying Next.js 16 to Kubernetes covers the rollout side of shipping a patch like this without downtime, and Kubernetes for Next.js is how we run it for teams who would rather not track advisories themselves.

Or read how we handle it in Kubernetes for Next.js.