CVE-2026-14257: brace-expansion Denial of Service Vulnerability

HIGH
Advisory Identifier: GHSA-mh99-v99m-4gvg
CVSS 7.5
CWE-400 CWE-770
Published
Advisory Summary

CVE-2026-14257 is a HIGH (CVSS 7.5) denial of service vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting brace-expansion libraries in the npm ecosystem. The issue involves dos via unbounded expansion length causing an out-of-memory process crash. Remediation is available by upgrading affected packages to version 5.0.8, 3.0.3, 2.1.3, 1.1.17.

CVE-2026-14257 at a Glance

CVE Identifier CVE-2026-14257
GHSA Identifier GHSA-mh99-v99m-4gvg
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-400: Uncontrolled Resource Consumption
Affected Product brace-expansion
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-14257

To remediate this vulnerability, upgrade all affected brace-expansion dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

brace-expansion fixed: 5.0.8
npm install brace-expansion@5.0.8
brace-expansion fixed: 3.0.3
npm install brace-expansion@3.0.3
brace-expansion fixed: 2.1.3
npm install brace-expansion@2.1.3
brace-expansion fixed: 1.1.17
npm install brace-expansion@1.1.17
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls brace-expansion

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-14257, inspect your project dependencies using npm:

npm ls brace-expansion

Analyze the terminal output to locate any version of brace-expansion in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 5.0.8), your application is affected.

Note: The npm ls brace-expansion command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption, where the product does not properly control the allocation and maintenance of a limited resource. In brace-expansion, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary `expand()` bounds the *number* of results it produces (the `max` option, `100_000` by default) but not their *length*. By chaining many brace groups, an attacker keeps the result count under `max` while making every result grow with the number of groups. Building `max` long results — plus the intermediate arrays combined at each brace group — exhausts memory and crashes the Node process with an **uncatchable** out-of-memory error. `try/catch` around `expand()` does not help: the fatal error terminates the process. A ~7.5 KB input (`'{a,b}'.repeat(1500)`) is enough to crash a default Node process. ### Details For `N` chained brace groups such as `'{a,b}'.repeat(N)`: - the result count is `2^N`, immediately capped at `max` (`100_000`), so the `max` protection appears to hold, but - each result is `N` characters long, so the total output size is `max × N` characters, which grows without bound in `N`. `expand_` combines each brace set with the fully-expanded tail: ```js const post = m.post.length ? expand_(m.post, max, false) : [''] ... for (let j = 0; j < N.length; j++) { for (let k = 0; k < post.length && expansions.length < max; k++) { const expansion = pre + N[j] + post[k] // grows one group longer per level ... expansions.push(expansion) } } ``` The loop guard `expansions.length < max` limits how many strings are built, but nothing limits how long they get. Each recursion level materializes another array of up to `max` strings, one character longer than the level below, and — because V8 represents `pre + N[j] + post[k]` as a cons-string (rope) that references `post[k]` — those intermediate strings stay reachable through the whole chain. Memory therefore scales with `max × N`. Measured on `5.0.7` (`'{a,b}'.repeat(N)`, default `max`): | groups (N) | input bytes | result count | peak RSS | |---|---|---|---| | 20 | 100 | 100,000 | ~80 MB | | 50 | 250 | 100,000 | ~214 MB | | 100 | 500 | 100,000 | ~409 MB | | 300 | 1,500 | 100,000 | ~1,148 MB | | 1500 | 7,500 | — | **OOM crash** | ### Proof of concept ```js const { expand } = require('brace-expansion') // ~7.5 KB input — crashes the process with a fatal, uncatchable OOM: // FATAL ERROR: ... JavaScript heap out of memory try { expand('{a,b}'.repeat(1500)) } catch (e) { // never reached — the process is already dead } ``` ### Impact Any application that passes attacker-influenced strings to `brace-expansion.expand()` — directly, or transitively via `minimatch` / `glob` brace patterns — can be crashed by a small request. Because the failure is a fatal V8 out-of-memory error rather than a thrown exception, it cannot be caught and it takes down the whole worker/process, denying service. ### Remediation Upgrade to a patched release. The fix bounds the total number of characters a single `expand()` call may accumulate (`EXPANSION_MAX_LENGTH`, default `4_000_000`, configurable via a new `maxLength` option), applied inside the output-building loops so intermediate arrays are bounded too. Once the limit is reached, output is truncated — consistent with how `max` already truncates — instead of growing without bound. The limit sits well above any realistic expansion (100,000 results hitting `max` measure ~1M characters), so legitimate input is unaffected. After the fix, `'{a,b}'.repeat(1500)` returns a bounded, truncated result in ~0.7 s using ~340 MB and never crashes, including under a constrained 512 MB heap. The fix bounds memory but the algorithm still rebuilds intermediate arrays at each level (roughly `O(N × maxLength)` work on this input class). A streaming rewrite that produces output in `O(total output size)` can be a non-urgent follow-up. If immediate upgrade isn't possible, avoid passing untrusted input to `expand()` / glob brace patterns, or pass a small explicit `max` **and** `maxLength`.

