CVE-2026-44578 is a high-severity SSRF vulnerability in self-hosted Next.js that allows unauthenticated attackers to reach internal services through the WebSocket upgrade handler. Learn how it works, who's affected, and how to fix it.
On May 11, 2026, Vercel patched CVE-2026-44578, an 8.6-CVSS Server-Side Request Forgery (SSRF) vulnerability in the Next.js WebSocket upgrade handler. The flaw affects certain Next.js versions. A single unauthenticated HTTP request can force the server to fetch internal resources, including cloud metadata endpoints that may expose IAM credentials. Vercel-hosted apps are not affected.
This article covers what the vulnerability is, the exact mechanism in router-server.ts, which deployments are at risk, the cloud-specific impact, and the concrete steps to patch or mitigate it.
In brief:
- CVE-2026-44578 is a pre-auth SSRF in the Next.js WebSocket upgrade handler (CVSS 8.6).
- Affects self-hosted Next.js 13.4.13 through 15.5.15 and 16.0.0 through 16.2.4.
- Attackers can reach any internal service on port 80, including AWS IMDSv1, with crafted GET requests.
- Fixed in Next.js 15.5.18 and 16.2.6; proxy mitigations are available for teams that cannot patch immediately.
What Is Server-Side Request Forgery (and Why It Matters Here)
An SSRF vulnerability lets an attacker trick a server into making outbound HTTP requests on their behalf, using the server's own network context. Your server becomes an unwitting proxy. The attacker never touches the internal network directly; they just ask your application to do it for them.
SSRF in a framework-level HTTP handler is more dangerous than SSRF in application code. When the bug is in your code, it's scoped to a specific endpoint or feature. When it's in the framework's request-handling machinery, every self-hosted instance of that framework exposes the vulnerable code path regardless of what the application does.
No user code has to be written incorrectly for the flaw to be exploitable. You can have a perfectly security-reviewed codebase and still be wide open.
The security of your web stack depends on every layer, not just your application logic. In a headless architecture, where your frontend and backend are separate services communicating over HTTP, a framework-level SSRF in the frontend can become a direct path to your content API and other internal services.
In cloud environments, SSRF is the canonical path to extracting IAM credentials from Instance Metadata Services (IMDS) at 169.254.169.254. Those credentials typically grant access to S3 buckets, databases, queues, and other cloud resources. A single SSRF vulnerability can turn into full cloud account compromise.
Sign up for the Logbook, Strapi's Monthly newsletter
How CVE-2026-44578 Works Under the Hood
The vulnerability lives in router-server.ts, specifically in the WebSocket upgrade handler logic.
Next.js's router handles two types of incoming connections: regular HTTP requests via the request event, and WebSocket upgrade requests via the upgrade event. Both handlers call the same internal resolveRoutes() function to determine routing. That function returns several values: finished, statusCode, matchedOutput, and parsedUrl.
The Routing Asymmetry
The regular HTTP request path calls proxyRequest() only when routing approves it: finished && parsedUrl.protocol && !statusCode. All three conditions must be true.
The WebSocket upgrade handler, before the patch, destructured only matchedOutput and parsedUrl. It checked parsedUrl.protocol alone:
// router-server.ts (VULNERABLE)
const { matchedOutput, parsedUrl } = await resolveRoutes({
req,
res,
isUpgradeReq: true,
signal: signalFromNodeResponse(socket),
});
// BUG: only checks parsedUrl.protocol, ignores finished and statusCode
if (parsedUrl.protocol) {
return await proxyRequest(req, socket, parsedUrl, head);
}How the Attacker Sets parsedUrl.protocol
Absolute-form URIs are allowed by HTTP/1.1 per RFC 7230 §5.3.2. A client can legally send GET http://169.254.169.254/latest/meta-data/ HTTP/1.1. When url.parse() runs on that string, it produces parsedUrl.protocol = 'http:'. That truthy value is all the vulnerable if (parsedUrl.protocol) check needs to proceed.
The normalizeRepeatedSlashes Wrinkle
The // in http:// triggers a specific branch inside resolveRoutes():
// Inside resolveRoutes()
if (urlNoQuery?.match(/(\\|\/\/)/)) {
parsedUrl = url.parse(normalizeRepeatedSlashes(req.url!), true);
return {
parsedUrl,
resHeaders,
finished: true, // routing is done, it's a redirect
statusCode: 308, // the correct response would be a 308 redirect
};
}resolveRoutes() is saying: "This is a redirect situation, don't proxy it." It sets finished: true and statusCode: 308. The vulnerable upgrade handler threw those signals away. It saw protocol: 'http:' and called proxyRequest() anyway. The target hostname survived end-to-end, but any explicit port was stripped during normalization, so the proxy fell back to its default port for the scheme.
Here is the diff from fix commit c4f69086 showing the before and after:
Change 1, destructuring finished and statusCode:
- const { matchedOutput, parsedUrl } = await resolveRoutes({
- req,
- res,
- isUpgradeReq: true,
- signal: signalFromNodeResponse(socket),
- });
+ const { finished, matchedOutput, parsedUrl, statusCode } =
+ await resolveRoutes({
+ req,
+ res,
+ isUpgradeReq: true,
+ signal: signalFromNodeResponse(socket),
+ });Change 2, the fixed proxy condition:
- if (parsedUrl.protocol) {
- return await proxyRequest(req, socket, parsedUrl, head);
+ if (finished && parsedUrl.protocol) {
+ if (!statusCode) {
+ return await proxyRequest(req, socket, parsedUrl, head);
+ }
+
+ return socket.end();
}The patched condition requires three things simultaneously before proxying: finished must be true, routing entered the external proxy branch, parsedUrl.protocol must be truthy, the destination is an external URL, and statusCode must be falsy, routing did not set a redirect status.
When an attacker sends the absolute-form URI, resolveRoutes() returns finished: true and statusCode: 308; the new if (!statusCode) guard stops proxyRequest(), and socket.end() closes the connection cleanly.
Who Is Affected (and Who Is Not)
Exposure comes down to which Next.js version you run and whether a reverse proxy sits between the internet and the app.
Vulnerable Deployments
The affected version ranges span Next.js 13.4.13 through 15.5.15, and 16.0.0 through 16.2.4, when running the built-in Node.js server (next start or a custom server). Check your current version:
node -p "require('next/package.json').version"Deployment patterns at risk include bare-metal and VM Node.js processes, Docker containers exposing port 3000/80/443 without a reverse proxy, Kubernetes pods running Next.js directly on ingress, and staging or dev environments on unpatched versions. If you self-host with Docker, pay close attention to whether a reverse proxy sits in front of your Next.js container.
The WebSocket upgrade vulnerability exists in the Next.js built-in Node.js server and cannot be patched without upgrading the framework itself. Any self-hosted instance running next start on an affected version that accepts inbound HTTP is potentially exploitable.
Not Affected
Vercel and Next.js security communications do not explicitly state that Vercel-hosted deployments are excluded from this vulnerability. Apps behind a reverse proxy (nginx, Caddy, HAProxy) that rejects absolute-form request URIs before they reach Next.js get protection from the proxy layer.
Mainstream reverse proxies typically forward requests to backends, and some can be configured to rewrite URIs, though you should verify your specific configuration.
Cloud Metadata Exposure: The Real Danger
The SSRF-to-IMDS path is the highest-impact scenario for this vulnerability. On AWS EC2, IMDSv1, the legacy metadata service, responds to unauthenticated GET requests at 169.254.169.254:80.
A single SSRF request retrieves the IAM role name, then a second request pulls the full credential set: AccessKeyId, SecretAccessKey, and SessionToken. Those STS credentials carry whatever permissions the instance role has, as defined by the role's attached policies.
The cloud-specific nuances matter:
AWS IMDSv1: Fully exploitable. Unauthenticated GET on port 80 is exactly what this SSRF delivers.
AWS IMDSv2 (with HttpTokens=required): Not exploitable by this CVE. IMDSv2 requires a PUT request to mint a session token. This is a GET-only SSRF, so it cannot obtain the token. IMDSv2 rejects token-initiating PUT requests that carry X-Forwarded-For. If HttpTokens is set to optional, however, IMDSv1-style unauthenticated GETs still work and you remain vulnerable.
GCP metadata: The GCP metadata server rejects requests with the Upgrade: websocket header, returning HTTP 400. Exploitation depends on the application's ability to issue requests that satisfy GCP metadata server requirements.
Azure IMDS: Most Azure IMDS endpoints require the Metadata: true header, which Microsoft documents as an SSRF mitigation. Without it, credential endpoints return errors. This SSRF does not add that header.
Oracle OCI: OCI v2 metadata requires Authorization: Bearer Oracle and rejects requests containing X-Forwarded-For, mirroring the AWS IMDSv2 defense.
DigitalOcean metadata: Potentially reachable via this vector on port 80, though no authoritative source has confirmed exploitation details.
Beyond cloud metadata, the SSRF can reach any co-located service on port 80: admin panels, internal APIs, health-check endpoints, and monitoring dashboards. Credential theft from cloud metadata gets the headlines, but internal service enumeration deserves equal attention when assessing the risk to your infrastructure's security.
How to Detect Exploitation Attempts
Three detection surfaces are worth checking:
Server logs: Look for Failed to proxy http:/ in Next.js process logs. The single slash after http: is a normalization fingerprint associated with this SSRF path firing. A successful exploit against a live, reachable target, like IMDSv1, may produce no error log at all, so absence of this entry does not confirm safety.
grep "Failed to proxy http:/" /var/log/nextjs/app.logReverse proxy and access logs: Flag request lines that begin with http:// or https:// (absolute-form URIs) combined with Upgrade: websocket. That combination should never appear in legitimate traffic.
grep -E '"[A-Z]+ https?://' /var/log/nginx/access.logNetwork monitoring: Alert on outbound connections from the Next.js process to 169.254.169.254:80, metadata.google.internal:80, or any RFC 1918/link-local address on port 80 that does not correspond to a configured rewrite route.
Network-level detection via iptables logging or VPC flow logs can be a more reliable layer in some environments because many Next.js deployments rely on limited or development-only request logging, and HTTP upgrade handling switches the connection to a socket-level flow before a normal request/response cycle completes.
Patch and Mitigate: Step by Step
You have two main options: upgrade Next.js to a fixed release, or front the app with a reverse proxy that blocks the exploit pattern.
Upgrade Next.js (Preferred Fix)
The patch is a two-line change in the upgrade handler that adds the finished and statusCode checks. Update to Next.js 15.5.16 or 16.2.5, or the latest patched version in your release line.
- Check your current version:
npm list nextor inspectpackage.json. - Update:
npm install next@15.5.16(ornext@16.2.5for v16 users). - Restart the application process.
- Verify by sending the exploit payload against your own staging instance. The connection should close immediately with no data returned.
Note: Next.js 13.x and 14.x stable are not affected by this CVE, and no backported patch is listed for those stable release lines. If you're on either major version, you need to upgrade to the latest fixed stable release of Next.js (next@latest). Upgrade to the latest available patch in your branch to be safe.
Reverse Proxy Mitigation (If You Cannot Patch Immediately)
If a same-day upgrade is not feasible, place a reverse proxy in front of Next.js and add a rule to reject absolute-form request URIs. Nginx, Caddy, and HAProxy reject these by default in most configurations, but an explicit rule adds defense-in-depth.
For nginx:
server {
listen 80;
server_name your-app.example.com;
# CVE-2026-44578 mitigation: block absolute-form request URIs
if ($request_uri ~ "^https?://") {
return 400;
}
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}The rule uses $request_uri (the raw, pre-normalization value) rather than $uri to prevent encoding bypass. Do not blanket-block Upgrade: websocket headers. That breaks every legitimate WebSocket feature in your application. The vulnerability is specific to the combination of an upgrade header and an absolute-form request line. The absolute-form URI is the distinguishing signal to block.
For teams using a Docker-based deployment pipeline, add the nginx container as a sidecar or gateway in front of the Next.js container. The tradeoffs between self-hosting and managed hosting apply here: managed platforms often handle reverse proxy configuration and patching for you, while self-hosted setups require you to own every layer of the stack.
Enforce IMDSv2 on AWS
Even on unpatched instances, enforcing IMDSv2 (HttpTokens=required) neutralizes the most damaging exploitation path. This GET-only SSRF cannot perform the PUT request required to mint an IMDSv2 session token, and the X-Forwarded-For header that http-proxy adds causes IMDSv2 to reject the request outright.
# Check current IMDS configuration:
aws ec2 describe-instances --instance-ids i-XXXXX \
--query 'Reservations[].Instances[].MetadataOptions'
# Enforce IMDSv2:
aws ec2 modify-instance-metadata-options \
--instance-id i-XXXXX \
--http-tokens requiredApply least-privilege permissions to your EC2 instance roles so that even if credentials are somehow exfiltrated, the blast radius is limited.
What This Means for Co-Located Backends
Many deployments run Next.js alongside other internal services in the same server, container network, or VPC. If that Next.js instance is unpatched, it can become a path to reach co-located admin interfaces, content APIs, or other backend services that listen on port 80 through a proxy. Choosing a CMS with strong security defaults reduces your exposure, but the frontend layer still has to hold its own.
Because SSRF requests originate from a trusted internal server, they can often bypass or misuse internal authentication and trust controls on those services. The request originates from the Next.js process itself, so it inherits the network trust of that runtime.
Services behind a properly configured reverse proxy are better positioned, but the frontend layer is still part of your security surface. Keeping Next.js patched matters just as much as hardening the rest of the stack.
For a stronger overall posture, pin dependency versions, run npm audit regularly, enforce IMDSv2 on AWS instances, and use managed hosting or reverse proxies in front of self-hosted Node.js processes. If you run a Next.js frontend paired with a Strapi backend, keeping both layers patched and fronted by a proxy is non-negotiable.
Teams that want to offload infrastructure risk for the CMS layer can use Strapi Cloud, which handles reverse proxy configuration, TLS, and patching out of the box. The pattern behind this CVE, two handlers calling the same function with asymmetric safety checks, is common. When safety conditions are added to one code path, every other code path calling the same underlying function needs the same review.
Key Takeaways for Self-Hosted Next.js Teams
If you self-host Next.js, start with three checks: confirm your version, confirm whether a reverse proxy is in front of the app, and confirm whether cloud metadata is reachable from that runtime. Then patch to a fixed release line as soon as you can. If that upgrade has to wait, block absolute-form request URIs at the proxy layer and tighten metadata access, especially on AWS.
This bug is a good reminder that framework-level request handling deserves the same scrutiny as your own application code. A small mismatch between two handlers created a surprisingly useful SSRF primitive, and that is exactly the kind of issue that slips through until it shows up in production. If you run Next.js as the frontend for a headless CMS like Strapi, the risk is concrete: an unpatched Next.js instance can proxy requests straight to your content API, admin panel, or any co-located service.
Strapi's API-first backend, role-based access controls, and separate admin authentication give you defense in depth, but only if the frontend layer in front of it is not an open relay. Patch Next.js, front it with a reverse proxy, enforce IMDSv2, and keep your CMS behind its own access controls so a single vulnerability in one layer cannot cascade through the rest of the stack.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.