Next.js ImageResponse RCE fix: safe share card from a Sume job

Next.js 16.3.6 patched a critical ImageResponse RCE. After upgrading, build a share-card route that reads the Sume artifact server-side, not from the URL.

5 min readSume
All posts

If your Next.js app renders share cards with ImageResponse from next/og, upgrade to 16.3.6 or later today. On September 22 the Next.js team published an out-of-band security update for a critical remote code execution issue in the Node.js ImageResponse (Next.js security update, read 2026-10-04). It affects versions >=16.2.0 <16.3.6. The Edge ImageResponse is not affected, and 15.x received hardening only, with 15.5.26 as the patched line.

The September 30 scheduled release moved the supported lines to 16.3.8 and 15.5.27 and fixed further issues, so pin to at least that if you can (September 2026 security release, read 2026-10-04). The rest of this post is about the part you control: how a route that composes a Sume generation into a card should get its inputs.

Do not take the image URL from the request

The tempting route is /api/card?img=https://...&title=..., which turns your server into a renderer for whatever a visitor passes. A safer shape takes only a Sume job id. The server reads the job from GET /v1/jobs/{id} with your key, checks that it is completed, and uses the first artifact's url. Sume artifacts are served from https://media.sume.com/artifacts/..., and the docs say to use these Sume media URLs from the result, since raw provider URLs are not public API outputs (Jobs and results).

Keep the title short and fixed-length on the server side too. Text that arrives from a form or a prompt is the input class that renderer bugs tend to involve, so treat it as untrusted even on a patched version.

A route handler that reads the job first

The handler below calls the Sume jobs endpoint with the x-api-key header, refuses anything that is not a completed image artifact on media.sume.com, and renders a 1200 by 630 card. It runs on the Node runtime, which is the one the advisory concerns, so run it only on a patched release.

import { ImageResponse } from "next/og";

export const runtime = "nodejs";

export async function GET(req: Request) {
  const id = new URL(req.url).searchParams.get("job") ?? "";
  if (!/^[A-Za-z0-9_-]{6,80}$/.test(id)) return new Response("bad id", { status: 400 });

  const res = await fetch(`https://api.sume.com/v1/jobs/${id}`, {
    headers: { "x-api-key": process.env.SUME_API_KEY! },
    cache: "no-store",
  });
  if (!res.ok) return new Response("not found", { status: 404 });
  const body = await res.json();
  const job = body.data ?? body;
  const art = job.result?.artifacts?.[0];
  if (job.status !== "completed" || art?.type !== "image") {
    return new Response("not ready", { status: 409 });
  }
  if (new URL(art.url).host !== "media.sume.com") {
    return new Response("unexpected host", { status: 502 });
  }

  return new ImageResponse(
    (<div style={{ display: "flex", width: "100%", height: "100%" }}>
      <img src={art.url} width={1200} height={630} style={{ objectFit: "cover" }} />
    </div>),
    { width: 1200, height: 630 },
  );
}

Checks to run after the upgrade

  • Run npm ls next in every deployed app and confirm the resolved version is 16.3.6 or newer, or 15.5.26 or newer on the 15 line.
  • Search the codebase for ImageResponse and note which routes run on the Node runtime versus Edge.
  • Confirm the job response shape in your own account with one real call, since the sample above reads data.result.artifacts as documented.
  • Log the Sume request_id from failed job reads so a bad card can be traced back to one API call.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume