← Back to Feed CACHED · 2026-10-22 16:52:05 · CACHE_KEY CVE-2026-88932
CVE-2026-88932 · CWE-400 · Disclosed 2026-09-14

multer diskStorage incomplete abort cleanup leading to orphaned file accumulation DoS

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

Like a restaurant that never buses its tables, multer keeps serving until there's no room left

CVE-2026-88932 is a denial-of-service flaw in multer, the dominant Express.js middleware for multipart file uploads (~8–20 million weekly npm downloads). In versions 2.2.0 through 2.3.x, when a client aborts an upload *before* the diskStorage engine assigns a destination path, the abort-cleanup routine skips the file because it has no path to delete — but the underlying fs.WriteStream finishes anyway, landing a complete orphan on disk. The bug is a bypass of the earlier CVE-2026-5038 fix (which only cleaned files that already had a path). An unauthenticated remote attacker can repeatedly start and abort multipart POSTs to accumulate orphans until the upload directory or /tmp fills, starving the host of disk. Only diskStorage is affected; memoryStorage and cloud-backed engines are immune. Fixed in multer 2.4.0 via commit 53337f9, which tracks an abortCleanupDone flag and calls storage._removeFile on any late-completing write.

The vendor rates this MEDIUM / 5.3 and that is accurate. The CVSS vector (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L) correctly reflects unauthenticated network access with low availability impact. Upgrading A:L → A:H would require arguing the attack reliably crashes the entire host, but in practice disk-fill is gradual, monitorable, and recoverable — any alerting on disk utilisation catches it long before total outage. The narrow vulnerable version window (two minor releases), the DoS-only impact ceiling, zero in-the-wild exploitation, and available compensating controls (disk quotas, rate-limiting, monitoring) all confirm the vendor's MEDIUM is neither inflated nor understated.

"Disk-fill DoS via aborted uploads — real but bounded, vendor MEDIUM is fair"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify multer diskStorage endpoint

The attacker locates an HTTP route that accepts multipart/form-data uploads backed by multer's diskStorage engine. This is common on user-facing upload forms, avatar endpoints, and document-ingestion APIs. The route must be reachable without authentication (or with trivially obtainable credentials). No specialised tooling is required — a simple curl with --form suffices.
Conditions required:
  • Target application uses multer 2.2.0–2.3.x with diskStorage
  • Upload endpoint is network-reachable
Where this breaks in practice:
  • Many production deployments front uploads behind authentication or CAPTCHA
  • Some deployments use memoryStorage, S3 SDKs, or cloud-backed engines — all immune
  • The vulnerable version window is only two minor releases
Detection/coverage: npm audit, Snyk, Dependabot, and Trivy all flag CVE-2026-88932 in lockfile scans. No network-level signature needed at this step.
STEP 02

Send multipart upload and abort before path assignment

The attacker opens a multipart POST, begins streaming a file body, then drops the TCP connection (RST or FIN) *before* the diskStorage destination/filename callback returns. If those callbacks are asynchronous (e.g., they hash or query a DB), the race window widens significantly. The abort fires multer's cleanup, which iterates only files that already have a path — the in-flight file has none yet, so it is skipped.
Conditions required:
  • Attacker can open and close TCP connections rapidly
  • diskStorage destination/filename callback takes non-trivial time (async I/O widens the window)
Where this breaks in practice:
  • Synchronous callbacks narrow the window to microseconds — the race becomes unreliable
  • WAFs and reverse proxies may enforce minimum request durations or rate-limit aborted connections
  • CDNs like Cloudflare buffer uploads, so the abort may never reach the origin in the critical window
Detection/coverage: Elevated 499/client-abort rates in NGINX access logs. Unusual spike in fs.open / inode creation visible in host-level audit or eBPF tracing.
STEP 03

Late-completing write lands orphaned file

The fs.WriteStream pipe completes after abort cleanup has already finished. Because abortCleanupDone is true but no code removes the newly written file, it persists on disk with no reference in multer's state. The attacker repeats steps 2–3 at scale to accumulate orphans.
Conditions required:
  • The fs.WriteStream must complete after cleanup (race won)
  • Disk must have remaining capacity to accept writes
