← Back to Feed CACHED · 2026-11-02 10:47:17 · CACHE_KEY CVE-2026-117531
CVE-2026-117531 · CWE-400

@fastify/multipart malformed multipart upload DoS hangs delayed file consumers

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A waiter takes the order, walks away with the plate, and the table never turns over

This vulnerability is in the @fastify/multipart plugin for Fastify, versions 5.3.0 through 10.1.6. When a request body ends inside a file part without a closing boundary, the plugin destroys the in-progress file stream without an error. If the application handler iterates request parts and performs asynchronous work before consuming the file, it attaches to a stream that never emits error or end; the connection hangs, and the server never responds. The issue is fixed in 10.1.7, where the parser error reaches the file stream and the delayed consumer settles with an error instead.

Vendor severity of MEDIUM (5.9) is too high for most deployments. The chain only works with an uncommon handler pattern—deferred file consumption—not the documented fast path of await request.file(). An unauthenticated client still needs many malformed sockets to exhaust resources, and the impact is availability only. For teams that do not have that specific deferred-consumer pattern, this is a LOW-priority package update. For the small subset that does, it should be treated as a HIGH-availability risk, not a fleet-level security compromise.

"Vendor puts this at MEDIUM, but the trigger depends on an uncommon Fastify handler pattern—treat as LOW unless you use deferred file consumers."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Find a Fastify route with delayed file consumption

The attacker targets an endpoint using @fastify/multipart where the handler loops over request.parts() or request.files() and awaits unrelated async work before consuming the file stream. The common await request.file() method is not affected because the stream is consumed immediately. This is the precondition that makes the malformed body hang a connection.
Conditions required:
  • Fastify app runs @fastify/multipart 5.3.0–10.1.6
  • Route accepts multipart file uploads
  • Handler performs async work before consuming part.file or stream
Where this breaks in practice:
  • Most Fastify examples consume the file immediately
  • Deferred consumption is application-specific; the attacker must identify such a route
  • The vulnerable route may require authentication or be internal
Detection/coverage: Not directly detectable from the outside; code review or tracing of multipart handlers is required.
STEP 02

Send a truncated multipart body with complete Content-Length

The client sends a multipart request whose body ends inside a file part with no closing boundary. The request has a valid Content-Length, the client does not abort, and the upload stays within any configured file size limit. The server sees a complete body but cannot finalize the part.
Conditions required:
  • Attacker can reach the route via HTTP
  • Request body ends inside a file part
  • No file size limit is exceeded
Where this breaks in practice:
  • Need to construct exact MIME framing; standard HTTP clients can do it with raw socket scripts
  • Reverse proxies may normalize or time out malformed uploads
Detection/coverage: Web application firewalls may flag missing multipart boundary, but many will pass it as syntactically plausible until MIME validation.
STEP 03

Trigger parser error that never reaches the file stream

In affected versions, the plugin destroys the in-progress file stream without propagating the parser error. A handler that has attached listeners is left waiting for error or end; neither arrives. The request promise never settles, and the connection remains open.
Conditions required:
  • Vulnerable version 5.3.0 through 10.1.6
  • Delayed consumer already attached to the file stream
Where this breaks in practice:
  • This is a narrow lifecycle bug; if the stream has already been consumed or destroyed by the handler, the hang does not occur
Detection/coverage: Server logs may show a multipart request that never completes and no matching response status.
STEP 04

Accumulate pending connections to exhaust server resources

The attacker repeats the malformed upload many times against the vulnerable route. Each hanging connection consumes a socket, file handle, and possibly a worker thread or event-loop callback. On Node.js Fastify servers, pending connections can accumulate until the process hits OS or framework limits and stops serving legitimate requests.
Conditions required:
  • Route accepts high enough concurrent connections
  • Server lacks per-IP connection limits or aggressive timeouts
  • Attacker can generate many sockets/IPs
Where this breaks in practice:
  • Default OS and reverse proxy connection limits still cap total sockets
  • Autoscaling or process supervisors may restart/take over
  • The attack must be sustained, because a single malformed request is just one idle connection
Detection/coverage: Node.js event loop lag, high open FDs, and reverse proxy 502/504 bursts are leading indicators; no CVE-specific scanner signature exists.
03 · Compensating Control

1
LOW 3.8→IGNORE 0.0
SEVERITY REDUCED
Upgrade to @fastify/multipart 10.1.7 or later — This is the only complete fix. The patched release propagates the parser error to the file stream, so a delayed consumer settles with an error instead of hanging. For LOW severity, noisgate mitigation SLA is not applicable; schedule the upgrade within the 365-day remediation window, but if you have the delayed-consumer pattern, treat as high-priority and patch immediately.
2
LOW 3.8→LOW 2.9
Apply reverse-proxy per-IP connection and rate limits — Limit the number of concurrent sockets and request rate per source IP for uploading endpoints. This does not fix the underlying hang but caps how many pending connections a single client can accumulate. Deploy within 30 days as best-effort hardening; if the endpoint is public and uses deferred consumers, deploy immediately.
3
LOW 3.8→LOW 3.2
Monitor event loop lag and open-file handle counts for Fastify workers — Track Node.js event loop lag, FD usage, and request durations. Alert when multipart routes show sustained in-flight requests without responses. This turns a silent resource leak into an on-call page, but does not remediate the underlying bug; set baseline alerts within 30 days.
What doesn't work
  • File size limits / bodyLimit — the malformed upload does not exceed the size limit; limits will not reject it.
  • Authentication alone — requiring auth only reduces the attacker population; any authenticated user could still hang the route if it uses the deferred pattern.
  • WAF multipart filtering — many WAFs normalize or block clearly malformed uploads, but a body ending in a file part can look like ordinary multipart traffic, so WAF coverage is inconsistent.
