A speed limiter that only fails when you set the speed limit to 'banana'
body-parser is the Express middleware that parses incoming request bodies (JSON, urlencoded, raw, text). In versions prior to 1.20.6 (1.x) and prior to 2.3.0 (2.x), if the application passes an *invalid* value to the limit option (something bytes cannot parse — e.g. 'infinity', '10 potatoes', negative numbers), the parser silently falls through to no effective cap. That lets a client stream an arbitrarily large body into memory, driving RSS growth until the Node worker crashes or the pod OOM-kills. The trigger is not a malicious request pattern in isolation — it requires the app to have shipped with a bad config.
Vendor severity of LOW / 3.7 is honest. CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L captures the reality: attack complexity is High because it depends on a misconfiguration the maintainer already committed, and the impact ceiling is a single-process availability hit. No confidentiality, no integrity, no code execution. Reassessment holds it at LOW — this is a code-hygiene bug, not an incident.
3 steps from start to impact.
Ship a misconfigured limit
limit string to bodyParser.json({ limit: 'infinity' }) or similar. The bytes library returns null, and body-parser fails to substitute the default '100kb' cap. This is the *pre-condition* — no attacker action yet.- App uses body-parser 1.x <1.20.6 or 2.x <2.3.0
- Developer passes a non-numeric / unparseable limit value
- Most apps use the default limit or a valid string like
'1mb' - Linters and code review catch obviously wrong values
- Framework scaffolds (Express Generator, NestJS) ship sane defaults
limit values; npm audit flags the CVE once the advisory is indexedReach the vulnerable endpoint
curl, hey, or a custom script sending Content-Length: 5000000000) and streams garbage.- Endpoint reachable from attacker's network position
- Endpoint uses the misconfigured parser instance
- Upstream reverse proxies (nginx, ALB, Cloudflare) enforce their own body-size limits well below Node's memory ceiling
- WAFs cap request size independently
- Rate limiters and connection limits blunt sustained streaming
Exhaust worker memory
- Node process has enough headroom for buffer to be observable
- No upstream size cap intervenes first
- Kubernetes restart policies recover within seconds
- PM2 / cluster mode absorbs single-worker crashes
- Horizontal autoscaling masks the impact
The supporting signals.
| In-the-wild exploitation | None observed. No KEV listing, no campaign reporting, no honeypot hits attributable to this CVE. |
|---|---|
| Proof-of-concept | Reproducer is trivial (three lines of Express + curl --data-binary @big.bin); no weaponized public PoC repo of note as of disclosure. |
| EPSS | Fresh CVE (disclosed 2026-07-09); EPSS not yet scored. Class-comparable DoS-via-misconfig bugs typically land <0.5% percentile. |
| KEV status | Not listed. Unlikely candidate — no code execution, requires pre-existing misconfig. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L — Network, High complexity, no auth, Low availability impact only. |
| Affected versions | body-parser < 1.20.6 (1.x line) and body-parser < 2.3.0 (2.x line). Also transitively affects Express <4.x bundling old body-parser. |
| Fixed versions | 1.20.6 and 2.3.0 — patch validates the limit option at parser-instantiation time and falls back to the default when unparseable. |
| Exposure population | body-parser has ~50M weekly npm downloads; realistic vulnerable-config population is a small fraction — most consumers pass no limit or a valid string. |
| Disclosure | 2026-07-09 via the body-parser GitHub Security Advisory. |
| Reporter | Credited in the GHSA / npm advisory (see sources). |
noisgate verdict.
The decisive factor is that exploitation is gated on the application having *already* shipped an invalid limit value — a self-inflicted misconfiguration, not an attacker-controlled precondition — and the ceiling is single-process availability with no confidentiality or integrity impact. Upstream proxies and container restart policies further cap real-world impact to transient service blips.
Why this verdict
- Precondition is self-inflicted: the vuln requires the developer to have already passed an invalid
limit— a bug the attacker cannot induce remotely. That's the AC:H in the vector, and it's earned. - Impact ceiling is A:L only: worst case is a Node worker OOM. No RCE, no info disclosure, no persistence. Modern orchestration (K8s, PM2, systemd) recycles the process in seconds.
- Upstream body-size caps neutralize most exposure: nginx
client_max_body_size, ALB/Cloudflare request limits, and WAF policies typically enforce a ceiling far below what would starve a Node worker. - Role multiplier — line-of-business web tier: even on a public-facing API server, the outcome is transient DoS of one process. Not fleet-scale, not identity-scale, not supply-chain-scale. Floor stays LOW.
- Role multiplier — high-value roles (auth service, payment gateway): same outcome, transient DoS. No pivot to identity or data. LOW floor holds.
- No exploitation signal: not in KEV, no campaigns, PoC is trivial but uninteresting to weaponize because it demands the target already be broken.
Why not higher?
MEDIUM would imply the vuln is reachable across a meaningful population of default-configured deployments. It isn't — a correctly-configured or default-configured body-parser is *not* vulnerable. The attacker cannot induce the trigger; they can only exploit an existing mistake, which is what AC:H captures.
Why not lower?
IGNORE would be wrong because the bug is real, the fix is trivial, and misconfigured deployments do exist in the wild (especially older Express apps with hand-rolled config). Patching remains worthwhile as hygiene and to close a class-C availability hazard against public endpoints.
What to do — in priority order.
- Audit all
bodyParser.*({ limit: ... })call sites — Grep the codebase forlimit:insidebody-parser,express.json,express.urlencoded,express.raw,express.textcalls. Confirm every value is a validbytes-parseable string (e.g.'100kb','1mb') or an integer. No mitigation SLA applies for LOW — treat as backlog hygiene, but close within the 365-day remediation window. - Enforce an upstream body-size cap — Set
client_max_body_size 1m;in nginx,client_body_buffer_sizebounds in HAProxy, or the equivalent on your ALB / Cloudflare / API gateway. This makes the Node process unreachable at oversized-body volumes regardless of application config. Deploy as standard baseline within backlog hygiene cadence. - Bump body-parser to 1.20.6 or 2.3.0 — Update
package.jsonand runnpm audit fixorpnpm up body-parser. If Express is a transitive consumer, resolve viaoverrides(npm) orresolutions(yarn/pnpm). Ship in the next routine dependency-update cycle. - Add a Semgrep/CodeQL rule for invalid
limitvalues — Codify a lint rule that rejects non-string / non-integer literals inlimit:and forbids values that don't match^\d+(\.\d+)?\s*(b|kb|mb|gb)$/i. Prevents regressions across the fleet.
- WAF signatures on request bodies — the payload isn't malicious content, it's just volume. WAFs that inspect body content will happily let a stream of zeros through unless a size cap is also configured.
- Rate limiting by request count — one large request can kill a worker; a per-IP RPS limiter that allows even 1 request/sec is enough to sustain the DoS.
- Container memory limits alone — they contain the blast radius but do not prevent the DoS; the pod still cycles.
Crowdsourced verification payload.
Run on any host or CI runner with Node.js and jq installed, from your repo root. Invoke as ./check-cve-2026-12590.sh /path/to/project (defaults to .). No elevated privileges required — this is a read-only manifest and lockfile inspection.
#!/usr/bin/env bash
# check-cve-2026-12590.sh
# Detect vulnerable body-parser versions and suspicious limit configurations.
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -u
PROJECT_DIR="${1:-.}"
cd "$PROJECT_DIR" 2>/dev/null || { echo "UNKNOWN: cannot cd to $PROJECT_DIR"; exit 2; }
STATUS=0
FOUND_ANY=0
vercmp_lt() {
# returns 0 if $1 < $2 (semver-ish)
[ "$1" = "$2" ] && return 1
printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1 | grep -qx "$1"
}
check_version() {
local v="$1"
FOUND_ANY=1
local major="${v%%.*}"
if [ "$major" = "1" ]; then
if vercmp_lt "$v" "1.20.6"; then
echo "VULNERABLE: body-parser $v (1.x < 1.20.6)"
STATUS=1
else
echo "PATCHED: body-parser $v"
fi
elif [ "$major" = "2" ]; then
if vercmp_lt "$v" "2.3.0"; then
echo "VULNERABLE: body-parser $v (2.x < 2.3.0)"
STATUS=1
else
echo "PATCHED: body-parser $v"
fi
else
echo "UNKNOWN: unexpected major version $v"
[ $STATUS -eq 0 ] && STATUS=2
fi
}
# 1. Inspect installed tree via npm ls (authoritative)
if command -v npm >/dev/null 2>&1 && [ -f package.json ]; then
while IFS= read -r ver; do
[ -n "$ver" ] && check_version "$ver"
done < <(npm ls body-parser --all --parseable --long 2>/dev/null \
| awk -F: '/body-parser@/ {sub(/.*body-parser@/,""); print $1}' \
| sort -u)
fi
# 2. Fallback: scan lockfiles
if [ $FOUND_ANY -eq 0 ]; then
for lf in package-lock.json npm-shrinkwrap.json; do
[ -f "$lf" ] || continue
if command -v jq >/dev/null 2>&1; then
while IFS= read -r ver; do
[ -n "$ver" ] && check_version "$ver"
done < <(jq -r '.. | objects | select(.version and (input_filename | test("body-parser") | not)) | select(.resolved? // "" | test("body-parser")) | .version' "$lf" 2>/dev/null | sort -u)
fi
done
fi
# 3. Config heuristic: flag suspicious limit values
echo "--- scanning source for suspicious limit configs ---"
SUSPECT=$(grep -RInE "limit\s*:\s*['\"]?(infinity|Infinity|-[0-9]+|0)" \
--include='*.js' --include='*.ts' --include='*.mjs' --include='*.cjs' \
. 2>/dev/null)
if [ -n "$SUSPECT" ]; then
echo "VULNERABLE-CONFIG: suspicious limit values detected:"
echo "$SUSPECT"
STATUS=1
fi
if [ $FOUND_ANY -eq 0 ] && [ -z "$SUSPECT" ]; then
echo "UNKNOWN: body-parser not found in project"
exit 2
fi
if [ $STATUS -eq 0 ]; then
echo "RESULT: PATCHED"
elif [ $STATUS -eq 1 ]; then
echo "RESULT: VULNERABLE"
else
echo "RESULT: UNKNOWN"
fi
exit $STATUS
If you remember one thing.
body-parser that only detonates on apps that already shipped a broken limit: value. Per the noisgate mitigation SLA for LOW, there is no mitigation SLA — go straight to the noisgate remediation SLA window of ≤365 days. Fold the version bump (1.20.6 / 2.3.0) into your next routine dependency-update cycle, run the verification script across your Node monorepos to flag the ~1-in-a-thousand codebase that passes 'infinity' or a negative number, and confirm your edge proxies enforce a sane client_max_body_size. If any of your public-facing Node services *do* trip the config heuristic, close those specific instances within 30 days as targeted hygiene — the class-wide fleet has more urgent work.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.