Patching Next.js ImageResponse SVG-to-RCE and Axios HTTP/2 SSRF in a Node.js Service

Patching Next.js ImageResponse SVG-to-RCE and Axios HTTP/2 SSRF in a Node.js Service

pr0h0•
nextjsaxiosnodejssecurityssrf
AI Usage (99%)

Introduction

Two reports, one day, two different risk shapes

This post maps the October 2026 Next.js ImageResponse SVG-to-RCE flaw and the Axios HTTP/2 SSRF and denial-of-service reports onto the API surfaces in a real Node.js service, then gives concrete patching and hardening steps for each.

On 2026-10-01, four pieces of coverage landed within about eighty minutes of each other. gbhackers published "Next.js ImageResponse Vulnerability Lets Remote Attackers Execute Code Through SVG Content" at 13:11 UTC. CyberSecurityNews published "Node.js ImageResponse Implementation Vulnerability Enables Remote Code Execution" at 13:00 UTC and "Axios HTTP/2 Flaws Enable SSRF Control Bypass and Node.js Denial of Service" at 12:50 UTC. cyberpress published "Critical Next.js ImageResponse Flaw Enables Remote Code Execution via Malicious SVG Input" at 11:56 UTC. Those timestamps come from the discovery feed metadata, not from the articles themselves.

Two subjects, two very different shapes of failure. The ImageResponse issue turns attacker-supplied content into code execution inside your server process. The Axios issue walks around a control you probably believe you already have — and degrades availability on top of it.

My position: patch both, treat the image renderer as urgent

If you only have time for one of these today, take the ImageResponse one. Code execution on a Node.js process that has network access and, in most deployments, cloud credentials in its environment is a different category of problem than an outbound-request control that can be sidestepped. The Axios issue is serious — SSRF bypasses are how cloud metadata endpoints get read — but on its own it is a control failure, not a full compromise primitive.

What this post does and does not claim

What I have is headline-level. The coverage names the components and the impact classes. It does not include advisory text, CVE identifiers, affected version ranges, or the mechanism behind either bug. I will keep the confirmed reporting and my own inference visibly separate, and I will not fill the gaps to make this post look more complete than the sources are.

What the public reporting says about the Next.js and Axios flaws

Next.js ImageResponse and malicious SVG

The reports describe a critical flaw where malicious SVG content passed into ImageResponse leads to remote code execution, and a companion report frames the same class of issue as a Node.js ImageResponse implementation problem. I read that as two angles on one underlying defect rather than two independent bugs — but that is my interpretation, not something the coverage states.

Axios HTTP/2 flaws: SSRF control bypass and Node.js denial of service

Separate reporting describes HTTP/2 flaws in Axios that allow an SSRF control bypass and a denial-of-service condition against Node.js services. That is the claim as published. How the bypass works is not in the material I have.

Reported versus not established

ClaimStatus
Malicious SVG input to Next.js ImageResponse can lead to RCEReported by gbhackers and cyberpress, 2026-10-01
A related ImageResponse implementation issue affects Node.jsReported by CyberSecurityNews, 2026-10-01
Axios HTTP/2 flaws enable SSRF control bypassReported by CyberSecurityNews, 2026-10-01
Axios HTTP/2 flaws enable denial of service against Node.js servicesReported by CyberSecurityNews, 2026-10-01
CVE identifiers, affected version ranges, fixed versionsNot established in the material available here
The specific mechanism that turns SVG into code executionNot established
Any DoS amplification factor, request volume, or timingNot established

What this post will not do

I am not going to invent CVE IDs, version ranges, or a proof-of-concept payload. There is no advisory text in front of me, and guessing at a version boundary is how people end up patching nothing while believing they patched something. What follows is inventory, architecture, and defensive controls that hold regardless of what the eventual advisory says.

Mapping the affected API surface in your codebase

ImageResponse

