A poorly-guarded normalization step lets a stranger yank the power cord on your Node process
@fastify/middie is the Express-style middleware adapter for Fastify. In versions 9.1.0 through 9.3.2, the standalone engine's URL normalization step throws an uncaught exception on certain malformed request paths (CWE-248). Because the throw escapes the request scope and propagates up the event loop, the entire Node worker terminates — not just the offending request. Any unauthenticated remote client that can reach the HTTP listener can trigger it, and repeated requests turn a single crash into a rolling denial of service.
The vendor's HIGH (7.5) label maps to CVSS AV:N/AC:L/PR:N/UI:N/C:N/I:N/A:H — network-reachable, no auth, no user interaction, availability-only. That vector is technically accurate, but it assumes every affected process runs bare with no supervisor. In practice, Node apps run under PM2, systemd, or a Kubernetes readiness probe, which recycle crashed workers in seconds. The bug is real and worth patching, but the *sustained* impact profile is closer to MEDIUM than HIGH once you account for how these apps actually ship.
4 steps from start to impact.
Locate a Fastify service using middie standalone
@fastify/middie and uses its standalone URL parsing branch.- Public HTTP(S) endpoint
- App registers @fastify/middie 9.1.0–9.3.2
- Standalone engine path in use
- Middie is a subset of Fastify installs; standalone mode is a further subset
- Reverse proxies (nginx, Cloudflare, ALB) may strip/normalize the malformed URL before it reaches Node
npm audit, GitHub Advisory) flag the vulnerable range once the GHSA is publishedCraft the malformed path that trips normalization
curl, httpx, or a two-line Node script are sufficient — no exploit framework required. Public PoC will land in security advisories once the GHSA is broadly disclosed.- Ability to send arbitrary HTTP request line
- Path not rewritten by upstream proxy
- WAF path-normalization rules (ModSecurity CRS, Cloudflare Managed Rules) often reject the malformed URI
- HTTP/2 frontends re-encode :path
Uncaught exception crashes the Node worker
- No
process.on('uncaughtException')swallow - Single-worker or all-workers-affected topology
- Cluster/PM2/systemd/k8s restart the worker within seconds
- Load balancers reroute during restart
Restart= log entriesSustain the DoS by looping the request
- Sufficient request volume to outpace restart timers
- No rate limit on the target route
- Rate limiting (nginx
limit_req, Cloudflare Rate Limiting, AWS WAF) throttles the loop - Autoscalers provision more workers than the attacker can crash
The supporting signals.
| In-the-wild exploitation | None observed as of 2026-07-02; no GreyNoise tag, no CISA KEV entry |
|---|---|
| Public PoC | No public PoC repo yet; trivial to derive from the GHSA patch diff once merged |
| EPSS | Not yet scored — expect <0.5% given DoS-only impact and Node ecosystem norms |
| KEV status | Not listed on CISA KEV |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H — network, no auth, availability-only |
| Affected versions | @fastify/middie 9.1.0 through 9.3.2 |
| Fixed version | Upgrade to the next patched release above 9.3.2 per the fastify/middie GHSA |
| Exposure population | npm shows middie at ~250k weekly downloads; standalone-engine usage is a subset — likely low single-digit % of Fastify prod deployments |
| Disclosure | 2026-07-01, reported through the OpenJS Foundation CNA process |
| CWE | CWE-248 — Uncaught Exception |
noisgate verdict.
Downgraded to MEDIUM because impact is availability-only on a stateless application tier that is universally deployed under a process supervisor (PM2, systemd, Kubernetes) that recycles crashed workers in seconds. The single decisive factor is the *bounded blast radius*: no confidentiality or integrity impact, and no privileged role — a crashed Node worker is not a compromised host.
Why this verdict
- Availability-only impact: the CVSS vector is
C:N/I:N/A:H. Even a successful chain produces a crashed Node worker, not RCE, not data disclosure, not privilege escalation. - Restart economics favor the defender: every non-toy Fastify deployment runs under PM2, systemd, Docker
restart: always, or a k8s liveness probe. Restart latency (1–10s) is measured in the same units as the attacker's request loop, so sustaining an outage requires continuous flood volume rather than a single shot. - Upstream normalization absorbs most payloads: the malformed paths that trigger the throw are exactly the shapes that reverse proxies (nginx, Cloudflare, ALB) and WAFs (CRS 942/932 rules) already reject or rewrite.
- Role multiplier: middie is an *application-tier* middleware adapter. It is NOT deployed as an identity provider, hypervisor, CA, backup admin, or kernel-mode agent. Worst-case blast radius stops at the individual Fastify service; there is no fleet, tenant, or domain escalation path from an uncaught exception. No high-value-role floor is triggered.
- No KEV, no public PoC, no active exploitation as of disclosure +1 day.
Why not higher?
HIGH is defensible if you take the CVSS vector at face value, but doing so ignores that Node's operational model already treats uncaught exceptions as recoverable events. There is no confidentiality or integrity impact, no chain into adjacent systems, and no privileged role to compromise. Calling this HIGH would burn defender attention that belongs on the auth-bypass class of middie CVEs (CVE-2026-6270, CVE-2026-33804) that are genuinely worse.
Why not lower?
LOW would understate the reality that an unauthenticated remote attacker with a two-line curl script can degrade service availability, particularly for smaller deployments without WAF or autoscaling in front. For any internet-exposed API where uptime SLAs matter, this is worth patching in the normal cycle rather than deferring indefinitely.
What to do — in priority order.
- Pin middie to the fixed version in your lockfile — Update
package.json/package-lock.json/pnpm-lock.yamlto the next release above 9.3.2, run CI, and roll to prod. As a MEDIUM verdict there is *no noisgate mitigation SLA* — schedule this into the normal dependency-update cadence within the 365-day remediation window, but there is no reason not to ship it in the next sprint. - Enable URI normalization at the edge — Turn on strict path normalization / reject-malformed-URI rules on your reverse proxy or WAF (nginx
ignore_invalid_headers, Cloudflare Managed Rules, AWS WAFAWSManagedRulesCommonRuleSet). This blocks the crash payload before it ever reaches Node and doubles as defense-in-depth against the other middie normalization CVEs. - Confirm process supervision is actually restarting workers — Verify PM2
max_restartsis not set punitively low, systemdRestart=alwayswith a shortRestartSec, or k8s liveness/readiness probes are configured. A misconfigured supervisor turns a MEDIUM DoS into an actual outage. - Add per-IP and per-path rate limiting — Attach
@fastify/rate-limitor an edge rate limiter to cap request rate on any path — sustained crash loops require volume the attacker cannot afford if you throttle at 100 req/s per IP. - Register a defensive
uncaughtExceptiontelemetry hook — Do NOT swallow the exception silently — that hides real bugs. Log it, emit a metric, then let the process exit and restart. This gives you signal that exploitation is being attempted.
- Authentication in front of the route: the crash happens during URL normalization, before your auth middleware runs — requiring a login does not help.
- TLS or mTLS: the payload is a legal HTTP request line; encryption does not change the bytes middie sees.
- Content-Security-Policy / HSTS / other browser headers: this is a server-side crash triggered by curl-class clients, no browser involved.
- Node's
--unhandled-rejections=warnflag: this concerns Promises, not synchronous throws in middleware.
Crowdsourced verification payload.
Run this on any host or CI runner that has read access to your Node project's lockfile. Invoke as ./check-middie.sh /path/to/project — no elevated privileges required. Exits 0 for PATCHED, 1 for VULNERABLE, 2 for UNKNOWN.
#!/usr/bin/env bash
# noisgate: CVE-2026-14181 — @fastify/middie uncaught exception in URL normalization
# Vulnerable: 9.1.0 <= version <= 9.3.2
# Fixed: first release above 9.3.2 (check vendor GHSA for exact tag)
set -u
PROJECT="${1:-.}"
if [ ! -d "$PROJECT" ]; then
echo "UNKNOWN: project path not found: $PROJECT" >&2
exit 2
fi
cd "$PROJECT" || { echo "UNKNOWN"; exit 2; }
# Prefer lockfile source of truth; fall back to node_modules.
LOCK=""
for f in package-lock.json pnpm-lock.yaml yarn.lock; do
[ -f "$f" ] && LOCK="$f" && break
done
VER=""
if [ -n "$LOCK" ]; then
VER=$(grep -Eo '"@fastify/middie"[^}]*"version"[[:space:]]*:[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' "$LOCK" \
| grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [ -z "$VER" ]; then
VER=$(grep -A1 '@fastify/middie' "$LOCK" | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
fi
fi
if [ -z "$VER" ] && [ -f "node_modules/@fastify/middie/package.json" ]; then
VER=$(grep -Eo '"version"[[:space:]]*:[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' \
node_modules/@fastify/middie/package.json | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
fi
if [ -z "$VER" ]; then
echo "UNKNOWN: @fastify/middie not found in $PROJECT"
exit 2
fi
# semver compare: vulnerable if 9.1.0 <= VER <= 9.3.2
IFS='.' read -r MA MI PA <<< "$VER"
vuln=1
if [ "$MA" -eq 9 ]; then
if [ "$MI" -gt 1 ] || { [ "$MI" -eq 1 ] && [ "$PA" -ge 0 ]; }; then
if [ "$MI" -lt 3 ] || { [ "$MI" -eq 3 ] && [ "$PA" -le 2 ]; }; then
vuln=0
fi
fi
fi
if [ $vuln -eq 0 ]; then
echo "VULNERABLE: @fastify/middie $VER is in range 9.1.0..9.3.2 (CVE-2026-14181)"
exit 1
else
echo "PATCHED: @fastify/middie $VER is outside the vulnerable range"
exit 0
fi
If you remember one thing.
@fastify/middie in the 9.1.0–9.3.2 range. Because this is a MEDIUM verdict, there is no noisgate mitigation SLA — go straight to the noisgate remediation SLA of ≤ 365 days, but realistically ship the version bump in your next dependency-refresh sprint since it is a one-line change. For any internet-exposed Fastify service, enable strict URI normalization at your WAF / reverse proxy this week as low-effort defense-in-depth, and verify PM2/systemd/k8s are configured to recycle crashed workers automatically. Do NOT elevate this above the middie auth-bypass CVEs (CVE-2026-6270, CVE-2026-33804) already in your queue — those are worse.Sources
- OpenJS Foundation CNA advisories
- fastify/middie GHSA-cxrg-g7r8-w69p (middleware path bypass)
- fastify/middie GHSA-v9ww-2j6r-98q6 (ignoreDuplicateSlashes bypass)
- fastify/middie GHSA-72c6-fx6q-fr5w (child plugin scope bypass)
- GitLab Advisory Database — @fastify/middie CVE-2026-33804
- GitLab Advisory Database — @fastify/middie CVE-2026-2880
- OpenCVE — fastify vendor tracker
- CWE-248: Uncaught Exception
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.