CVE-2026-14257 Affected brace-expansion Versions

4 component(s)
Fixed Versions:
5.0.83.0.32.1.31.1.17
Product / Package Ecosystem Affected Versions Fixed Version
brace-expansion
npm
≥ 4.0.0
< 5.0.8
5.0.8
brace-expansion
npm
≥ 3.0.0
< 3.0.3
3.0.3
brace-expansion
npm
≥ 2.0.0
< 2.1.3
2.1.3
brace-expansion
npm
< 1.1.17
1.1.17

References

Frequently Asked Questions

What is CVE-2026-14257?
CVE-2026-14257 is a security vulnerability in brace-expansion involving DoS via unbounded expansion length causing an out-of-memory process crash under CWE-400: Uncontrolled Resource Consumption.
Which brace-expansion versions are affected?
Affected versions include releases prior to 5.0.8, 3.0.3, 2.1.3, 1.1.17 across affected packages: brace-expansion.
What version fixes CVE-2026-14257?
Upgrading to 5.0.8, 3.0.3, 2.1.3, 1.1.17 (or the latest patched release for your respective branch) resolves the vulnerability.
Is brace-expansion affected?
Yes, brace-expansion is affected in releases prior to 5.0.8, 3.0.3, 2.1.3, 1.1.17. Upgrade your dependency to 5.0.8, 3.0.3, 2.1.3, 1.1.17 or later.
What is the CVSS score of CVE-2026-14257?
CVE-2026-14257 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-14257?
CVE-2026-14257 is classified under CWE-400: Uncontrolled Resource Consumption (The product does not properly control the allocation and maintenance of a limited resource.).
How can I check npm dependencies for CVE-2026-14257?
Run the dependency tree command: 'npm ls brace-expansion'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-mh99-v99m-4gvg",
  "modified" : "2026-07-31T19:37:56Z",
  "published" : "2026-07-24T21:53:14Z",
  "aliases" : [ "CVE-2026-14257" ],
  "summary" : "brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash",
  "details" : "### Summary\n\n`expand()` bounds the *number* of results it produces (the `max` option,\n`100_000` by default) but not their *length*. By chaining many brace groups,\nan attacker keeps the result count under `max` while making every result grow\nwith the number of groups. Building `max` long results — plus the intermediate\narrays combined at each brace group — exhausts memory and crashes the Node\nprocess with an **uncatchable** out-of-memory error. `try/catch` around\n`expand()` does not help: the fatal error terminates the process.\n\nA ~7.5 KB input (`'{a,b}'.repeat(1500)`) is enough to crash a default Node\nprocess.\n\n### Details\n\nFor `N` chained brace groups such as `'{a,b}'.repeat(N)`:\n\n- the result count is `2^N`, immediately capped at `max` (`100_000`), so the\n  `max` protection appears to hold, but\n- each result is `N` characters long, so the total output size is\n  `max × N` characters, which grows without bound in `N`.\n\n`expand_` combines each brace set with the fully-expanded tail:\n\n```js\nconst post = m.post.length ? expand_(m.post, max, false) : ['']\n...\nfor (let j = 0; j < N.length; j++) {\n  for (let k = 0; k < post.length && expansions.length < max; k++) {\n    const expansion = pre + N[j] + post[k]   // grows one group longer per level\n    ...\n    expansions.push(expansion)\n  }\n}\n```\n\nThe loop guard `expansions.length < max` limits how many strings are built, but\nnothing limits how long they get. Each recursion level materializes another\narray of up to `max` strings, one character longer than the level below, and —\nbecause V8 represents `pre + N[j] + post[k]` as a cons-string (rope) that\nreferences `post[k]` — those intermediate strings stay reachable through the\nwhole chain. Memory therefore scales with `max × N`.\n\nMeasured on `5.0.7` (`'{a,b}'.repeat(N)`, default `max`):\n\n| groups (N) | input bytes | result count | peak RSS |\n|---|---|---|---|\n| 20 | 100 | 100,000 | ~80 MB |\n| 50 | 250 | 100,000 | ~214 MB |\n| 100 | 500 | 100,000 | ~409 MB |\n| 300 | 1,500 | 100,000 | ~1,148 MB |\n| 1500 | 7,500 | — | **OOM crash** |\n\n### Proof of concept\n\n```js\nconst { expand } = require('brace-expansion')\n\n// ~7.5 KB input — crashes the process with a fatal, uncatchable OOM:\n//   FATAL ERROR: ... JavaScript heap out of memory\ntry {\n  expand('{a,b}'.repeat(1500))\n} catch (e) {\n  // never reached — the process is already dead\n}\n```\n\n### Impact\n\nAny application that passes attacker-influenced strings to\n`brace-expansion.expand()` — directly, or transitively via `minimatch` / `glob`\nbrace patterns — can be crashed by a small request. Because the failure is a\nfatal V8 out-of-memory error rather than a thrown exception, it cannot be caught\nand it takes down the whole worker/process, denying service.\n\n### Remediation\n\nUpgrade to a patched release. The fix bounds the total number of characters a\nsingle `expand()` call may accumulate (`EXPANSION_MAX_LENGTH`, default\n`4_000_000`, configurable via a new `maxLength` option), applied inside the\noutput-building loops so intermediate arrays are bounded too. Once the limit is\nreached, output is truncated — consistent with how `max` already truncates —\ninstead of growing without bound. The limit sits well above any realistic\nexpansion (100,000 results hitting `max` measure ~1M characters), so legitimate\ninput is unaffected.\n\nAfter the fix, `'{a,b}'.repeat(1500)` returns a bounded, truncated result in\n~0.7 s using ~340 MB and never crashes, including under a constrained 512 MB\nheap.\n\nThe fix bounds memory but the algorithm still rebuilds intermediate arrays at\neach level (roughly `O(N × maxLength)` work on this input class). A streaming\nrewrite that produces output in `O(total output size)` can be a non-urgent\nfollow-up.\n\nIf immediate upgrade isn't possible, avoid passing untrusted input to\n`expand()` / glob brace patterns, or pass a small explicit `max` **and**\n`maxLength`.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "brace-expansion"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "4.0.0"
      }, {
        "fixed" : "5.0.8"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "npm",
      "name" : "brace-expansion"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "3.0.0"
      }, {
        "fixed" : "3.0.3"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "npm",
      "name" : "brace-expansion"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.0.0"
      }, {
        "fixed" : "2.1.3"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "npm",
      "name" : "brace-expansion"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.1.17"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/juliangruber/brace-expansion/security/advisories/GHSA-mh99-v99m-4gvg"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-14257"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/juliangruber/brace-expansion/pull/129"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/juliangruber/brace-expansion/pull/130"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/juliangruber/brace-expansion/pull/136"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/juliangruber/brace-expansion/commit/139d015104e71433ad52a41d19467c48ecbb2c7d"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/juliangruber/brace-expansion/commit/a1bd33999ea75262c4749fff3bbb0d1372bd07b5"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/juliangruber/brace-expansion/commit/cb4b9e47cc2ec777c14b2b4492fb431a56f6a031"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/juliangruber/brace-expansion/commit/d13ff455a58b0d56704f0111e3c2a0b16ceb06eb"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/juliangruber/brace-expansion"
  }, {
    "type" : "WEB",
    "url" : "https://www.npmjs.com/package/brace-expansion"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-400", "CWE-770" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-24T21:53:14Z",
    "nvd_published_at" : "2026-07-23T14:17:00Z"
  }
}