Where this breaks in practice:
  • OS-level disk quotas on the upload user/directory cap the damage
  • Container environments often have ephemeral storage limits (e.g., Kubernetes emptyDir.sizeLimit)
  • tmpwatch / systemd-tmpfiles can periodically purge stale files from /tmp
Detection/coverage: Disk utilisation alerts (>80% threshold) in any monitoring stack (Prometheus node_exporter, Datadog, CloudWatch). Orphaned files appear as recently-created files with no corresponding application record.
STEP 04

Disk exhaustion → denial of service

Once the upload directory or /tmp is full, the application (and potentially co-located services) can no longer write to disk. Log rotation fails, database WAL writes fail, and the Node.js process may crash on ENOSPC errors. The impact is availability loss for the application, not data exfiltration or code execution.
Conditions required:
  • Attacker sustains the abort loop long enough to fill available disk
  • No disk monitoring or quota intervenes
Where this breaks in practice:
  • Any disk monitoring with auto-remediation (alert + cleanup script) breaks the chain
  • Separate volume mounts for uploads isolate blast radius to the upload feature only
  • Kubernetes liveness probes restart the pod; new ephemeral storage resets the attack
Detection/coverage: ENOSPC errors in application logs. Disk-full alerts in infrastructure monitoring. df -h on the target host shows 100% utilisation on the upload mount.
03 · Compensating Control

