This is a valet key that only matters if you left the server-room side door unlocked
CVE-2026-44578 is a server-side request forgery bug in Next.js WebSocket upgrade handling. Affected versions are >=13.4.13 <15.5.16 and >=16.0.0 <16.2.5, and the exposure is specifically self-hosted deployments using the built-in Node.js server. The vulnerable path lets a crafted WebSocket upgrade request trick the origin into proxying to attacker-chosen internal or external destinations, including localhost services or cloud metadata endpoints.
The vendor's HIGH 8.6 is directionally fair, but a little hot for enterprise prioritization because the reachable population is narrower than the CVSS suggests. This is not a generic Next.js internet bug, not a Vercel-hosted issue, and not direct code execution; it is a deployment-contingent SSRF whose real damage depends on what the origin can reach over the network.
4 steps from start to impact.
Reach a vulnerable origin
curl, nc, custom Python sockets, or a Nuclei template are enough.- Internet or otherwise untrusted-network reachability to the Next.js origin
- Application is self-hosted rather than Vercel-hosted
- Deployment uses the built-in Node.js server
- A large chunk of enterprise Next.js traffic is terminated behind CDN/reverse-proxy layers
- Some shops do not expose origins directly to the internet
- Vercel-hosted deployments are explicitly not affected
Send a crafted absolute-URL WebSocket upgrade
Connection: Upgrade and Upgrade: websocket, pointing the request line at an absolute URL. The public patch commit and tests show the vulnerable flow in router-server.ts; public PoC collections exist, so attackers do not need to rediscover the bug.- Origin accepts or forwards WebSocket upgrade requests
- Target version is in the vulnerable range
- Some reverse proxies normalize or restrict upgrade behavior
- Applications that do not rely on upgrade handling may already block it upstream
Abuse the proxy path for SSRF
- The origin can egress to the chosen target
- Internal target is routable from the app host/container
- Good egress controls can block metadata IPs and RFC1918 targets
- Cloud metadata hardening such as AWS IMDSv2 reduces the easy-win path
- Many internal services still require auth even if reached
169.254.169.254, localhost admin ports, RFC1918 ranges, or unusual outbound destinations immediately after upgrade requests.Turn SSRF into data access or cloud pivot
- Useful internal targets exist behind the app tier
- Those targets expose sensitive data or issue credentials/tokens
- No direct RCE primitive in the vulnerable component
- Well-segmented networks and hardened metadata services sharply reduce payoff
- Blast radius is usually bounded to what that app host can already reach
The supporting signals.
| In-the-wild status | As of 2026-06-01, I found no CISA KEV listing and no primary-source confirmation of active exploitation. Treat public PoC availability as a warning, not as proof of campaigns. |
|---|---|
| Proof-of-concept availability | Yes, public PoC exists. The vendor patch commit includes regression tests showing the exploit condition, third-party PoC writeups reference working exploit code, and ProjectDiscovery shipped a Nuclei template covering CVE-2026-44578. |
| EPSS | User-supplied intel says EPSS 0.0581. I did not verify the percentile directly from FIRST during this assessment, so treat percentile as unverified. |
| KEV status | Not listed in the CISA Known Exploited Vulnerabilities Catalog as checked for this assessment on 2026-06-01. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N = unauthenticated remote reachability with high confidentiality impact, but no built-in integrity or availability primitive. |
| Affected versions | Vendor advisory: >=13.4.13 <15.5.16 and >=16.0.0 <16.2.5, limited to self-hosted apps using the built-in Node.js server. |
| Fixed versions | Patch in 15.5.16 and 16.2.5 for this CVE. Note: Vercel's coordinated May 7, 2026 security rollup later recommended moving to 15.5.18 or 16.2.6 to clear the broader batch of May 2026 Next.js issues. |
| Exposure and scanning reality | Scanner coverage is emerging rather than mature. ProjectDiscovery added a Nuclei template, but internet-exposure estimates floating around for Next.js origins are third-party Shodan-based claims and not independently verified here. |
| Disclosure timeline | GitHub advisory published 2026-05-06; NVD published 2026-05-13; GitHub advisory updated 2026-05-14. Vendor coordinated release note landed 2026-05-07. |
| Reporter / publisher | Primary advisory published by Vercel / Next.js maintainers via GitHub Security Advisory GHSA-c4j6-fc7j-m34r. |
noisgate verdict.
The decisive factor is that this is still unauthenticated remote SSRF against an app origin, which is a real pivot path to cloud metadata and internal services when exposed. The score comes down because the vulnerable population is meaningfully narrowed to self-hosted built-in Node.js deployments and the impact is highly dependent on what the origin can reach.
Why this verdict
- Unauthenticated remote path: no auth, no user click, and no complicated exploit chain to trigger the SSRF primitive
- Deployment friction lowers the score: only self-hosted Next.js using the built-in Node.js server is affected; Vercel-hosted deployments are out, which materially shrinks the exposed population
- Blast radius is conditional: the bad outcome depends on what the app tier can reach, whether metadata is hardened, and whether egress controls block internal destinations
Why not higher?
This is not direct RCE and not a universal Next.js flaw. An attacker still needs a reachable vulnerable origin and something valuable behind it such as permissive metadata, localhost admin ports, or weakly trusted internal APIs; those prerequisites are where many enterprise deployments add real friction.
Why not lower?
Do not over-downgrade it just because it is 'only SSRF.' Unauthenticated SSRF from an internet-facing app tier is exactly how attackers steal cloud credentials, hit localhost-only admin surfaces, and pivot into internal services without ever touching user auth.
What to do — in priority order.
- Block upgrade traffic where unused — If the application does not need WebSocket upgrades, deny
Connection: Upgrade/Upgrade: websocketat the reverse proxy or load balancer. For a HIGH verdict, deploy this within 30 days; if the origin is internet-exposed and you cannot patch immediately, do it sooner. - Deny metadata and private-range egress — Block app-tier access to
169.254.169.254, localhost admin ports, and unnecessary RFC1918 destinations from the Next.js host or pod. This directly cuts the value of the SSRF even if the bug is still present; for a HIGH verdict, put these egress controls in place within 30 days. - Hide origins behind trusted ingress — Do not expose the built-in Next.js origin directly to untrusted networks; place it behind a hardened ingress or reverse proxy that only forwards the traffic patterns you actually use. For this HIGH finding, prioritize this architectural containment within 30 days if patching has operational delay.
- Monitor outbound from Node.js — Alert on the Node.js process initiating connections to metadata IPs, RFC1918 space, localhost service ports, or unusual external destinations immediately after upgrade requests. Detection will not stop the bug, but it shortens dwell time while you work through the 30-day mitigation window.
- A generic WAF-only response does not solve this reliably; Vercel explicitly stated these May 2026 advisories cannot be reliably blocked at the WAF layer.
- Relying on MFA or app auth misses the point because the vulnerable path is pre-auth and unauthenticated.
- Assuming 'it's behind a reverse proxy' is enough is false; many proxies happily forward upgrade traffic to the vulnerable origin.
Crowdsourced verification payload.
Run this on the application host, container, CI workspace, or source repo checkout that contains the Next.js app. Invoke it with python3 verify_next_cve_2026_44578.py /path/to/app and no elevated privileges are required; it reads package.json, lockfiles, and node_modules/next/package.json if present.
#!/usr/bin/env python3
# verify_next_cve_2026_44578.py
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN
import json
import os
import re
import sys
from typing import Optional, Tuple
def parse_semver(v: str) -> Optional[Tuple[int, int, int]]:
if not v:
return None
v = v.strip()
if v.startswith('v'):
v = v[1:]
m = re.match(r'^(\d+)\.(\d+)\.(\d+)', v)
if not m:
return None
return tuple(int(x) for x in m.groups())
def cmp_ver(a, b):
return (a > b) - (a < b)
def is_vulnerable(ver: Tuple[int, int, int]) -> bool:
return (cmp_ver(ver, (13, 4, 13)) >= 0 and cmp_ver(ver, (15, 5, 16)) < 0) or \
(cmp_ver(ver, (16, 0, 0)) >= 0 and cmp_ver(ver, (16, 2, 5)) < 0)
def load_json(path: str):
try:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return None
def get_version_from_package_json(app_dir: str) -> Optional[str]:
data = load_json(os.path.join(app_dir, 'package.json'))
if not data:
return None
for section in ('dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'):
deps = data.get(section, {})
if isinstance(deps, dict) and 'next' in deps:
return str(deps['next'])
return None
def get_installed_version(app_dir: str) -> Optional[str]:
nm = os.path.join(app_dir, 'node_modules', 'next', 'package.json')
data = load_json(nm)
if data and isinstance(data.get('version'), str):
return data['version']
return None
def clean_spec(spec: str) -> Optional[str]:
if not spec:
return None
spec = spec.strip()
m = re.search(r'(\d+\.\d+\.\d+)', spec)
return m.group(1) if m else None
def main() -> int:
if len(sys.argv) != 2:
print('UNKNOWN - usage: python3 verify_next_cve_2026_44578.py /path/to/app')
return 2
app_dir = sys.argv[1]
if not os.path.isdir(app_dir):
print(f'UNKNOWN - path not found: {app_dir}')
return 2
installed = get_installed_version(app_dir)
declared = get_version_from_package_json(app_dir)
version_source = None
version_str = None
if installed:
version_source = 'installed node_modules/next/package.json'
version_str = installed
elif declared:
cleaned = clean_spec(declared)
if cleaned:
version_source = 'declared package.json dependency spec'
version_str = cleaned
if not version_str:
print('UNKNOWN - could not determine Next.js version from node_modules or package.json')
return 2
ver = parse_semver(version_str)
if not ver:
print(f'UNKNOWN - could not parse Next.js version: {version_str}')
return 2
if is_vulnerable(ver):
print(f'VULNERABLE - detected Next.js {version_str} from {version_source}; CVE-2026-44578 affects >=13.4.13 <15.5.16 and >=16.0.0 <16.2.5 in self-hosted apps using the built-in Node.js server')
return 1
print(f'PATCHED - detected Next.js {version_str} from {version_source}; version is outside the vulnerable ranges for CVE-2026-44578')
return 0
if __name__ == '__main__':
sys.exit(main())
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.