Next.js dev MCP endpoint leak: keep your Sume API key out of source
CVE-2026-94486 let a malicious page read source snippets from next dev. Keep the Sume API key in an env var, server-side, and log request ids not headers.

A Low-severity advisory is still a reason to check where your Sume key lives. Next.js CVE-2026-94486 says a malicious website could read parts of a project through the next dev MCP endpoint, and a key written into source can travel with those parts.
Next.js facts are from its September 30 security release; Sume facts from Authentication and the SDK overview, read 2026-10-01.
What does the advisory say?
The post says next dev exposes an MCP endpoint that does not verify request origin. A malicious website could read the project location on disk, source snippets from error reports, the route inventory and dev logs. It says production deployments do not serve it. The fix is in v16.3.8 and v15.5.27.
| Data | Exposed by the dev MCP endpoint |
|---|---|
| Project location on disk | Yes |
| Source snippets from error reports | Yes |
| Route inventory | Yes |
| Dev logs | Yes |
| Production deployments | Not served |
Where should the Sume key live?
A Sume API key spends your credits and there is no browser-safe variant. The docs say never to ship one to client JavaScript or a NEXT_PUBLIC_* variable, and to put your own endpoint in front and build the Sume request there. Read it from process.env so no literal sits in a line that an error report could quote.
I am inferring the link between source snippets and keys: the advisory lists snippets, not secrets, so it does not say env values were exposed.
What does a safe route look like?
The handler builds the client from the env var and returns only the job id. On failure it logs the status, not headers or the body. The request_id inside Sume's error envelope is what support needs.
import { createSumeClient, generateVideoV1 } from "@sume-com/sdk";
export async function POST(req: Request) {
const apiKey = process.env.SUME_API_KEY;
if (!apiKey) return Response.json({ error: "server misconfigured" }, { status: 500 });
const { prompt } = await req.json();
const client = createSumeClient({ apiKey });
const { data, error, response } = await generateVideoV1({
client,
headers: { "idempotency-key": crypto.randomUUID() },
body: { prompt, mode: "async" },
});
if (error) {
console.error("sume submit failed", response.status);
return Response.json({ error: "upstream" }, { status: 502 });
}
return Response.json({ job_id: data!.data.request_id });
}What should you do if a key was exposed?
Rotate it from the dashboard; the docs say to rotate a key that may have been exposed. Also keep keys out of support tickets and screenshots.
Sources
Related posts
More in Developers
- Revised NO FAKES Act: counter-notice and what it means for APIs
The revised NO FAKES Act adds counter-notice, a research exemption and streaming-music fixes. A plain summary read against avatar, face-swap and music routes.
- Notion 429/529 retry_after in the body vs Sume retry-after header
Notion now puts additional_data.retry_after in 429/529 bodies. Sume sends a retry-after header on rate_limited. One small helper can read both.
- Notion MCP 20 calls per 10 seconds: poll Sume jobs less
Notion MCP allows 20 search and 20 data source query calls per connection every 10 seconds. Use a Sume webhook so job polling does not compete for them.
- OpenAI Agents API durable sessions and Sume job ids
A durable OpenAI Agents API session continues across turns; a Sume render is a separate job. Keep the job id in the session and re-read it, never re-create.
Written by Sume