ImageResponse shows up wherever a route handler generates an image at request time: dynamic OG images, social cards, and — the case that should worry you — user-profile or user-content previews. One question decides your exposure: is any of the input reaching it attacker-influenced? If every call site passes literals from your own code, you are in good shape. If any of them interpolates a request parameter, a stored username, a title from a database row that users can write, or a fetched upstream document, you are in the blast radius.

Axios: HTTP/2 transport and URL construction

For Axios, you are looking for two things: where HTTP/2 is enabled or where the adapter/transport is chosen, and where request URLs are built from user-controlled or upstream-controlled data. The second one is the SSRF surface; the first one is what decides whether your SSRF defenses actually run.

Grep both surfaces

rg -n --glob '!node_modules' --glob '!dist' --glob '!*.min.*' \
  -e 'ImageResponse' \
  -e 'next/og' \
  -e '@vercel/og' \
  -e 'satori' \
  -g '*.{js,jsx,ts,tsx,mjs,cjs}'

rg -n --glob '!node_modules' --glob '!dist' \
  -e 'http2' \
  -e 'transport:' \
  -e 'HTTP_PROXY' \
  -e 'NO_PROXY' \
  -e 'no_proxy' \
  -e 'httpAgent' \
  -e 'httpsAgent' \
  -e 'lookup:' \
  -g '*.{js,jsx,ts,tsx,mjs,cjs}'

Reading the grep output

Expected shape of the first command's output in a service with dynamic OG images:

app/api/og/route.tsx:3:import { ImageResponse } from 'next/og';
app/api/og/route.tsx:14:  const title = searchParams.get('title') ?? 'Untitled';
app/api/og/route.tsx:22:  return new ImageResponse(<Card title={title} />, { width: 1200, height: 630 });

That is a searchParams value flowing into the renderer. Zero hits for the whole first command is genuinely good news — you do not have this surface. Zero hits on the second command is not good news on its own: a transitive dependency can turn HTTP/2 on without you ever writing the string http2 in your own code. Treat the second grep as a starting point, not a clearance.

ImageResponse: why a rendering path becomes a code-execution surface

The ImageResponse data flow: JSX and CSS to SVG to PNG

The published pipeline is JSX and CSS in, SVG out of Satori, then rasterization to PNG by a resvg-based renderer — @resvg/resvg-wasm on the edge runtime, a native binding in Node. Check your installed tree to confirm which one ships. Every step after parsing starts touching bytes you did not author.

Why SVG is a hard format to parse safely

This part is inference, not confirmed. SVG is a rich format: it has script elements, external entity and URL references, filter primitives, and font handling. A renderer that does not fully sandbox parsing is a plausible place for untrusted content to escape its box — either through a memory-safety bug in the native rasterizer, or through a resource-fetching path that gives the renderer outbound network reach. The public reporting does not name the mechanism, and I am not going to claim I know it.

Prerequisites an attacker needs for SVG-to-RCE

  1. A reachable endpoint that feeds attacker-controlled data into ImageResponse.
  2. No sanitization or format allowlist between the input and the renderer.
  3. A renderer process with network or filesystem reach beyond its working directory.

Remove any one and the chain breaks. Removing the second is the cheapest.

This bug class keeps recurring

Server-side renderers that accept user content are consistently where template, SVG, and image pipelines turn into RCE. None of that is specific to Next.js: it is what happens when a parser designed for trusted documents is handed untrusted bytes and run in a privileged process.

ImageResponse mitigations that do not depend on the patch landing

Stop accepting SVG from untrusted sources

If a user can hand you an SVG, treat it as executable content, because it is. If you genuinely must render user SVG, rasterize it behind a strict allowlist and reject anything containing scripts, entities, or external references — and accept that this is a filter you will be maintaining forever.

⚠️

Sanitizing SVG by regex is a losing game. Reject-and-re-render is defensible; strip-the-dangerous-bits is not.

Pin the template instead

