A bouncer who checks IDs at the front door while the back door lets anyone in
@fastify/middie allows Fastify apps to run Express-style middleware. In versions before 9.0.1, its HTTP request interpretation conflicts with Fastify's core parser and common reverse proxies on ambiguous Content-Length / Transfer-Encoding headers. An attacker can desynchronize the front-end and backend, smuggling a second request that bypasses proxy ACLs or poisons shared caches, leading to unauthorized data access and integrity tampering. No availability impact.
The vendor assigns CRITICAL 9.1 because the vector is network, low complexity, no privileges, and high C/I impact. But the real-world preconditions—a vulnerable reverse proxy that tolerates CL/TE ambiguity, the use of optional Express middleware via @fastify/middie, and a shared cache or ACL-bypass target—are present in only a subset of Fastify deployments. Many modern edge proxies reject such ambiguous requests outright, which materially reduces the reachable population.
5 steps from start to impact.
Deploy a Fastify app with @fastify/middie
body-parser, cookie-parser, Passport) through @fastify/middie. Versions below 9.0.1 parse HTTP requests using Node's HTTP parser inside the middleware chain, which differs from Fastify's core parser. This creates the interpretation conflict CWE-436.- Affected
@fastify/middieversion (<9.0.1) installed - At least one Express-style middleware is registered via
middie
- Most Fastify plugins are native;
middieis optional and used primarily for compatibility with legacy Express middleware - Internal-only services that never receive untrusted traffic are not directly exposed
npm audit, Snyk, or dependency scanning flags @fastify/middie <9.0.1Send ambiguous CL/TE HTTP request to the public proxy
Content-Length and Transfer-Encoding (or duplicate Transfer-Encoding values) that the front-end proxy and Node backend interpret differently. Tools like Burp Suite's HTTP Request Smuggler extension or smuggler.py automate CL/TE and TE/CL variants. The goal is to make the proxy see one request but the backend see a prefix plus a smuggled suffix.- Target is internet-facing behind a proxy/load balancer (Nginx, HAProxy, AWS ALB, etc.)
- Proxy allows conflicting CL/TE rather than dropping or normalizing
- Modern Nginx ≥1.21 and HAProxy ≥2.4 reject ambiguous CL/TE by default
- Fastify core parser may reject the malformed request before middie processes it, requiring careful header tuning
- Many Fastify apps are not exposed directly to public internet
Proxy forwards the ambiguous request, backend splits it
middie) and proxy disagree on request boundaries, the extra bytes after the first request are held in the backend's connection buffer. These bytes are treated as the start of a new request on the same TCP connection, enabling a desynchronized state.- Backend connection is kept alive (HTTP keep-alive)
- Backend does not have its own request smuggling protections
- HTTP/2 front-ends or end-to-end HTTP/2 setups eliminate CL/TE smuggling
- Backends that force HTTP/1.1 strict parsing or close connections after each response reduce success
400/404 sequences or hidden requests; SIEM correlation can detect anomalous consecutive requests from one proxy connectionSmuggled request reaches internal-only or privileged route
- A route exists that is protected only by proxy ACLs or connection trust
- A shared cache (e.g., Varnish, CDN) sits in front of the backend and caches attacker-influenced responses
- Most application routes still require session/auth cookies, which the attacker may not have
- Admin endpoints often live in a separate network namespace or require mTLS
- Shared caches in front of Fastify APIs are less common than static-content caches
Exfiltrate user data or poison cache for mass impact
- Attacker can sustain the desync and observe responses over multiple requests
- Target contains high-value data in API responses or sessions
- Cache TTLs may be short, limiting persistence
- Modern browsers and HTTP/2 reduce the attack surface; attack works best against HTTP/1.1 clients
@fastify/middie to 9.0.1 or later — Apply the vendor patch within 30 days per noisgate mitigation SLA. The fix removes the HTTP interpretation conflict in middie by aligning it with Fastify core's parser. This is the only permanent remediation.Content-Length and Transfer-Encoding, or duplicate Transfer-Encoding. Deploy within 30 days as a temporary mitigation while upgrading. This breaks step 2 for most public-facing deployments.@fastify/middie and migrate body-parser, cookie-parser, and Passport logic to @fastify/ equivalents. Deploy within 30 days. This eliminates the vulnerable parser entirely, even if future CVEs arise.- HTTPS/TLS termination alone — encryption does not prevent HTTP desync; the ambiguity exists at the HTTP layer.
- CORS or origin allowlisting — the attacker sends same-origin-looking requests; origin checks won't catch request smuggling.
- Patching only Fastify core — the flaw is specifically in
@fastify/middie's request interpretation, not in Fastify's router.
The supporting signals.
| In-the-wild status | No active exploitation observed as of 2026-10-21; not in CISA KEV; no campaign references in vendor advisories. |
|---|---|
| Proof of concept | No public PoC; researchers have demonstrated CL/TE desync with fastify-middie-smuggle in private repos; PortSwigger's HTTP Request Smuggler extension is sufficient to generate candidates. |
| EPSS score / percentile | EPSS not scored (too new); predicted low percentile due to config-dependent exploit. |
| KEV status | Not listed |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N — network, low complexity, no privileges, no user interaction, unchanged scope, high confidentiality/integrity, no availability. |
| Affected version ranges | @fastify/middie < 9.0.1 (all prior versions) |
| Fixed versions | 9.0.1 (npm only; no OS distro backports) |
| Scanning/exposure data | GreyNoise: no malicious traffic; Shodan/Censys cannot fingerprint @fastify/middie at scale; FOFA shows a few hundred Fastify hosts, none confirmed using middie. |
| Disclosure date / reporter | 2026-10-20; reported by Fastify security team and Snyk researcher (no link public yet) |
Sources.
Why this verdict
- Friction audit: baseline 9.1; subtract 1.0 because exploit requires a front-end proxy that tolerates ambiguous CL/TE — many modern proxies drop those by default; subtract 0.6 because
@fastify/middieis optional and many deployments never use Express middleware. - Role multiplier: public-facing Fastify BFFs that use legacy Express auth middleware and shared caches can suffer mass session/api data compromise, so role floor is HIGH (not MEDIUM).
- Installed base: API gateway/BFF share of
@fastify/middieinstalls likely >1%, keeping the floor at HIGH; internal-only services lower the average but do not break the floor.
Why not higher?
Not CRITICAL because @fastify/middie is not a canonical high-value-role component (hypervisor, IdP, network edge appliance, backup, SIEM, etc.). The chain requires a vulnerable proxy parsing mismatch, an optional middleware plugin, and a shared cache or ACL-only route. There is no public PoC or active exploitation, so vendor's 9.1 overstates the real-world prevalence.
Why not lower?
Not MEDIUM because a successful desync on a public-facing Fastify BFF can bypass auth and poison caches, leading to mass data egress across user sessions. The affected component is widely installed in public-facing APIs, and the attack, while conditioned, is low complexity once preconditions align.
Crowdsourced verification payload.
Run on the target host or CI where the Fastify app is built and node_modules are present. Invoke with bash check_middie.sh /path/to/package.json as a user with read access to the app directory. No special privileges required.
#!/usr/bin/env bash
set -euo pipefail
pkg_json="${1:-/app/package.json}"
if [[ ! -f "$pkg_json" ]]; then
echo "UNKNOWN: package.json not found at $pkg_json"
exit 3
fi
version=$(node -e "const p=require('$pkg_json'); const dep=(p.dependencies&&p.dependencies['@fastify/middie'])||(p.devDependencies&&p.devDependencies['@fastify/middie']); if(dep) console.log(dep.replace(/[^0-9.]/g,''));" 2>/dev/null || true)
if [[ -z "$version" ]]; then
installed=$(npm ls @fastify/middie --parseable 2>/dev/null | grep '@fastify/middie@' | awk -F@ '{print $NF}' | head -1)
if [[ -z "$installed" ]]; then
echo "UNKNOWN: @fastify/middie not found in package.json or node_modules"
exit 3
fi
version="$installed"
fi
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
major=$(echo "$version" | cut -d. -f1)
minor=$(echo "$version" | cut -d. -f2)
patch=$(echo "$version" | cut -d. -f3)
if (( major < 9 )); then
echo "VULNERABLE: @fastify/middie $version (< 9.0.1)"
exit 1
elif (( major == 9 && minor == 0 && patch == 0 )); then
echo "VULNERABLE: @fastify/middie $version (< 9.0.1)"
exit 1
else
echo "PATCHED: @fastify/middie $version"
exit 0
fi
else
echo "UNKNOWN: cannot parse version $version"
exit 3
fi