Skip to content

Build Not yet confirmed elsewhere1 publisher 2 min read Published

Next.js next/og passed unescaped input into the SVG that libvips, librsvg and libxml2 parse

Satori, the JSX-to-SVG step behind Next.js next/og, put external input into SVG unescaped, per a dev.to explainer of CVE-2026-94545. The explainer says code execution would need the injected SVG to corrupt memory in a native parser, while SSRF needs only the injection.

The Engineer · Build desk

How we use AI Send a correction

Unescaped input reaches native SVG parsers Where unescaped input goes in the next/og pipeline. The story has no figure worth charting, so this maps each step instead.

Five actors. Routes: the example puts the URL title into ImageResponse. Satori: inserted values as-is before the fix. Attacker: picks the tag; an image href gives SSRF. sharp, libvips: pass parsing to librsvg, libxml2. Explainer: memory corruption there can mean code execution.

Unescaped input reaches native SVG parsers
WhoHowKindClaim
ImageResponse routesExample route puts the title query param into ImageResponse JSX; explainer calls title attacker-controlledexposure11
Satori (preprocess.ts)Before the fix, built each SVG attribute with a plain template literal inserting the value as-isconstraint5
AttackerPicks the tag: an image with external href gives SSRF; foreignObject or style opens another parsing pathcapability8
sharp and libvipssharp delegates SVG parsing to libvips, which calls native libraries such as librsvg and libxml2exposure9
librsvg and libxml2Explainer says memory corruption while they parse it can mean code execution with server privilegesexposure13

What happened

  • Before the fix, Satori's preprocess.ts built each SVG attribute with a template literal that inserted the value as-is.
  • In the explainer's example, a title of A"><image href=... /> ends the attribute early and adds an image element at sibling level in the SVG.
  • The assembled SVG goes to sharp, which delegates parsing to libvips, and libvips calls librsvg and libxml2.
  • The explainer says code execution follows if the crafted SVG triggers memory corruption in librsvg or libxml2, running with the server process's privileges.

Why it matters

  • exposure A route that feeds a query parameter into ImageResponse is reachable by anyone who can send a GET, and the explainer says such routes are almost always public with no login.
  • constraint Severity for a given deployment depends on the libvips, librsvg and libxml2 builds it ships, because only a memory bug in those parsers turns the injection into code execution.
  • capability The attacker picks the injected tag, so an image href gives server-side requests while foreignObject or style open other parsing paths.

The attribute path gets the example, but the text path has the same hole. For anything that is not an object, the pre-fix code returns String(node) [6]. The pre-fix code has no step that maps double quotes, angle brackets or ampersands to entities [10]. In our view the serializer is the right place for the repair: once the pieces are joined into one string and handed to sharp [9], nothing in the string marks which characters came from the request. A template literal like that works on every title a developer is likely to test with.

The injection is shown end to end. The example payload produces a new image element in the assembled SVG [7], and an image element with an external href is an SSRF primitive [8]. Code execution is a separate claim. The explainer ties it to a memory-corruption bug in a native parser [13], and its example payload only injects an image tag [14]. We'd treat the injection as established by the code shown and the RCE as conditional on a parser flaw.

Reach depends on conditions. The explainer says two must hold, and the first is that ImageResponse runs on the Node.js runtime [12]. The text we have breaks off before the second condition and does not name a fixed release or the memory bug that code execution would need.

Everything here comes from one dev.to explainer. Its description of the route pattern (a query parameter read straight into JSX [11]) is a common way to build these endpoints, and the explainer's own claim is that they are almost always public [2]. That is a statement about typical deployments. Each app still has to be checked for whether it passes request values into ImageResponse.

What to watch

  • A CVE record or vendor advisory that gives fixed Satori and Next.js versions and a severity score.
  • A public proof that an injected SVG crashes or executes code in librsvg or libxml2, which would move RCE from conditional to shown.
  • The second condition in the explainer's list, which the available text cuts off.

Clarity's read

What the record supports and how the coverage leans. The claims behind it follow.

Reality

Evidence50
Adoption
Insufficient
Hype gap+25
Incentives
Insufficient
Confidence45
Why these scores

Claim ledger

Ranked by verification strength, evidence, and original report placement.

  1. [1]

    Next.js's next/og module generates Open Graph images from JSX and CSS.

    Reported Supported Source: dev.to explainerView cited source
  2. [2]

    Because it is routine to drop external input such as a blog post title or profile name into the image, the explainer says the feature is almost always exposed as a public endpoint reachable with no login.

    Reported Supported Source: dev.to explainerView cited source
  3. [3]

    ImageResponse converts JSX into an SVG string internally through a library called Satori.

    Reported SupportedView cited source

Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.

Add to ChatGPTAdd to Claude

Sources

1 independent publisher whose own reporting we read for this story.

  1. dev.to

    1 article · October 13, 2026

    CVE-2026-94545 Explained: What Happens When an OG-Image Generator Forgets to Escape

Share your take

Let Clarity write the post for you.

Signed-in readers get a short post drafted on this story in the register they choose — narrative, analytical, or a direct position — editable to the last word before it goes anywhere. The share buttons at the top of this story work without an account.

Topics and entities

Follow any of these and your For You feed starts watching them — no settings page required.

Loading related stories