Next.js 16.3.6 fixes an RCE in next/og: how to patch and audit
Next.js 16.3.6 patches GHSA-vcvr-r3jv-pc5j, a remote code execution flaw in next/og ImageResponse. Here's how to find exposure, upgrade, and harden OG routes.
- Next.js
- Operations

A security advisory lands for the framework your storefront or marketing site runs on. The label is remote code execution, and the affected component is next/og, the thing that renders social preview cards. The fix is a patch release. Someone on the team says the OG images are cosmetic and the upgrade can wait for the next sprint.
That is the wrong call. The component's job is cosmetic. Its exposure is not. An OG image route is a public, unauthenticated endpoint that runs server-side code on every request. RCE on that route means RCE on the server, whatever the route was meant to draw.
What the 16.3.6 release actually says
The v16.3.6 release notes are short. The release contains a security fix for GHSA-vcvr-r3jv-pc5j, titled "Remote Code Execution in next/og ImageResponse". That is the whole announcement.
A few things the notes do not say, and that we will not guess at:
- No CVE identifier is listed in the release notes.
- They do not describe the exploit mechanism or list affected version ranges.
- They do not list a severity score.
For those details, read the advisory itself on GitHub. Don't rely on a paraphrase, including this one.
The same repository has recently published v16.4.0-canary.37 through canary.39 and v15.5.26. The 16.3.6 notes don't say whether those releases include the same fix. If you're on the 15.x line or tracking canary, check the advisory's patched-versions list before you assume you're covered. If you're on 16.x stable, the target is 16.3.6. Don't stop at an earlier 16.3.x patch.
Why an image route is not a low-risk surface
OG image routes tend to get less review than the rest of an app, for three reasons.
First, they're usually dynamic by design. The whole point is a per-page card, so the route takes a slug, title, price, or product name from the URL and renders it. That's user-controlled input going straight into a renderer.
Second, they're public. Social crawlers have to reach them without cookies, so they sit outside whatever auth middleware covers the rest of the app.
Third, nobody watches them. A 500 on /api/og doesn't page anyone, and a spike in requests to it looks like a link getting shared.
Put those together and you get the endpoint an attacker would pick. Patch it now and audit it properly.
Find every ImageResponse in the codebase
Start with a full inventory. App Router projects can generate OG images in several places, and the file conventions hide some of them.
# Direct imports of the affected module
grep -rn --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' \
-e "from 'next/og'" -e 'from "next/og"' -e 'ImageResponse' \
app pages src lib 2>/dev/null
# File-convention image routes, which may not be imported anywhere
find . -path ./node_modules -prune -o \
\( -name 'opengraph-image.*' -o -name 'twitter-image.*' -o -name 'icon.*' -o -name 'apple-icon.*' \) -print
For each hit, write down three things:
- Is it static or dynamic? A file-convention image that renders a fixed brand card at build time has much less exposure than a route handler that reads
searchParamson every request. - What input reaches the renderer? Follow every value from
request.url,params, orsearchParamsinto the JSX you pass toImageResponse. Include values that go through a database lookup first, because a slug that picks a row is still attacker-chosen. - What does it fetch? Fonts, remote images, and product photos loaded by URL. If any part of that URL comes from the request, flag it.
If you also depend on @vercel/og directly, check the advisory for whether that package is in scope. It's a separate package from next/og, so don't assume either way.
Then confirm what's actually installed. Your package.json range isn't the answer:
npm ls next
# or
pnpm why next
# or
yarn why next
Monorepos often have more than one next in the tree. Every app that deploys needs checking.
Upgrade to 16.3.6 without surprises
On the 16.x line this is a patch bump, which is about as low-risk as a framework upgrade gets. Treat it like one, but still verify it.
npm install next@16.3.6
# match eslint-config-next if you pin it alongside
Pin the exact version for this deploy rather than a caret range. You want the lockfile to show 16.3.6 and nothing that resolves differently in CI. After installing:
- Run
npm ls nextagain and confirm every workspace resolved to 16.3.6. - Run a production build. Don't use a dev server. Image generation code paths can behave differently under
next build. - Request each OG route you inventoried with realistic parameters and check that the image still renders. Social cards that break quietly are the most likely regression, and nobody notices until a campaign link is shared.
- Deploy, then check the runtime version. On self-hosted Node, log
process.versionsand thenextpackage version at boot so you can prove what's running.
If you can't upgrade today because of a blocked dependency or a frozen release branch, disable the dynamic OG routes until you can. Return a static image or a 404. Losing preview cards for a few days costs less than leaving an RCE path open.
Harden image-generation routes after the patch
The patch fixes this bug. It doesn't fix the design that made the route worth attacking. After upgrading, tighten the routes so the next advisory in this area has less to reach.
- Don't pass raw query strings to the renderer. Accept an ID or slug, look up the title and price server-side, and render only what came from your own data. If you have to accept free text, cap its length and strip it to a known character set.
- Allowlist remote fetches. If the card includes a product image, build its URL from your own CDN host and a validated path. Never fetch a URL taken from the request.
- Load fonts locally. Bundle font files instead of fetching them by URL at request time.
- Cache aggressively. An OG image for a given slug rarely changes. Cache headers and CDN caching cut how often the renderer runs at all, and that shrinks both attack surface and cost.
- Rate-limit and log. Put the route behind the same edge rate limiting as your API, and log the parameters it receives. Unusual parameter shapes on an image route should get someone's attention.
A checklist to run today
- Read GHSA-vcvr-r3jv-pc5j in full, including affected and patched versions.
- Grep every app for
next/ogandImageResponse, and find allopengraph-image,twitter-image, and icon convention files. - Classify each as static or dynamic and trace user input into it.
- Confirm installed
nextversions withnpm ls nextin every workspace. - Upgrade 16.x apps to 16.3.6. For 15.x or canary, confirm the patched version from the advisory before you act.
- Build, smoke-test each OG route, deploy, and verify the running version.
- If you can't upgrade today, disable dynamic OG routes until you can.
- Add input allowlisting, fetch allowlisting, caching, and rate limiting to image routes.
Run the inventory first. It takes about ten minutes and tells you whether this is a routine patch bump or an incident. If you're running Next.js in production and want a second pair of eyes on the upgrade or a broader review of your route handlers, our enterprise Next.js work covers this kind of work, and you can get in touch here.
Need this done on a real stack?
Magento 2, Adobe Commerce, migrations to Medusa.js or Vendure, enterprise Next.js, WordPress, and AI automation.
Contact us