GHSA-3jxr-9vmj-r5cp

Suggest an improvement
Source
https://github.com/advisories/GHSA-3jxr-9vmj-r5cp
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-3jxr-9vmj-r5cp/GHSA-3jxr-9vmj-r5cp.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-3jxr-9vmj-r5cp
Aliases
Downstream
CGA (384)
ECHO (1)
MINI (40)
Published
2026-07-20T20:51:09Z
Modified
2026-09-10T03:51:10Z
Severity
  • 5.3 (Medium) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L CVSS Calculator
  • 7.7 (High) CVSS_V4 - CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:P/S:N/AU:Y/R:U/V:D/RE:M/U:Amber CVSS Calculator
Summary
brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups
Details

Summary

brace-expansion's expand() exhibits exponential-time - O(2ⁿ) - behavior in the number of consecutive non-expanding {} groups. A short, all-ASCII input (~90 bytes/30 groups) blocks the calling thread for minutes; a slightly longer input hangs it effectively indefinitely. Because the dominant consumers run on Node's single-threaded event loop, one small input can fully stall a worker/process.

In expand_, post is computed unconditionally at the top of the function, before the early-return branches that don't use it:

const post = m.post.length ? expand_(m.post, max, false) : [''];   // always recurses
  ...
if (!isSequence && !isOptions) {
  if (m.post.match(/,(?!,).*\}/)) {
    str = m.pre + '{' + m.body + escClose + m.post;
    return expand_(str, max, true); // restart — `post` discarded
  }
  return [str];
}

For input like a{},{},…, the first {} is non-expanding, so control reaches the {a},b} rewrite branch - but expand_ has already recursed into post over the entire remaining tail, only to throw the result away. Each level therefore spawns two recursive expansions over essentially the same remaining work: T(n) = 2·T(n−1) ⇒ O(2ⁿ).

The max option does not mitigate this: max only bounds the output-building loops; neither the post recursion nor the rewrite recursion consults it.

Measured on 5.0.6:

groups (n) input bytes time
20 60 130 ms
24 72 1.9 s
26 78 7.8 s
30 (PoC) 90 ~2 min

Proof of concept

const { expand } = require('brace-expansion');
// 30 non-expanding groups, ~90 bytes — blocks for minutes:
expand('a{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}');

Impact

Any application that passes attacker-influenced strings to brace-expansion.expand() - directly or transitively via minimatch/glob brace patterns - can be driven into a multi-minute-to-indefinite CPU hang by a tiny request, denying service on that thread/process.

Remediation

Upgrade to a patched release. The fix:

  1. Defers computing post until after the early-return branches (and computes it locally in the $-suffix branch), so post is only expanded when a brace set actually expands and the value is used. This alone removes the exponential.
  2. Converts the {a},b} rewrite from recursion to an in-function loop, so a long run of rewrites cannot grow the call stack.

Verified: the PoC drops from ~2 min to 0.55 ms, 5,000 groups complete in ~344 ms, and output is identical to 5.0.6 across a behavioral-equivalence suite (sequences, padding, $-prefix, a{},b}c, {},a}b, x{{a,b}}y, etc.). Post-fix complexity is ~O(n²) on this input class - acceptable for the security fix; a linear rewrite can be a non-urgent follow-up.

If immediate upgrade isn't possible, avoid passing untrusted input to expand() / glob brace patterns, or run such expansion under a timeout/worker.

Database specific
{
    "cwe_ids":  [
        "CWE-400",
        "CWE-407"
    ],
    "github_reviewed":  true,
    "github_reviewed_at":  "2026-07-20T20:51:09Z",
    "nvd_published_at":  "2026-06-30T10:16:34Z",
    "severity":  "HIGH"
}
References

Affected packages

npm / brace-expansion

Package

Name
brace-expansion
View open source insights on deps.dev
Purl
pkg:npm/brace-expansion

Affected ranges

Type
SEMVER
Events
Introduced
3.0.0
Fixed
5.0.7

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-3jxr-9vmj-r5cp/GHSA-3jxr-9vmj-r5cp.json"

npm / brace-expansion

Package

Name
brace-expansion
View open source insights on deps.dev
Purl
pkg:npm/brace-expansion

Affected ranges

Type
SEMVER
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
1.1.16

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-3jxr-9vmj-r5cp/GHSA-3jxr-9vmj-r5cp.json"

npm / brace-expansion

Package

Name
brace-expansion
View open source insights on deps.dev
Purl
pkg:npm/brace-expansion

Affected ranges

Type
SEMVER
Events
Introduced
2.0.0
Fixed
2.1.2

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-3jxr-9vmj-r5cp/GHSA-3jxr-9vmj-r5cp.json"