Back to Blog
high SEVERITY8 min read

How ReDoS via Unbounded Brace Expansion happens in Node.js and how to fix it

CVE-2024-4068 is a high-severity Regular Expression Denial of Service (ReDoS) vulnerability in the `braces` npm package (versions prior to 3.0.3) that allows an attacker to trigger catastrophic CPU consumption by supplying a crafted brace-expansion string with no character limit. The fix upgrades `braces` from 3.0.2 to 3.0.3 and its internal dependency `fill-range` from 7.0.1 to 7.1.1, both of which enforce limits on the size of input they will process. This patch was applied via a Yarn resoluti

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2024-4068 is a high-severity ReDoS (Regular Expression Denial of Service) vulnerability in the `braces` npm package, classified under CWE-1333 (Inefficient Regular Expression Complexity). In versions ≤ 3.0.2, the `braces` library performs brace expansion (e.g., `{1..1000000}`) without any limit on the number of characters or steps it will process, allowing a single malicious string to consume all available CPU. The fix is to upgrade `braces` to 3.0.3 and `fill-range` to 7.1.1, which introduce input-size guards, and to pin the resolution in `package.json` so all transitive dependents receive the patched version.

Vulnerability at a Glance

cweCWE-1333 (Inefficient Regular Expression Complexity) / CWE-400 (Uncontrolled Resource Consumption)
fixUpgrade `braces` to 3.0.3 and `fill-range` to 7.1.1, which add input-length and iteration guards, and pin the resolution in `package.json`.
riskAn attacker can supply a crafted brace-expansion string that causes the server process to spin at 100% CPU, denying service to all other users.
languageJavaScript / Node.js
root cause`braces` 3.0.2 and `fill-range` 7.0.1 perform expansion without enforcing any upper bound on the number of characters or iterations processed.
vulnerabilityReDoS / Unbounded Brace Expansion (Algorithmic Complexity Attack)

The Problem Hidden in Your Lock File

Most developers never think twice about yarn.lock. It's auto-generated, committed once, and largely ignored. But inside that file, a single pinned version of a utility package called braces was quietly carrying a high-severity denial-of-service vulnerability—CVE-2024-4068.

braces is one of those invisible workhorses of the Node.js ecosystem. It handles brace expansion: turning shorthand patterns like {a,b,c} or {1..100} into their full list of strings. It underpins micromatch, glob, fast-glob, and dozens of other tools your build pipeline almost certainly depends on. In version 3.0.2, it had a critical flaw: it placed no upper bound on the number of characters or expansion steps it would process.

This post walks through exactly what that means, how an attacker could exploit it, and the precise changes made to close the hole.


The Vulnerability Explained

What braces Does (and Where It Goes Wrong)

Brace expansion takes a pattern and produces an array of strings:

const braces = require('braces');

// Normal use
braces('{a,b,c}');       // ['a', 'b', 'c']
braces('{1..5}');        // ['1', '2', '3', '4', '5']

// The dangerous case in braces 3.0.2
braces('{1..10000000}'); // Attempts to generate 10,000,000 strings
                         // No limit enforced — event loop blocked

The vulnerability is not in a regex per se, but in the algorithmic complexity of the expansion itself. When braces processes a range like {1..10000000}, it delegates to fill-range to generate every integer in that range. In fill-range 7.0.1, this is done without any guard on the total number of values produced. The result: a single function call can consume gigabytes of memory and 100% of one CPU core for an extended period.

Because Node.js runs JavaScript on a single-threaded event loop, blocking that loop—even briefly—denies service to every other concurrent request. A sufficiently large range can block it for seconds, minutes, or indefinitely.

The Vulnerable Dependency Chain

The yarn.lock snapshot before the fix shows the problem clearly:

# BEFORE (vulnerable)
braces@^3.0.2, braces@~3.0.2:
  version "3.0.2"
  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
  dependencies:
    fill-range "^7.0.1"

fill-range@^7.0.1:
  version "7.0.1"
  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
  dependencies:
    to-regex-range "^5.0.1"

