Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
HIGH severity

GHSA-395f-4hp3-45gv

HIGHFix: ljharb/shell-quote@7ff5488

GHSA-395f-4hp3-45gv is a high-severity (CVSS 7.5) CWE-407 vulnerability in shell-quote. O3 Security confirms whether GHSA-395f-4hp3-45gv is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

shell-quote: Quadratic-complexity Denial of Service in `parse()` (CWE-407)

Also known asCVE-2026-13311
Published
Jul 20, 2026
Updated
Jul 28, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Jul 28, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, a proxy for how much of the ecosystem is exposed.

shell-quotenpm
73.4Mdownloads / week

Description

Summary

shell-quote's parse() finalizes its token list with a reduce that uses Array.prototype.concat as the accumulator. Each prev.concat(arg) copies the entire growing array, so parse() runs in O(n²) in the number of tokens. An unauthenticated attacker who can submit a string to any code path that calls parse() on it can block the single-threaded Node.js event loop for tens of seconds with a small input — a denial of service. The trigger needs no shell metacharacters (plain space-separated words suffice), so input filters that only screen for ;, |, $, or backticks do not help.

Root cause

parse.js (lines 200–203), in parseInternal — this path runs on every parse() call:

}).reduce(function (prev, arg) { // finalize parsed arguments
    // TODO: replace this whole reduce with a concat
    return typeof arg === 'undefined' ? prev : prev.concat(arg);
}, []);

prev.concat(arg) allocates a new array and copies all of prev on every iteration, so producing an N-token result costs 1 + 2 + … + N = O(N²) copies. A second acc.concat(s) reduce in the module.exports wrapper (lines 211–224, reached only when env is a function) has the same shape. The maintainer's own // TODO: replace this whole reduce with a concat already flags the construct.

Proof of Concept

const { parse } = require('shell-quote');
const ms = fn => { const t = process.hrtime.bigint(); fn(); return Number(process.hrtime.bigint()-t)/1e6; };
for (const N of [16000, 32000, 64000, 128000]) {
  console.log(N, 'tokens ->', ms(() => parse('x '.repeat(N))).toFixed(0), 'ms');
}

Measured on [email protected], Node v24:

input (N tokens)bytesparse()ratio vs prev (2× input)
16 00032 KB678 ms
32 00064 KB4 169 ms×6.2
64 000128 KB14 914 ms×3.6
128 000256 KB57 319 ms×3.8

Time grows ~×4 per 2× input → confirmed O(n²). A ~128 KB input blocks the event loop ~15 s; ~256 KB → ~57 s; a few hundred KB more → minutes. <img width="656" height="214" alt="image" src="https://github.com/user-attachments/assets/e8955b0e-0527-45ca-94b7-c3a2d8c0c82e" /> poc.js

Impact

parse() is synchronous on the main thread; while it copies arrays quadratically the entire event loop is blocked and the process serves no other requests. Any service that calls parse() on attacker-influenced input (command parsers, chat-ops / bot command handlers, REPLs, build-script / arg-string splitters) can be driven to a sustained DoS with a single small request. No code execution and no data disclosure — availability only.

End-to-end confirmation: a minimal HTTP server that calls parse() on the request body, hit with one POST of 'x '.repeat(32000) (~63 KB), froze for ~4.5 s. An out-of-process probe client issuing harmless GET /ping requests (normally ~1 ms) observed 27 consecutive pings stalled by up to 4374 ms during that single request — i.e. every concurrent client was denied service for the whole parse. Scaling the body to a few hundred KB extends the outage to minutes.

This is the same class as several accepted 2026 advisories for quadratic-parser DoS on untrusted input (e.g. markdown-it CVE-2026-48988, js-yaml CVE-2026-53550, python-multipart CVE-2026-53539). It is distinct from the known shell-quote command-injection issues (CVE-2021-42740, CVE-2016-10541, CVE-2026-9277), which are all in quote(), not parse().

Suggested remediation

Replace the O(n²) concat-in-reduce with a linear flatten that pushes into the accumulator instead of reallocating and copying it on every iteration. Apply the same shape to the wrapper's acc.concat(s) reduce. A defensive input-length cap on parse() is a cheap additional stop-gap.

Maintainer note (edit): the originally-suggested Array.prototype.flat() is ES2019 / Node 11+, but shell-quote declares engines: node >= 0.4, so .flat() would silently drop support for older runtimes. The fix instead flattens one-level array tokens with forEach/push — and deliberately not push.apply(...), since spreading a large array into function arguments can exceed the engine's argument count limit. Output is byte-identical to the current code across strings, undefined holes, one-level array tokens, and {op}/{comment}/{op:'glob'} objects, and finalizing is now linear (1,024,000 tokens in ~150 ms vs ~57 s for 128,000 before). Thanks for the clear report and PoC — the analysis and reproduction were spot on.

Disclosure

Found by source audit + wall-clock confirmation against 1.8.4 (and verified the same code is present on main). Reported privately here; no public disclosure until a fix is available.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npmshell-quoteall versions1.9.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for shell-quote. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update shell-quote to 1.9.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-395f-4hp3-45gv is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-395f-4hp3-45gv is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-395f-4hp3-45gv. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary `shell-quote`'s `parse()` finalizes its token list with a `reduce` that uses `Array.prototype.concat` as the accumulator. Each `prev.concat(arg)` copies the entire growing array, so `parse()` runs in **O(n²)** in the number of tokens. An unauthenticated attacker who can submit a string to any code path that calls `parse()` on it can block the single-threaded Node.js event loop for tens of seconds with a small input — a denial of service. The trigger needs **no shell metacharacters** (plain space-separated words suffice), so input filters that only screen for `;`, `|`, `$`, or backt
O3 Security · Impact-Aware SCA

Is GHSA-395f-4hp3-45gv in your dependencies?

O3 detects GHSA-395f-4hp3-45gv across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.

GHSA-395f-4hp3-45gv: shell-quote Denial of… | O3 Security