04 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo KEV listing; no known active campaigns reported.
Proof-of-concept availabilityNo public PoC repo named after this CVE found; pocindex.io query returns no nuclei/ExploitDB match at reassessment time.
EPSS score / percentileEPSS estimated 0.00043, 0.1st percentile (low immediate exploitability).
KEV statusNot in CISA Known Exploited Vulnerabilities catalog.
CVSS interpretationCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H — remote unauthenticated, high attack complexity due to rare handler pattern, availability-only.
Affected versions@fastify/multipart 5.3.0 through 10.1.6
Fixed versions@fastify/multipart 10.1.7; no distro backports because npm package
Scanning/exposureNot surface-scannable with Shodan/GreyNoise alone; exposure is proportional to Fastify routes with deferred file consumers.
Disclosure dateNot provided in authoritative record; null in structured metadata.
Reporting researcher/orgNot identified in authoritative record.

Sources.

  1. NVD — CVE-2026-117531
  2. GitHub release — @fastify/multipart v10.1.7
  3. npm — @fastify/multipart
  4. GitHub repository — fastify/fastify-multipart
  5. CISA KEV catalog
  6. FIRST EPSS data
  7. PoC Index — CVE-2026-117531
05 · The Call

Final Verdict
↓ DOWNGRADED to LOW (3.8/10)

Why this verdict

  • Baseline: vendor scored 5.9 because remote unauthenticated DoS against a Fastify route is plausible.
  • Prerequisite audit: exploitation requires a handler that uses the parts iterator and performs asynchronous work before consuming part.file; the common examples (await request.file()) consume immediately and do not hang.
  • No KEV or active exploitation: the CISA catalog does not list this CVE, and no mass scanning/exploitation has been reported.
  • Role multiplier: typical deployments are app-tier endpoints; even a Fastify app fronting an identity API only experiences availability loss, not domain/fleet/supply-chain compromise, so the high-value-role floor does not apply.
  • Friction compounding: malformed MIME body must exactly end inside a file part; many reverse proxies and Fastify defaults will still have connection limits/timeouts, and a single hanging request does not crash the process by itself.

Why not higher?

No privilege escalation, data confidentiality/integrity impact, or lateral movement. The attack requires accumulating many deliberately malformed sockets against a specific deferred-consumer route; an autoscaling group or watchdog can replace a saturated instance, and the attack leaves no permanent foothold. The AC:H in the CVSS vector reflects the conditional handler pattern, which knocks the real-world frequency down substantially.

Why not lower?

It is not IGNORE because an unauthenticated remote client can script the malformed requests and, if the app uses the exact deferred-consumer pattern, the route will leave sockets open until limits are hit. The availability-only impact on a public endpoint still merits a LOW remediation item rather than backlog-only review.

06 · Verification

Crowdsourced verification payload.

Run on the target host where the Fastify app is deployed, with read access to its package.json. Example: bash check_CVE-2026-117531.sh /srv/app/package.json. No root privileges required.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
set -u

PJ="${1:-package.json}"
if [ ! -f "$PJ" ]; then
  echo "UNKNOWN: no package.json at $PJ"
  exit 3
fi

if command -v node >/dev/null 2>&1; then
  VER=$(node -e "
    const fs = require('fs');
    const p = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
    const deps = {...p.dependencies, ...p.devDependencies};
    console.log(deps['@fastify/multipart'] || '');
  " "$PJ" 2>/dev/null || true)
else
  VER=$(grep -E '"@fastify/multipart"' -A2 "$PJ" | sed -n 's/.*"@fastify/multipart"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
fi

VER="${VER##^}"; VER="${VER##~}"
if [ -z "$VER" ]; then
  echo "UNKNOWN: @fastify/multipart not found in dependencies"
  exit 3
fi

node -e "
function cmp(a,b){const x=a.split('.').map(Number), y=b.split('.').map(Number); for(let i=0;i<3;i++){if((x[i]||0)<(y[i]||0))return -1;if((x[i]||0)>(y[i]||0))return 1;} return 0;}
const v=process.argv[1];
if (cmp(v,'5.3.0') >= 0 && cmp(v,'10.1.7') < 0) {
  console.log('VULNERABLE: declared @fastify/multipart version ' + v);
  process.exit(1);
} else {
  console.log('PATCHED: declared @fastify/multipart version ' + v);
  process.exit(0);
}
" "$VER"