1
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Upgrade multer to ≥ 2.4.0 — The definitive fix. Commit 53337f9 adds abortCleanupDone tracking and calls storage._removeFile on any late-completing write, eliminating orphan accumulation entirely. Since this is an npm dependency, the upgrade is npm install [email protected] and a lockfile commit. No mitigation SLA applies at MEDIUM — go straight to the 365-day noisgate remediation SLA window, though the upgrade is trivial and should be prioritised in your next dependency-update cycle.
2
MEDIUM 5.3→LOW 3.0
SEVERITY REDUCED
Mount upload directory on a dedicated volume with a size quota — Isolate the upload directory (or /tmp if that's where multer writes) on its own filesystem or volume with a hard size limit (e.g., fallocate + mkfs + mount, or Kubernetes emptyDir.sizeLimit). When the volume fills, only uploads fail — the application, database, and logs continue on the root filesystem. This contains the blast radius to a single feature. Deploy within the 365-day remediation window alongside the patch.
3
MEDIUM 5.3→LOW 2.5
SEVERITY REDUCED
Rate-limit and size-cap upload endpoints — Configure your reverse proxy (NGINX client_max_body_size, limit_req_zone; Cloudflare rate-limiting rules; AWS ALB request-rate rules) to cap upload size and throttle requests per source IP on upload routes. This slows the orphan accumulation rate to the point where disk monitoring has ample time to alert. A 1 MB cap and 10 req/s/IP limit makes filling a 50 GB volume take weeks instead of hours.
4
MEDIUM 5.3→LOW 2.0
SEVERITY REDUCED
Alert on disk utilisation > 80% with auto-cleanup — Ensure your monitoring stack (Prometheus node_filesystem_avail_bytes, Datadog disk check, CloudWatch DiskSpaceUtilization) alerts at 80% and triggers an automated cleanup of orphaned files in the upload directory (files older than N minutes with no matching application record). This converts the DoS from 'silent until outage' to 'detected and remediated automatically'.
5
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Switch to memoryStorage or cloud-backed storage engine — If your upload flow permits, switch from diskStorage to memoryStorage (for small files) or a cloud SDK (S3, GCS, Azure Blob). memoryStorage is explicitly unaffected by this CVE since no filesystem writes occur. This eliminates the vulnerability class entirely for your application, though it may require code changes to downstream consumers of the uploaded file.
What doesn't work
  • Helmet.js or CORS headers — these are HTTP response headers and have no effect on how multer handles incoming multipart request bodies or aborted connections.
  • Node.js --max-old-space-size — this limits heap memory, not disk writes. The orphaned files are written by fs.WriteStream to the filesystem, not held in V8 memory.
  • multer({ limits: { fileSize: N } }) — the fileSize limit only triggers after the file is being written. The race condition occurs in the abort-cleanup path *regardless* of size limits, so even a 1-byte orphan counts toward disk exhaustion at scale.
  • Upgrading to multer 2.2.0 or 2.3.0 — these are the *vulnerable* versions. The CVE-2026-5038 fix in 2.2.0 is what introduced the incomplete cleanup that this CVE bypasses. Only 2.4.0+ is safe.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Not listed in CISA KEV. No GreyNoise tags or Shadowserver campaigns reference this CVE. No reports of active exploitation from any ISAC or threat-intel feed as of 2026-10-23.
Proof-of-concept availabilityNo public PoC. pocindex.io returns no results. No GitHub repositories named after CVE-2026-88932. No ExploitDB, Nuclei template, or Metasploit module. The fix commit includes a regression test (test/orphan-before-path-assignment.js) that demonstrates the race, which could be adapted as a PoC. Exploitation is conceptually trivial (abort a curl --form mid-stream) but reliably winning the race requires async callbacks.
EPSS score0.00532 (0.532%) — 44th percentile. Indicates a below-average probability of exploitation in the next 30 days. Consistent with a DoS-only, no-PoC, library-level vulnerability.
KEV statusNot listed. No CISA Known Exploited Vulnerabilities entry. No binding operational directive applies.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L — 5.3 MEDIUM. Network-accessible, no auth required, but impact is availability-only and rated Low. The A:L reflects that disk fill is gradual and recoverable rather than an instant crash.
Affected versionsmulter ≥ 2.2.0, < 2.4.0 (npm). Specifically versions 2.2.0, 2.2.1, 2.3.0. Only the diskStorage engine is affected; memoryStorage is immune. This is a bypass of the CVE-2026-5038 fix that shipped in 2.2.0.
Fixed versionsmulter 2.4.0 (npm, published ~September 2026). Commit expressjs/multer@53337f9. No distro backports apply — multer is an npm package consumed directly.
Installed base / exposuremulter has ~8–20 million weekly npm downloads and 6,738 dependent packages. However, the vulnerable window (2.2.0–2.3.x) is narrow — many installations still run 1.x (pre-affected) or have already upgraded to 2.4.0. Shodan/Censys cannot fingerprint multer versions remotely; exposure estimates rely on npm download data.
Disclosure timelineDisclosed 2026-09-14. GHSA GHSA-3pph-fpjx-jg34 published same day. NVD entry modified 2026-10-21. Predecessor CVE-2026-5038 was fixed in multer 2.2.0 (June 2026); this CVE is its incomplete-fix bypass.
Reporter / creditReported by euriconicacio. Fix developed by UlisesGascon and bjohansebas (Express.js / OpenJS Foundation maintainers).

Sources.

  1. GitHub Advisory — GHSA-3pph-fpjx-jg34
  2. OSV — CVE-2026-88932
  3. OpenCVE — CVE-2026-88932
  4. hol.org — Aborted multer uploads still fill the disk after the 5038 fix
  5. Strix AI — CVE-2026-88932 analysis
  6. multer npm package
  7. GitLab Advisory — CVE-2026-88932
  8. Predecessor — CVE-2026-5038 on NVD
05 · The Call

Final Verdict
= UNCHANGED to MEDIUM (5.3/10)

Why this verdict

  • DoS-only impact ceiling: The vulnerability provides no path to code execution, data exfiltration, or privilege escalation. The worst outcome is disk exhaustion on a single host, which is recoverable by clearing orphaned files and restarting the process. This hard-caps severity at MEDIUM regardless of other factors.
  • Narrow version window: Only multer 2.2.0–2.3.x is affected — two minor releases spanning roughly three months. The 1.x branch (still widely deployed) and 2.4.0+ are immune. This significantly limits the reachable population.
  • Race condition reliability: Exploiting the timing window requires the abort to land *after* the file write starts but *before* the storage engine assigns a path. Synchronous destination/filename callbacks shrink this window to microseconds. Only async callbacks (DB lookups, hashing) make it reliably exploitable, and this is not the default configuration.
  • Compensating controls are effective and common: Disk quotas, separate upload volumes, container ephemeral storage limits, WAF rate-limiting on aborted connections, and basic disk monitoring all independently break the chain. Most production deployments have at least one of these.
  • Role multiplier: multer is application-layer middleware running in Node.js user-space. It is not a high-value-role component (not an identity provider, hypervisor, kernel agent, network edge, or CI/CD system). In its typical deployment role (Express.js web app handling file uploads), the blast radius of disk exhaustion is *single-host, single-application* — not domain, fleet, or supply-chain scale. Even on a high-value application (e.g., a document-ingestion API for a regulated system), the outcome remains temporary DoS, not compromise. No high-value-role floor applies.
  • No exploitation pressure: Zero in-the-wild exploitation, no public PoC, not KEV-listed, EPSS at 44th percentile. There is no urgency signal overriding the baseline assessment.

Why not higher?

Upgrading to HIGH would require either a path to code execution / data compromise, or a blast radius that reaches fleet or domain scale. This vulnerability offers neither — it is strictly availability-only, single-host DoS via gradual disk fill. The race condition is unreliable in default configurations, and standard infrastructure controls (disk quotas, monitoring, container limits) break the chain independently. There is no exploitation in the wild and no PoC to accelerate weaponisation.

Why not lower?

Dropping to LOW would understate the risk for the subset of deployments that expose unauthenticated upload endpoints with diskStorage, async callbacks, no disk quotas, and no monitoring — in that configuration, a sustained attack *will* fill the disk and cause an outage. The unauthenticated, network-accessible attack vector with no user interaction means any reachable instance is targetable. The 5.3 MEDIUM correctly balances the easy entry point against the limited impact.

06 · Verification

Crowdsourced verification payload.

Run this on each application host (or in your CI pipeline against package-lock.json / yarn.lock). No special privileges needed — it reads node_modules or the lockfile. Example: bash check_cve_2026_88932.sh /opt/myapp

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_88932.sh — Detect multer versions vulnerable to CVE-2026-88932
# Usage: bash check_cve_2026_88932.sh <project_root>
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN

set -euo pipefail

PROJECT_ROOT="${1:-.}"
VULN_MIN="2.2.0"
VULN_MAX="2.3.999" # anything < 2.4.0
FIXED="2.4.0"

version_gte() {
  # returns 0 if $1 >= $2 using sort -V
  [ "$(printf '%s\n%s' "$1" "$2" | sort -V | head -n1)" = "$2" ]
}

# Try to find installed multer version
MULTER_VERSION=""

# Method 1: node_modules package.json
if [ -f "${PROJECT_ROOT}/node_modules/multer/package.json" ]; then
  MULTER_VERSION=$(grep '"version"' "${PROJECT_ROOT}/node_modules/multer/package.json" \
    | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
fi

# Method 2: npm ls (fallback)
if [ -z "$MULTER_VERSION" ] && command -v npm &>/dev/null; then
  MULTER_VERSION=$(cd "$PROJECT_ROOT" && npm ls multer --json 2>/dev/null \
    | grep '"version"' | head -1 \
    | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' || true)
fi

if [ -z "$MULTER_VERSION" ]; then
  echo "UNKNOWN — multer not found in ${PROJECT_ROOT}"
  echo "If multer is not a dependency, this CVE does not apply."
  exit 2
fi

echo "Detected multer version: ${MULTER_VERSION}"

if version_gte "$MULTER_VERSION" "$FIXED"; then
  echo "PATCHED — multer ${MULTER_VERSION} >= ${FIXED}, CVE-2026-88932 is fixed."
  exit 0
elif version_gte "$MULTER_VERSION" "$VULN_MIN"; then
  echo "VULNERABLE — multer ${MULTER_VERSION} is in the affected range (${VULN_MIN}–<${FIXED})."
  echo "Upgrade to multer >= ${FIXED}: npm install multer@${FIXED}"
  exit 1
else
  echo "PATCHED — multer ${MULTER_VERSION} < ${VULN_MIN}, not affected by CVE-2026-88932."
  echo "(Note: versions < 2.2.0 may be affected by the predecessor CVE-2026-5038.)"
  exit 0
fi