← Blog
·8 min read

CVE-2025-55183: When Your Next.js Server Function Returns Its Own Source Code

A crafted request made Next.js Server Functions return their source code in the HTTP response. If you hardcoded an API key — even just for testing — it was readable.

Yumi Hirasako

Security Researcher

Most developers who heard about the React2Shell cluster went straight for the CVSS 10. That made sense. Remote code execution is the kind of thing that gets patched before breakfast. You updated, you moved on.

But there was a second vulnerability in the same batch. CVSS 5.3. "Medium severity." Easy to skip over when something rated 10 is sitting right next to it on the advisory page.

CVE-2025-55183 was an information disclosure flaw — a crafted HTTP request that caused a Next.js Server Function to return its own source code in the response body. Not a compiled bundle. The actual source. And if you had an API key hardcoded in that file — even temporarily, even "just to test" — it was readable by anyone who knew the right request to send.


What CVE-2025-55183 is (and what it isn't)

This vulnerability lives in the same neighborhood as React2Shell (CVE-2025-55182) — same affected version ranges, same underlying technology, same fix.

But it does something different.

React2Shell was about execution. An attacker could run arbitrary code on your server. CVE-2025-55183 is about disclosure. A specially crafted request exploited how the React Flight protocol serialized Server Function responses, causing the server to include the function's source text in the HTTP response body alongside the expected output.

The distinction matters: no commands ran. No database was deleted. But the server handed over its own code.

What's exposed:

  • String literals baked directly into the source file
  • Hardcoded API keys, tokens, or passwords
  • Internal API endpoints constructed as string constants
  • Any business logic written as inline strings

What's not exposed:

  • Secrets loaded at runtime via process.env.API_KEY — those are references, not literals
  • Values that come from external config at startup
  • Anything that isn't a static string in the source

The severity gap between CVSS 10 (RCE) and CVSS 5.3 (source disclosure) can make this feel like a minor issue. It isn't, for one reason: the worst-case scenario depends entirely on what's in the file.


What an attacker actually sees

Here's a simplified mock of what a vulnerable response looks like when the exploit is triggered. The server returns its normal JSON wrapper, but the React Flight protocol serialization includes the function source as part of the payload:

HTTP/1.1 200 OK
Content-Type: text/x-component
 
1:I["/chunks/app/actions.js",["server-reference"],"generateSummary"]
0:{"result":null,"serverReference":"generateSummary"}
S:"'use server'\n\nconst OPENAI_API_KEY = 'sk-proj-abc123xyz...'\n\nexport async function generateSummary(text) {\n  const res = await fetch('https://api.openai.com/v1/chat/completions', {\n    headers: { Authorization: `Bearer ${OPENAI_API_KEY}` },\n  })\n  return res.json()\n}\n"

That last line — the S: field — is the source. The React Flight serializer included it in the response when it shouldn't have. An attacker parsing that response body has the full function text, including any credentials written directly into it.

This isn't theoretical. The CVE was assigned because the exploit was demonstrated on real applications running unpatched versions.


The exact line of code that caused it

The vulnerability amplifies whatever bad practice was already in the code. On its own, a hardcoded secret is bad. Under CVE-2025-55183, it became remotely readable.

Here's the unsafe pattern:

'use server'
 
// BAD: this string literal gets included in the leaked source
const OPENAI_API_KEY = 'sk-proj-abc123xyz...'
 
export async function generateSummary(text) {
  const res = await fetch('https://api.openai.com/v1/chat/completions', {
    headers: { Authorization: `Bearer ${OPENAI_API_KEY}` },
    body: JSON.stringify({
      model: 'gpt-4o',
      messages: [{ role: 'user', content: text }],
    }),
    method: 'POST',
  })
  return res.json()
}

And here's the version that was safe even on vulnerable Next.js versions:

'use server'
 
// GOOD: process.env reference — a runtime value, never a source literal
export async function generateSummary(text) {
  const res = await fetch('https://api.openai.com/v1/chat/completions', {
    headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
    body: JSON.stringify({
      model: 'gpt-4o',
      messages: [{ role: 'user', content: text }],
    }),
    method: 'POST',
  })
  return res.json()
}

The difference is one line. process.env.OPENAI_API_KEY is a pointer to a value that gets resolved at runtime, after the source is compiled and deployed. There's no literal string in the file to leak. The source code that hits the network contains the variable name, not the secret.

If your Server Functions use process.env for every credential, CVE-2025-55183 couldn't expose your secrets. The risk was always tied to the hardcoded literal pattern.


How AI assistants create this pattern by default

This is the part that makes the vulnerability genuinely dangerous in 2026.

Cursor, GitHub Copilot, Claude, v0 — all of them scaffold Server Actions and server-side API integrations with placeholder secrets. The generated code works. You test it. It hits the endpoint. You ship.

The placeholder comment looks like this:

'use server'
 
// TODO: replace with your actual API key
const ANTHROPIC_API_KEY = 'sk-ant-api03-placeholder...'
 
export async function generateText(prompt) {
  // ...
}

The AI isn't doing anything wrong. It's generating working, testable code. But "replace before deploying" is a step that's easy to miss when you're moving fast. And when you're vibe-coding an MVP at midnight, "I'll clean this up before prod" has a way of becoming "wait, did I actually clean this up?"

We scanned a sample of public repositories and found this exact pattern — literal API keys in files marked 'use server' — in projects that were actively deployed. Some were placeholder strings. Some were real credentials. CVE-2025-55183 made the distinction irrelevant: if it was a string literal in the source, it was readable.

Data Hogo's scanner flags hardcoded secrets in server-side files as part of its 199 security checks — before they can be exploited. Free scan, no card needed.


How to audit your Server Functions right now

Start with a targeted search across all files that use 'use server':

# Find all server files that may contain hardcoded credentials
grep -r "use server" . --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" -l \
  | xargs grep -l "sk-\|api_key\|API_KEY\|secret\|token\|password\|Bearer\|Authorization"

If that returns results, open each file and look for string literals — const KEY = 'value' — versus environment references — process.env.KEY. Any literal value in a 'use server' file is a candidate for exposure.

Also check your version. Run this in the project root:

npm list next

Then compare against the safe versions:

Version line Safe from CVE-2025-55183
Next.js 14.x 14.2.35+
Next.js 15.x 15.2.4+

If you're behind, update:

npm install next@latest

Patching stops future exploitation. But it doesn't undo the window when your app ran on a vulnerable version with hardcoded secrets in the source.


Three things to do today

1. Move every hardcoded credential to process.env.

No exceptions. No "this is just for testing." If it's a secret and it's a string literal in your source, move it to .env.local and reference it via process.env. This applies to all files, not just server files — but start with anything marked 'use server'.

2. Rotate anything that was hardcoded during the vulnerable window.

If you had a hardcoded API key in a 'use server' file and your app ran on Next.js below 14.2.35 or 15.2.4, treat that key as compromised. Go to the provider's dashboard, revoke the old credential, generate a new one, and deploy with the new key in your environment variables. Patching Next.js doesn't un-expose a key that was already read.

For a step-by-step on what to do when you think a key has been leaked, the exposed API key playbook covers the full rotation and audit process.

3. Audit your git history.

The secret that was in the source is probably still in your git history, even if you removed it from the latest commit. Run:

git log --all --full-history -S "sk-" -- "*.ts" "*.js" "*.tsx" "*.jsx"

If you find old commits containing credential strings, consider using git filter-repo to remove them from the history, and force-push to any remotes. Then rotate the credentials regardless — assume they were read.


TL;DR

  • CVE-2025-55183 is an information disclosure vulnerability in Next.js, CVSS 5.3, from the same batch as React2Shell.
  • A crafted HTTP request caused Server Functions to return their source code in the HTTP response via the React Flight protocol.
  • Only string literals in the source were exposed — process.env references were safe.
  • The fix is Next.js 14.2.35+ or 15.2.4+. Same version range as the React2Shell patch.
  • If you had hardcoded secrets in Server Functions during the vulnerable window, rotate them now — patching doesn't un-leak a credential.
  • AI coding tools generate the exact unsafe pattern (hardcoded placeholders) that this CVE exploited. Audit before deploying AI-scaffolded server code.

FAQ

Does CVE-2025-55183 expose secrets stored in process.env?

No. Secrets loaded via process.env.API_KEY are runtime references — they resolve to values never written into your source files. CVE-2025-55183 exposed string literals baked directly into the source: const KEY = 'sk-prod-abc123'. If you used process.env exclusively, your secrets were not exposed.

How is CVE-2025-55183 different from React2Shell (CVE-2025-55182)?

CVE-2025-55182 (React2Shell) was a remote code execution vulnerability — CVSS 10 — that let an attacker run arbitrary commands on your server. CVE-2025-55183 is an information disclosure vulnerability — CVSS 5.3 — where a crafted request causes a Server Function to return its own source code in the HTTP response. Different outcomes, same fix.

If I had a hardcoded secret in a Server Function and I've since moved to process.env, am I safe now?

Moving to process.env going forward is correct, but the secret that was hardcoded is still in your git history and may have been read during the window when your app ran on an unpatched Next.js version. Treat that specific secret as compromised: rotate it, revoke the old credential, and audit your logs for unexpected usage.

CVENext.jsServer Actionssecretssource code exposureApp Routervulnerabilities