Like a leaky faucet that slowly floods the basement while you sleep, but there's a drain you forgot to open
CVE-2026-19474 affects @fastify/multipart versions 3.0.0 through 10.1.0. When an unauthenticated client begins a multipart upload, completes one file part, starts a second, then aborts the connection, the saveRequestFiles() function fails mid-stream and skips its cleanup routine. The orphaned temporary files remain on disk indefinitely. Repeating this pattern causes linear disk growth — an attacker can slowly exhaust the partition hosting /tmp or whatever tmpdir the application uses, eventually causing service disruption when the filesystem fills.
The vendor rates this HIGH at CVSS 7.5, which is technically defensible on paper — AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H maps correctly to an unauthenticated network DoS. However, in practice this overstates the risk for most enterprise deployments. The attack is availability-only, the growth rate is linear (not exponential or crash-inducing), the endpoint must specifically use the saveRequestFiles() API (the streaming API is unaffected), and standard infrastructure controls — container ephemeral storage limits, tmpwatch/systemd-tmpfiles-clean, disk usage alerts — all interrupt the chain before it reaches impact. This is a cleanup bug that operations teams routinely guard against at the infrastructure layer.
4 steps from start to impact.
Locate a Fastify upload endpoint
@fastify/multipart that calls saveRequestFiles(). This is typically a file upload form or API route. No authentication is required.- Target runs @fastify/multipart >= 3.0.0 and < 10.1.1
- The route handler uses
saveRequestFiles()rather than the streaming multipart API
- Not all Fastify apps use
saveRequestFiles()— many use the stream-basedfile()orparts()iterators, which are not affected - The endpoint may sit behind a WAF, CDN, or reverse proxy with upload rate limits
Send a crafted multipart request
saveRequestFiles() throws but the already-written temp file from part one is never cleaned up.- Network connectivity to the upload endpoint
- Ability to send raw HTTP and control TCP teardown timing
- Each request leaks only the bytes actually written to disk for the completed part — the attacker must send real data, consuming their own bandwidth
- Reverse proxies (nginx, HAProxy) may buffer the full request body before forwarding, preventing the mid-stream abort from reaching Fastify
Repeat to exhaust disk
/tmp partition (or configured tmpdir) fills, causing write failures across the service and potentially other co-located processes.- Sustained network access over hours or days depending on partition size
- No automated temp file reaping in place
- Default Linux
systemd-tmpfiles-cleanruns daily and removes files in/tmpolder than 10 days - Container runtimes with
ephemeralStorageLimitkill the pod and restart it when the limit is breached - Disk usage monitoring (Prometheus node_exporter, Datadog, CloudWatch) alerts ops teams well before full
- Rate limiting at the load balancer or CDN layer throttles the attack throughput
/tmp, container OOMKill / eviction events all surface this before service impact.Service disruption
- Disk partition must be completely full
- No disk monitoring or auto-remediation triggered earlier in the chain
- Load-balanced deployments limit blast radius to one instance
- Kubernetes liveness probes restart the affected pod, and ephemeral storage eviction prevents full-disk scenarios
- The outage is temporary and self-healing once temp files are reaped
saveRequestFiles() call in a try/catch and call await request.cleanRequestFiles() in the catch block. This is the vendor-recommended workaround and eliminates the leak entirely without upgrading. Deploy this code change within the noisgate remediation SLA of 365 days, or sooner if you lack the infrastructure controls below./tmp (or your configured tmpdir) is cleaned of files older than 1 hour. On systemd hosts, adjust /etc/tmpfiles.d/tmp.conf to set q /tmp 1777 root root 1h. This limits the maximum disk impact of the leak. Already present on most modern Linux distributions.resources.limits.ephemeralStorage on the pod spec (e.g., 2Gi). When the limit is breached, the kubelet evicts and restarts the pod, preventing disk exhaustion from affecting other workloads on the node.limit_req), HAProxy, or your CDN to rate-limit POST requests to upload endpoints. A limit of 10-20 req/s per source IP makes the disk fill attack impractically slow. This is a defense-in-depth measure.node_filesystem_avail_bytes, Datadog, CloudWatch) for the partition hosting temp files. This provides early warning regardless of the root cause.- WAF signature rules — There is no malicious payload to match; the request body is a normal multipart upload. The exploit is in the *abort timing*, not the content. WAF content inspection is irrelevant here.
- Request body size limits — The attacker only needs to send a small completed file part (even 1 byte) before aborting. Body size limits don't prevent the leak; they just cap the size of each orphaned file.
- Network-level DDoS protection (e.g., Cloudflare, AWS Shield) — These protect against volumetric floods, not slow-drip resource exhaustion via legitimate-looking HTTP requests that complete partially.
The supporting signals.
| In-the-Wild Exploitation | None observed. Not listed in CISA KEV. No known campaigns targeting this CVE as of 2026-08-15. |
|---|---|
| Proof of Concept | No public exploit code or PoC repository found. The advisory describes the trigger sequence (complete one part, start a second, abort) in enough detail that reproduction is trivial with curl — but no weaponized tooling exists. |
| EPSS Score | Not yet scored (CVE published 2026-08-14). Expected to land in the low single-digit percentile given availability-only impact and no known exploitation. |
| KEV Status | Not listed. No binding operational directive applies. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H — Network-accessible, no auth required, availability-only impact. No confidentiality or integrity impact. |
| Affected Versions | @fastify/multipart >= 3.0.0, < 10.1.1 — broad range spanning Fastify v3, v4, and v5 plugin generations. |
| Fixed Version | 10.1.1 (released 2026-08-14). No backports to older major streams announced. |
| Scanning / Exposure | Fastify is a server-side Node.js framework — instances are not directly fingerprinted by Shodan/Censys. Exposure depends on whether the application uses saveRequestFiles() specifically. npm weekly downloads for @fastify/multipart are ~500K, indicating moderate ecosystem adoption. |
| Disclosure Date | 2026-08-14 (coordinated disclosure via GitHub Security Advisory GHSA-62qx-hpj5-j6hc). |
| Reporter | Hemanth-9087 (reporter), mcollina (remediation), climba03003 (analyst), UlisesGascon (reviewer). |
Sources.
Why this verdict
- Availability-only impact: CVSS confirms C:N/I:N — no path to code execution, privilege escalation, or data breach. The worst outcome is temporary service degradation on a single instance.
- Linear growth rate with infrastructure guardrails: Disk exhaustion requires sustained attacker effort over hours/days. Standard ops controls —
systemd-tmpfiles-clean, containerephemeralStorageLimit, disk usage alerts — break the chain automatically in most modern deployments. - API-specific trigger: Only applications calling
saveRequestFiles()are affected. The streaming multipart API (file(),parts()iterators) is immune, narrowing the vulnerable population within the already-moderate@fastify/multipartinstall base. - Role multiplier: @fastify/multipart is an application-layer library typically deployed on line-of-business API servers. It is not canonically deployed on domain controllers, hypervisors, identity providers, backup infrastructure, or other high-value-role systems. The blast radius of a successful attack is limited to one application instance behind a load balancer — no fleet-scale, identity-scale, or supply-chain-scale impact is plausible.
Why not higher?
Upgrading to HIGH would require either active exploitation evidence, a path beyond availability impact, or deployment in a canonical high-value role. None of these apply. The DoS is slow, linear, and bounded by standard infrastructure controls that are present in the vast majority of production environments. The CVSS 7.5 score reflects a theoretical worst case that ignores real-world compensating infrastructure.
Why not lower?
Dropping to LOW would understate the risk for teams running bare-metal or VM deployments without automated temp file cleanup, where a determined attacker *could* fill a disk partition and disrupt service. The unauthenticated, network-accessible attack surface with no user interaction required keeps this above LOW. The broad affected version range (3.0.0 through 10.1.0) also means many deployments are vulnerable.
Crowdsourced verification payload.
Run this on each host or container running a Fastify application that uses @fastify/multipart. Requires read access to node_modules. Example: bash check_cve_2026_19474.sh /app where /app is the application root directory. No elevated privileges needed.
#!/usr/bin/env bash
# check_cve_2026_19474.sh — Detect @fastify/multipart < 10.1.1 (CVE-2026-19474)
# Usage: bash check_cve_2026_19474.sh <app_root_dir>
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
APP_DIR="${1:-.}"
PKG_JSON="$APP_DIR/node_modules/@fastify/multipart/package.json"
if [ ! -f "$PKG_JSON" ]; then
echo "UNKNOWN — @fastify/multipart not found in $APP_DIR/node_modules"
exit 2
fi
VERSION=$(grep '"version"' "$PKG_JSON" | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')
if [ -z "$VERSION" ]; then
echo "UNKNOWN — could not parse version from $PKG_JSON"
exit 2
fi
# Compare version to 10.1.1
MINOR=$(echo "$VERSION" | cut -d. -f1)
if [ "$MINOR" -lt 3 ]; then
echo "PATCHED — @fastify/multipart $VERSION is below the affected range (>= 3.0.0)"
exit 0
elif [ "$MINOR" -lt 10 ]; then
echo "VULNERABLE — @fastify/multipart $VERSION is affected (CVE-2026-19474, fix in 10.1.1)"
exit 1
elif [ "$MINOR" -eq 10 ]; then
PATCH=$(echo "$VERSION" | cut -d. -f2)
MICRO=$(echo "$VERSION" | cut -d. -f3)
if [ "$PATCH" -lt 1 ]; then
echo "VULNERABLE — @fastify/multipart $VERSION is affected (CVE-2026-19474, fix in 10.1.1)"
exit 1
elif [ "$PATCH" -eq 1 ] && [ "${MICRO:-0}" -lt 1 ]; then
echo "VULNERABLE — @fastify/multipart $VERSION is affected (CVE-2026-19474, fix in 10.1.1)"
exit 1
else
echo "PATCHED — @fastify/multipart $VERSION is >= 10.1.1"
exit 0
fi
else
echo "PATCHED — @fastify/multipart $VERSION is above the affected range"
exit 0
fi