Where the use case allows, pre-generate images or pin them to a fixed template so no user bytes reach the renderer at all. Dynamic OG images usually need a title string, not an arbitrary document. A length-capped plain text string run through your own escaping is a fundamentally smaller surface than a data URL.

Run image generation with least privilege

No outbound network. No writable filesystem outside a temp directory. CPU and memory limits on the process. If the renderer cannot reach the metadata service and cannot write a shell script to disk, an escape has far less to work with.

Rate-limit the image render endpoint

A slow or repeated render is often the first observable symptom of someone probing the parser. Bound concurrent renders per process, not just requests per IP.

The tradeoff, stated honestly

Filtering SVG input is the option that sounds cheap and is not. Template pinning is what I would actually pick for production, because it removes the input rather than trying to launder it.

Axios HTTP/2: SSRF allowlist bypass and denial of service

Two distinct Axios impacts: SSRF bypass and denial of service

Keep them separate when you triage. One defeats a security control — that is a confidentiality and lateral-movement problem. The other degrades availability. Different blast radius, different urgency.

Why an HTTP/2 transport can change the SSRF answer

Inference: HTTP/2 changes connection reuse and request shaping. Controls implemented as headers or environment variables — proxy selection, no_proxy, Host handling — and per-request interception hooks can behave differently on an HTTP/2 path than on HTTP/1.1. The concrete version of this concern that I would test first is that http2.connect() does not take the agent plus custom lookup combination that HTTP/1.1 hardening relies on, so DNS-layer validation written for an http.Agent may simply never execute on the h2 path.

Why transport-level controls matter for SSRF defenses

Most JavaScript services "prevent" SSRF with string matching or a regex over the URL. Any control that runs before connection establishment — before DNS resolution, before the socket — is exactly what a transport-level difference can sidestep. The fix is to move the check to where the connection actually happens.

On the denial-of-service claim

The report gives no request volume, timing, or amplification factor, so I am not going to state one. Treat it as an availability risk to bound with timeouts and size caps, not as a measured amplification number.

Hardening Axios for outbound requests

Validate at the DNS layer

Resolve the hostname, check the resolved addresses against a denylist of private, loopback, link-local, and metadata ranges, and do it on every connection rather than once at config time. Node's net.BlockList gives you subnet matching without hand-rolling CIDR arithmetic.

Re-validate after redirects

A redirect is a new request to a new host, and it needs the same checks. If the target is not fully trusted, set maxRedirects: 0 and follow the redirect yourself.

Disable HTTP/2 explicitly if you do not need it

Do not rely on the default. Confirm the effective config at runtime by asserting the negotiated protocol on a request to an h2-capable host.

Bound timeouts, redirects, and response size

Set connect, response, and total timeouts, and cap response size. That limits the availability impact regardless of the specific flaw.

outbound-client.js
import axios from "axios";





const ALLOWED_HOSTS = new Set(["api.internal.example.com", "cdn.example.com"]);

const blocked = new BlockList();
blocked.addSubnet("0.0.0.0", 8, "ipv4");
blocked.addSubnet("10.0.0.0", 8, "ipv4");
blocked.addSubnet("100.64.0.0", 10, "ipv4");    // CGNAT
blocked.addSubnet("127.0.0.0", 8, "ipv4");
blocked.addSubnet("169.254.0.0", 16, "ipv4");   // link-local + cloud metadata
blocked.addSubnet("172.16.0.0", 12, "ipv4");
blocked.addSubnet("192.168.0.0", 16, "ipv4");
blocked.addAddress("::1", "ipv6");
blocked.addSubnet("fc00::", 7, "ipv6");
blocked.addSubnet("fe80::", 10, "ipv6");
blocked.addSubnet("::ffff:0:0", 96, "ipv6");    // IPv4-mapped addresses