Two packages are involved:

  1. braces 3.0.2 — the entry point that parses brace-expansion patterns
  2. fill-range 7.0.1 — the library braces calls to generate numeric ranges, with no iteration limit

A Concrete Attack Scenario

Imagine an application that accepts a glob pattern from a user to filter files, or a build tool that processes user-supplied template strings. Any code path that passes untrusted input into braces() (directly or through micromatch, glob, or similar) is exploitable:

// Hypothetical vulnerable endpoint
app.post('/search', (req, res) => {
  const pattern = req.body.pattern;  // User-controlled input
  const matches = micromatch(fileList, pattern); // Internally calls braces()
  res.json(matches);
});

// Attacker sends:
// POST /search
// { "pattern": "{1..99999999}" }
// → Server event loop blocked, all other requests time out

The attacker doesn't need authentication. A single HTTP request with a malicious pattern is enough to take down the service.

Real-world impact for this application: Even though the scanner assessed the vulnerability as "present in dependency tree, not confirmed reachable," the risk is real any time user-influenced strings flow through the dependency chain that includes braces. The attack surface is broad because braces is a transitive dependency of many common tools.


The Fix

What Changed and Why

The fix required updates to two packages and two files:

1. yarn.lock — Upgrading Both braces and fill-range

# BEFORE
-braces@^3.0.2, braces@~3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
-  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
-  dependencies:
-    fill-range "^7.0.1"

# AFTER
+braces@3.0.3, braces@^3.0.2, braces@~3.0.2:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
+  integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+  dependencies:
+    fill-range "^7.1.1"
# BEFORE
-fill-range@^7.0.1:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#..."
-  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==

# AFTER
+fill-range@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#..."
+  integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==

braces 3.0.3 now requires fill-range ^7.1.1 (up from ^7.0.1). The new fill-range 7.1.1 introduces the actual defensive logic: it checks the size of the range before attempting to generate values and throws an error (or returns safely) if the expansion would exceed a safe threshold. This breaks the attack at the point where unbounded work would begin.

2. package.json — The Resolution Pin

Simply updating yarn.lock is not enough. Yarn resolves versions based on package.json constraints from all packages in the tree. Without a resolution pin, a future yarn install could silently re-resolve braces back to 3.0.2 if any transitive dependency still specifies braces@~3.0.2.

The fix adds a resolutions field to package.json:

# package.json
   "volta": {
     "node": "24.11.0",
     "yarn": "1.22.22"
+  },
+  "resolutions": {
+    "braces": "3.0.3"
   }

The resolutions field is a Yarn 1.x feature that forces all packages in the dependency tree—regardless of what version they request—to receive exactly braces@3.0.3. This is the correct defense-in-depth approach: even if a transitive dependency is never updated to request ^3.0.3, the resolution override ensures the patched version is always used.

Before vs. After: The Security Boundary

Before (3.0.2) After (3.0.3)
Input {1..100} Expands normally Expands normally
Input {1..10000000} Blocks event loop Throws / returns safely
fill-range version 7.0.1 (no limit) 7.1.1 (enforces limit)
Pinned in package.json No Yes (via resolutions)

The fix is backward compatible: valid, reasonably-sized brace expressions continue to work exactly as before. Only pathologically large inputs are now rejected.


Prevention & Best Practices

1. Run SCA (Software Composition Analysis) on Every Commit

This vulnerability was caught by Trivy scanning yarn.lock. SCA tools compare your locked dependency versions against CVE databases and flag known-vulnerable packages. Integrate Trivy, Snyk, or npm audit / yarn audit into your CI pipeline so new vulnerabilities are caught before they reach production.

# Quick check with yarn
yarn audit --level high

# Or with Trivy
trivy fs --scanners vuln yarn.lock

2. Use resolutions (Yarn) or overrides (npm) for Transitive Dependencies

When a vulnerability is in a transitive dependency you don't control directly, use your package manager's override mechanism:

