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.


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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #82

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.