// Node calls lookup after DNS resolution and before connect.
function safeLookup(hostname, options, callback) {
dns.lookup(hostname, { ...options, all: true }, (err, addresses) => {
  if (err) return callback(err);
  for (const entry of addresses) {
    const family = entry.family === 6 ? "ipv6" : "ipv4";
    if (blocked.check(entry.address, family)) {
      return callback(new Error("blocked address for " + hostname + ": " + entry.address));
    }
  }
  if (options.all) return callback(null, addresses);
  return callback(null, addresses[0].address, addresses[0].family);
});
}

export const outbound = axios.create({
baseURL: "https://api.internal.example.com",
timeout: 8000,               // total request timeout
maxRedirects: 0,             // follow redirects manually, re-validating
maxContentLength: 2 * 1024 * 1024,
maxBodyLength: 1 * 1024 * 1024,
proxy: false,                // ignore HTTP_PROXY / NO_PROXY env vars
httpAgent: new http.Agent({ lookup: safeLookup, keepAlive: true }),
httpsAgent: new https.Agent({ lookup: safeLookup, keepAlive: true }),
});

outbound.interceptors.request.use((config) => {
const url = new URL(config.url, config.baseURL);
if (!ALLOWED_HOSTS.has(url.hostname)) {
  throw new Error("host not allowlisted: " + url.hostname);
}
if (url.protocol !== "https:") {
  throw new Error("scheme not allowed: " + url.protocol);
}
return config;
});
💡

The allowlist interceptor is a cheap first gate, but the lookup function is the control that matters. Interceptors run before the adapter; DNS validation runs at connect time, on every connection, after any redirection.

Upgrade and verification plan for patching Next.js and Axios

Order of operations for patching

  1. Inventory actual installed versions.
  2. Patch or isolate the internet-facing image-generation path first.
  3. Upgrade Axios.
  4. Re-verify the controls.

Inventory the tree, not package.json

npm ls next axios --all
npm ls @vercel/og satori --all
git diff -- package-lock.json | rg -A2 -B2 '"axios"|"next"'
node -p "require('axios/package.json').version"

npm ls --all follows the dependency tree. package.json can say ^1.x while the resolved lockfile entry is something else entirely — and transitive copies of Axios are the ones you will miss.

Re-run the greps after patching

After upgrading, run both grep commands again and confirm the HTTP/2 path is off, or that your lookup hook is still wired on whatever agent the adapter now uses. Assert the negotiated protocol rather than assuming it:

node -e "require('axios').get('https://cloudflare.com/cdn-cgi/trace').then(r=>console.log(r.request.res.httpVersion))"

Expect 1.1 on a hardened client. Anything else means the transport changed under you.

One manual SSRF test

Point a staging instance at an internal address through the allowlisted path:

curl -sS -i 'https://staging.example.com/api/fetch?url=http://169.254.169.254/latest/meta-data/'

Pass looks like HTTP 400 with a body such as {"error":"host not allowlisted"}. Fail looks like any 2xx carrying metadata text — or a long hang with no timeout firing. Both are actionable; the hang is the more common outcome in services that only validate strings.

The caveat: pin a version only after the advisory names one

Without a published advisory in hand, "upgrade to the latest release" is the actionable step. Pin to a specific fixed version once the advisory text names one. Verify which version you should be on at Next.js security advisories and Axios security advisories before you pin.

Further Reading

Those last two are aggregator redirects, not publisher canonical URLs — they may go stale.

Conclusion

The split is simple. The ImageResponse issue is an input-to-code-execution problem: untrusted SVG reaching a parser inside a process that has network and filesystem access. The fix you control today is to remove that input — pin the template, cap the string, and stop accepting SVG from anyone you would not let run code on the box. The Axios issue is a reminder that SSRF defenses built on URL strings are not defenses; the check has to happen after DNS resolution, on every connection, on every transport.

Patch on the vendor's schedule. Change your architecture on yours. The mitigations above work whether or not the patch is complete, and that is the whole reason to do them.

Your next ten minutes: run the two rg commands against your repo. If the first one returns hits, look at the arguments. If the second one returns nothing, go check your lockfile for a transitive Axios before you conclude anything.

Share this post

More posts

Comments