// Yarn 1.x
"resolutions": {
  "braces": "3.0.3"
}

// npm 8.3+ / package.json
"overrides": {
  "braces": "3.0.3"
}

This guarantees the patched version is used everywhere in the tree, not just where you have a direct dependency.

3. Validate and Cap User-Supplied Glob/Pattern Strings

Never pass raw user input into pattern-expansion libraries without validation:

// Dangerous
const results = micromatch(files, req.body.pattern);

// Safer
const MAX_PATTERN_LENGTH = 200;
const pattern = String(req.body.pattern || '');
if (pattern.length > MAX_PATTERN_LENGTH) {
  return res.status(400).json({ error: 'Pattern too long' });
}
const results = micromatch(files, pattern);

Defense in depth: validate at the application layer and rely on the library's own guards.

4. Understand Your Transitive Dependency Tree

Run yarn why braces or npm explain braces to see every package that depends on braces. This tells you your actual attack surface and which packages would need updating if a new vulnerability were found.

$ yarn why braces
# => fast-glob > micromatch > braces
# => chokidar > anymatch > micromatch > braces

5. Relevant Security Standards

  • CWE-1333: Inefficient Regular Expression Complexity — the canonical classification for ReDoS and algorithmic complexity attacks
  • CWE-400: Uncontrolled Resource Consumption — applies when there is no cap on the work performed per input
  • OWASP: Denial of Service Cheat Sheet — covers resource exhaustion attack patterns

Key Takeaways

  • braces 3.0.2 and fill-range 7.0.1 must both be upgraded — the vulnerability spans two packages in a chain, and patching only one is insufficient.
  • A resolutions pin in package.json is required for Yarn 1.x projects to prevent future yarn install runs from silently re-resolving back to the vulnerable version.
  • Brace-expansion patterns are a non-obvious attack surface: unlike SQL injection or XSS, this attack vector is easy to miss in code review because braces is almost always a transitive, invisible dependency.
  • The event-loop threading model of Node.js amplifies this risk: a single blocked call denies service to all concurrent users, making ReDoS particularly dangerous in server-side JavaScript.
  • Trivy's SCA scanning of yarn.lock caught this without any source-code analysis — demonstrating that dependency scanning alone, applied to lock files, is a high-value, low-cost security control.

How Orbis AppSec Detected This

  • Source: The braces package receives expansion patterns that may be influenced by user input flowing through libraries such as micromatch or glob in the application's dependency tree.
  • Sink: The fill-range function called internally by braces@3.0.2 (resolved in yarn.lock at line ~587) performs unbounded numeric range expansion with no iteration or character limit.
  • Missing control: Neither braces 3.0.2 nor fill-range 7.0.1 enforced any maximum on the number of values to generate or the total characters to process, leaving the expansion loop open to resource exhaustion.
  • CWE: CWE-1333 (Inefficient Regular Expression Complexity) / CWE-400 (Uncontrolled Resource Consumption)
  • Fix: Upgraded braces to 3.0.3 and fill-range to 7.1.1 (which add internal input-size guards), and pinned the resolution in package.json to ensure all transitive dependents receive the patched version.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CVE-2024-4068 is a reminder that denial-of-service vulnerabilities don't require exotic techniques—sometimes a single string with a large numeric range is enough to take down a Node.js service. The braces package is embedded so deeply in the JavaScript tooling ecosystem that most projects are exposed without knowing it.

The fix is surgical and safe: upgrading to braces 3.0.3 and fill-range 7.1.1 adds the missing input-size guards while leaving all valid patterns unaffected. Pinning the version via resolutions in package.json ensures the fix holds across future installs. And integrating SCA scanning into your CI pipeline means you'll catch the next vulnerable transitive dependency before it ever ships.

Lock files are not just bookkeeping—they are a security artifact. Treat them accordingly.


References

Frequently Asked Questions

What is a ReDoS vulnerability?

ReDoS (Regular Expression Denial of Service) is an attack where a crafted input causes a regex or pattern-expansion engine to take exponential time, blocking the event loop and denying service.

