1. What CVE-2026-44578 actually is
On 2026-05-06, Tim Neutkels published GHSA-c4j6-fc7j-m34r: a server-side request forgery in the Next.js built-in Node.js server. Crafted Upgrade: websocket requests bypass the safety gate that normal HTTP requests are subjected to. The patch text is unusually clear about the original assumption:
Upgrade requests are only proxied when routing has explicitly marked them as safe external rewrites.
That phrasing is the whole story. Next.js has an intentional proxy feature — rewrites — whose job is to forward requests to upstream URLs. The HTTP path applied a safety check that gated proxying behind the "safe external rewrites" allowlist. The WebSocket-upgrade path didn't. So an attacker who could open a WS handshake against a self-hosted Next.js server could ride the rewrites proxy to anywhere the server could reach — including cloud-metadata endpoints (IMDS), IAM token endpoints, internal admin dashboards, anything on RFC 1918 space the deployment could route to.
| Field | Value |
|---|---|
| CVE | CVE-2026-44578 |
| GHSA | GHSA-c4j6-fc7j-m34r |
| CVSS 3.1 | 8.6 High — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N |
| Affected | Next.js >=13.4.13 <15.5.16 and >=16.0.0 <16.2.5 |
| Fixed | 15.5.16 · 16.2.5 |
| Exposure | Self-hosted Next.js using the built-in Node.js server |
| Not exposed | Vercel-hosted deployments (different edge layer handles WS upgrades) |
| Discoverer | Tim Neutkels |
The deployment-topology split is worth lingering on. The same code is shipped in both cases — but on Vercel, the upgrade hits Vercel's edge first, and the vulnerable code path is never reached. Self-hosted operators run the bare Node server. That's a Layer 2 responsibility-sphere distinction in TLCTC terms: @Vercel shoulders the WS-upgrade boundary; @Org shoulders it when self-hosting. Identical code, different control point, different cluster of operators with the gap.
A successful SSRF here gives the attacker an HTTP response body from any endpoint the server can reach. The high-impact targets on AWS, GCP, and Azure are well-known: IMDS (169.254.169.254), instance IAM credentials, GCE metadata server, Azure IMDS — all returning IAM material that can then be presented to the cloud control plane. Self-hosted Kubernetes adds the cluster-internal API server and the service-account token mount. None of those expect a request from the public-facing app server.
2. Three Next.js "SSRF eras"
Next.js has shipped at least three families of request-forwarding flaws across its lifetime. Each headline reads "SSRF" or "middleware bypass" or "proxy abuse." Underneath, two distinct TLCTC clusters carry the weight, and one of them carries it twice.
Era 1 — Image-optimizer SSRFs (feature-as-intended)
The /api/image endpoint accepts a remote URL parameter, fetches it on the server, transforms it (resize, format conversion), and returns the result. That's the entire feature. Multiple advisories over the years tightened this surface — allowlists by hostname, then by explicit remotePatterns, then narrowing protocols. Each iteration was a follow-up to attackers pointing the URL parameter at internal services. The proxy was always intentional; the question was only how tightly the allowlist could be drawn.
TLCTC verdict: #1 Abuse of Functions. The image optimizer is a documented proxy. Pointing it at IMDS abuses the documented feature; the safety gate is just the allowlist around it. R-EXEC and R-CRED don't apply at the SSRF step; the credential application happens at #4 after exfiltration.
Era 2 — Middleware/header bypasses (trust-check flaws)
A different family entered the discourse with CVE-2025-29927 in early 2025. A client-controllable header (x-middleware-subrequest) was honoured as an internal-trust marker; setting it from outside caused middleware to skip its own checks, exposing protected routes. The May 2026 advisory bundle that shipped alongside 44578 added segment-prefetch and dynamic-route-parameter variants — same shape: a code path that should have run the middleware did not.
TLCTC verdict: #2 Exploiting Server. The user-validated rule applies: bypass means something has not worked as designed — that is a code flaw, not an abuse. The middleware never executes; nothing about its functionality is "used" by the attacker. A flaw in trust-handling lets the attacker reach what middleware should have gated. This is structurally not SSRF — same family of headlines, different generic vulnerability.
Era 3 — WS-upgrade SSRF (44578)
And now CVE-2026-44578. The bug is structurally identical to the Era 2 family at first glance: a code path that should have run the "safe external rewrites" check did not. Easy to call #2 on autopilot. But the abused thing is different. The rewrites proxy executes. It does exactly what it's designed to do — forward the request to the URL the WS upgrade specifies and return the response. The missing safety check is the bypass primitive; the rewrites proxy is the abused capability. TLCTC clusters by the abused capability, not by the bypass.
TLCTC verdict: #1 Abuse of Functions — same as Era 1. The rewrites feature is the new /api/image: a server-side proxy that's documented and intentional. The exploit reaches it through a different code path (WS upgrade instead of URL parameter), and the safety gate around it was differently configured (allowlist on remote URLs vs allowlist on safe external rewrites), but the underlying generic vulnerability is the same: a server with a proxy feature pointed somewhere it shouldn't be allowed to point.
| Era | Mechanism | Abused capability executes? | Cluster |
|---|---|---|---|
| Era 1 Image-optimizer |
URL parameter points /api/image at internal target. Bypass = inadequate allowlist. |
Yes — image fetcher runs, returns body | #1 Abuse of Functions |
| Era 2 Middleware/header bypass (29927, segment-prefetch, dynamic-route) |
Header injection / path-resolution flaw skips middleware entirely. Bypass = the bug itself. | No — middleware never runs | #2 Exploiting Server |
| Era 3 WS-upgrade SSRF (44578) |
Upgrade: websocket skips the safe-rewrites gate. Bypass = the missing check. |
Yes — rewrites proxy runs, returns body | #1 Abuse of Functions |
Two of three eras are the same cluster. The middle one isn't — and isn't trying to be. That's the taxonomy doing useful work: separating real-but-related issues whose defences live in different places.
3. Why the (non-)shift matters
OWASP A10 calls all three eras "SSRF" or close to it. CVSS scores them roughly the same band. The CVE titles are nearly identical. If your taxonomy stops there, your defence playbook stops there too: patch, allowlist, add WAF rules, repeat next quarter.
The TLCTC cluster shift between Era 1/3 (#1) and Era 2 (#2) maps to a real shift in control placement:
- Era 1 (#1) control: tighten the allowlist inside the feature. The image optimizer is going to fetch URLs — that's its job — so the safety is at
remotePatterns, protocol restrictions, response-size limits, and outbound DNS resolution. The proxy itself is the surface you're defending. - Era 2 (#2) control: harden the trust check that the bug bypassed. Strip
x-middleware-subrequestat the edge. Validate segment-prefetch paths. Defence sits at input validation and trust-boundary enforcement, not at "what does the route do?" - Era 3 (#1) control: same playbook as Era 1, but the surface is the
rewritesproxy this time, not/api/image. Tighten which destinations rewrites are allowed to hit. BlockUpgrade: websocketrequests at the reverse proxy that aren't for known WebSocket endpoints. Restrict outbound to IMDS and RFC 1918 ranges from the application tier.
The clean separation matters because the Era 1 controls don't fix Era 3 and vice versa. A team that patched the image-optimizer URL allowlists in 2024 is not protected from 44578. A team that hardened middleware against 29927 in 2025 is not protected either. But a team that understood Era 1 as a #1 problem — this is a proxy feature, so the safety lives where outbound destinations are decided — was already thinking about rewrites the same way, and was closer to ready when 44578 dropped. TLCTC is the layer that survives the patch cycle.
Did the abused capability execute as designed (just for a forbidden target)? If yes → #1 Abuse of Functions. If no — if the bug consists of a protective control failing to run at all — #2 Exploiting Server. The bypass mechanism (header injection, missing validation, parser confusion) does not determine the cluster. The abused capability does.
4. Attack path and DRE
The full TLCTC v2.1 notation for 44578:
#1 ||[api][@External→@Org]|| →[Δt=VC-3] #4 + [DRE: C]
- Step 1 — #1 Abuse of Functions. The attacker sends a crafted
Upgrade: websocketrequest to a self-hosted Next.js endpoint. The WS-upgrade handler skips the safe-external-rewrites gate; the rewrites proxy then makes an outbound request to the attacker-chosen destination (typicallyhttp://169.254.169.254/latest/meta-data/iam/security-credentials/or the equivalent on GCP/Azure). The response body — IAM credentials, scoped tokens, internal service responses — is returned to the attacker. Single coherent step under Axiom VI: one generic vulnerability, one cluster. - Boundary —
||[api][@External→@Org]||. The attack crosses the API boundary, source sphere@External(the attacker), target sphere@Org(the self-hosted Next.js deployment). Required for any externally-originating attack on an org-owned surface. - Δt = VC-3 (minutes). SSRF-to-credential-use is operator-paced. Once the exploit fires and credentials come back, an attacker types them into
aws sts get-caller-identityin minutes, not seconds. (Could be VC-4 if fully scripted, but the canonical case is interactive triage of what just landed in the response body.) - Step 2 — #4 Identity Theft. The credentials returned in step 1 are presented to the cloud control plane. R-CRED applies: credential acquisition maps to the enabling cluster (already counted as part of step 1's response body — no separate acquisition step), credential application is always #4.
- + [DRE: C]. Confidentiality is the immediate data risk event: cloud credentials, IAM tokens, internal service responses, admin dashboard content, any data the proxy could reach. Whether the credentials are then used for further harm (Integrity, Accessibility downstream) is out of scope for the 44578 path proper — that's the next attack-path study, anchored to whatever the attacker does with the stolen identity.
rewrites proxy reached via WS-upgrade bypass of the safe-external-rewrites gate5. Sidebar — Capital One 2019, same cluster
The 2019 Capital One breach is the canonical cross-stack reference for this pattern. A misconfigured WAF (ModSecurity in a custom deployment) had an instance-metadata-fetching feature that an external attacker could direct at AWS IMDSv1 on 169.254.169.254. The IMDS returned IAM credentials for the WAF's instance role; those credentials had s3:ListBucket and s3:GetObject on roughly 100 million records' worth of buckets. The rest is the breach.
The feature being abused was a URL-fetching helper inside the WAF — intended functionality, not a parser flaw. The IMDS endpoint was an intended AWS feature, accessible to any process on the instance, requiring no auth. The credentials the IMDS returned were intended too. Three intentional features stacked into a credential-exfiltration chain. TLCTC verdict: #1 Abuse of Functions at the WAF step, #4 Identity Theft at credential application, DRE: C at the data-exfil endpoint. Same shape as 44578.
The relevance: CVE-2026-44578 happens on Node.js inside Next.js's request handler, not on the JVM inside a WAF. The "feature" is a rewrites proxy, not a URL-fetching helper. The bypass primitive is a WS upgrade, not a WAF rule misconfiguration. Every detail differs except the cluster. That's the test of a useful taxonomy: it stays anchored to the cause when the surface changes.
Most retrospectives lead with "SSRF" or "IMDS misconfiguration." Both labels are accurate but neither is portable: SSRF is a code-level pattern, IMDS is an AWS-specific surface. The TLCTC reading — "#1 abuse of a server-side fetch feature, chained into #4 via IAM credentials returned in-band" — describes the cause in terms that survive the next stack and the next cloud. That's why the 2026 WS-upgrade SSRF reads like a remix of 2019: it is one, at the cluster level.
6. Controls — pre-event and post-event
For self-hosted Next.js operators, the immediate work splits cleanly along the Bow-Tie:
Pre-event (before the SSRF fires)
- Patch. Upgrade to 15.5.16 or 16.2.5. Non-negotiable, do this first.
- Reverse-proxy filter. If you can't patch immediately, block
Upgrade: websocketat the reverse proxy for any request path that isn't an explicit WebSocket endpoint. Most self-hosted Next.js apps have zero legitimate WS routes; an explicit allowlist is the workaround. - Rewrites audit. Read your
next.config.jsrewritesentries. Any wildcard or template that resolves to an attacker-controlled host is an Era 1 problem still waiting to be tripped. Lock them to specific upstream hostnames. - Egress restriction. The application tier should not be able to reach
169.254.169.254,169.254.170.2, the GCE/Azure metadata equivalents, or RFC 1918 internal services it doesn't legitimately use. NetworkPolicy / security group / egress firewall at the cloud edge.
Post-event (after credentials leak)
- IMDSv2. Mandatory hop-limit-1, session-token-required IMDS on AWS. Same SSRF then returns nothing useful because the IMDSv1 GET response path is gone.
- Short-lived, narrowly-scoped IAM. The instance/pod role should hold the minimum to function. A 44578 hit that surfaces credentials with
s3:*on every bucket is on the IAM owner, not on Next.js. - Egress telemetry. Anomalous outbound from the application tier to metadata IPs is a high-signal detection. Most application tiers should never talk to
169.254/16; alert on the first packet. - Credential-use detection at the cloud edge. CloudTrail / Azure Activity / GCP Audit alerts on instance-role credential use from outside the instance — flags the #4 step even if the #1 step was missed.
7. Closing
Two years from now, Next.js will have patched some new request-forwarding flaw. The bypass primitive will be new — maybe a Server Action that loses its origin check, maybe a Server Component that fetches an untrusted URL inside an RSC payload, maybe something nobody has prefigured yet. Headlines will call it SSRF again. CVSS will land it in the same band. And, if the abused capability still executes as designed (just for a forbidden target), TLCTC will quietly write #1 Abuse of Functions in the cluster column — the same column it filled in 2024 for the image optimizer, in 2019 for Capital One, and today for the WS-upgrade handler.
The bypass primitive keeps moving. The cluster doesn't. That's not a limitation of the framework. That's the framework working.
Sources
- Cybersecurity News, 2026-05 — Next.js Vulnerability Exposes Credentials (origin article that prompted this analysis)
- GitHub Security Advisory, 2026-05-06 — GHSA-c4j6-fc7j-m34r — Server-side request forgery in applications using WebSocket upgrades
- Vercel/Next.js Security Advisories — github.com/vercel/next.js/security/advisories (the May 2026 bundle context: GHSA-492v-c6pp-mqqv, GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6, GHSA-h64f-5h5j-jqjh)
- CVE-2025-29927 — GHSA-f82v-jwr5-mffw — the canonical Era 2 middleware bypass
- Krebs on Security, 2019-07-29 — Capital One Data Theft Impacts 106M People
- TLCTC framework — tlctc.net (cluster definitions, axioms, R-* rules)
- Companion analysis — CVE-2026-46300 ("Fragnesia") — TLCTC Analysis
- Companion analysis — CVE-2026-31431 ("Copy Fail") — TLCTC Analysis
This analysis applies the TLCTC v2.1 framework: ten cause-oriented threat clusters, the Bow-Tie SRE/DRE separation, attack path notation with Δt velocity classes and v2.1 boundary operators (||...|| for system-edge crossings). The #1 vs #2 verdict in this post follows the user-validated rule: did the abused capability execute as designed? — if yes → #1; if the bug consists of a protective control failing to run at all → #2. For framework definitions: tlctc.net.
Licensed under CC BY 4.0. Framework: tlctc.net · Source: github.com/Barnes70/TLCTC