How do you prevent ReDoS in Node.js?

Use libraries that enforce input-size limits, set timeouts on pattern-matching operations, validate and cap user-supplied glob or brace-expansion strings before processing, and keep dependencies up to date.

What CWE is this ReDoS vulnerability?

CWE-1333 (Inefficient Regular Expression Complexity) and CWE-400 (Uncontrolled Resource Consumption) both apply to this class of vulnerability.

Is input validation alone enough to prevent this ReDoS?

Input validation helps, but the root fix must be in the library itself—`braces` 3.0.3 adds internal guards so even if upstream validation is missed, the library will not expand unbounded inputs.

Can static analysis detect this vulnerability?

Yes. Trivy flagged this exact issue by matching the installed version of `braces` in `yarn.lock` against its CVE database, demonstrating that SCA (Software Composition Analysis) tools are effective at catching known vulnerable dependencies.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #82

Related Articles

high

How Denial of Service via Unbounded Intermediate Arrays happens in JavaScript and how to fix it

CVE-2026-69152 is a high-severity Denial of Service vulnerability in the `brace-expansion` npm package (versions prior to 1.1.18/2.1.4/3.0.6/5.0.9) that allows attackers to crash a Node.js application by crafting glob patterns that generate unbounded intermediate arrays, effectively bypassing the earlier CVE-2026-14257 mitigation. The fix upgrades `brace-expansion` from 1.1.14 to 1.1.18 in `frontend/package-lock.json`, closing the bypass and restoring safe memory bounds during pattern expansion.

high

How Quadratic CPU Consumption happens in JavaScript YAML parsing and how to fix it

A high-severity denial-of-service vulnerability (GHSA-5p4m-2wfm-xmqj) was discovered in js-yaml affecting both the 3.x and 4.x branches, where parsing YAML documents containing `!!omap` tags triggers quadratic CPU consumption. The fix upgrades js-yaml from `^4.1.1` to `5.2.0` in the project's GitHub Actions workflow dependencies, closing the attack surface for any untrusted YAML input processed by CI/CD tooling.

critical

How Missing Rate Limiting happens in Express.js and how to fix it

Two public API endpoints in `server.js` — `/api/health` and `/api/contact` — were exposed without any rate limiting middleware, allowing attackers to exhaust server resources or spam an SMTP server with unlimited requests. The fix adds rate limiting to both endpoints, with stricter controls on the resource-intensive `/api/contact` route that triggers email sending operations. This change closes a directly exploitable denial-of-service vector in a production web service.

high

How Denial of Service via Specific Input Sequence happens in JavaScript (marked) and how to fix it

CVE-2026-41680 is a high-severity Denial of Service vulnerability in the marked Markdown parsing library, affecting versions prior to 18.0.2. By supplying a crafted input sequence to the parser, an attacker can cause the application to hang or exhaust resources, making the frontend unavailable. Upgrading marked from 18.0.0 to 18.0.2 in both `package.json` and `package-lock.json` closes the vulnerability without affecting valid Markdown rendering.

high

How Quadratic CPU Consumption happens in JavaScript YAML parsing and how to fix it

A high-severity denial-of-service vulnerability in js-yaml (GHSA-5p4m-2wfm-xmqj) caused quadratic CPU consumption when resolving `!!omap` YAML types in both the 3.x and 4.x branches. The fix upgrades js-yaml from 3.14.2 to 3.15.1 and from 4.1.1 to 4.3.1, eliminating the algorithmic complexity exploit while leaving all valid YAML inputs unaffected.

high

How Denial of Service via Unbounded Data Happens in JavaScript and how to fix it

CVE-2025-58754 is a high-severity Denial of Service vulnerability in the popular axios HTTP client library, caused by the absence of a data size check on incoming response or request payloads. An attacker who can influence the size of data processed by axios could exhaust server memory or CPU, bringing down dependent Node.js applications. The fix upgrades axios from version 1.8.4 to 1.18.0, closing the unbounded data processing path.