Back to Blog
high SEVERITY7 min read

How Denial-of-Service via Unbounded Array Expansion 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, where crafted input strings cause the library to generate unbounded intermediate arrays that exhaust memory and CPU—bypassing the earlier CVE-2026-14257 mitigation. The fix upgrades `brace-expansion` across all affected version branches (1.x, 2.x, 3.x, 5.x) and pins the safe version in `package.json` to prevent regression.

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

Answer Summary

CVE-2026-69152 is a high-severity Denial-of-Service (DoS) vulnerability (CWE-400: Uncontrolled Resource Consumption) in the `brace-expansion` JavaScript/Node.js package. An attacker can supply a specially crafted brace-expansion pattern string that causes the library to allocate unbounded intermediate arrays, exhausting process memory and CPU even after the prior CVE-2026-14257 mitigation was applied. The fix upgrades `brace-expansion` to patched versions (1.1.18, 2.1.4, 3.0.6, or 5.0.9 depending on the semver range in use) and pins the safe version explicitly in `package.json` so future installs cannot regress to a vulnerable release.

Vulnerability at a Glance

cweCWE-400
fixUpgrade brace-expansion to 1.1.18 / 2.1.4 / 3.0.6 / 5.0.9 and pin the safe version in package.json
riskAn attacker-controlled brace pattern string can exhaust process memory and CPU, causing application unavailability
languageJavaScript / Node.js
root causebrace-expansion expands nested or repeated brace patterns into intermediate arrays without a hard upper bound on array size, bypassing the length check added for CVE-2026-14257
vulnerabilityDenial-of-Service via unbounded intermediate array expansion

How Denial-of-Service via Unbounded Array Expansion Happens in JavaScript and How to Fix It

The Problem with "Fixed" Vulnerabilities

Security patches are not always final. Sometimes a fix closes one door while leaving a window cracked open. That is exactly what happened with brace-expansion, a widely-used Node.js utility that converts shell-style brace patterns like {a,b,c} into expanded string arrays. A previous vulnerability—CVE-2026-14257—was patched by adding a check on the output array length. CVE-2026-69152 bypasses that check entirely by attacking the intermediate arrays created during expansion, before any length guard is ever consulted.

This post explains how the bypass works, what was changed to fix it, and how to make sure your own projects are not quietly running a vulnerable version.


The Vulnerability Explained

What brace-expansion Does

brace-expansion is a dependency pulled in by hundreds of popular packages—glob, minimatch, fast-glob, micromatch, and many others. It takes a pattern string and expands it:

const expand = require('brace-expansion');
expand('{a,b}{1,2,3}');
// => ['a1', 'a2', 'a3', 'b1', 'b2', 'b3']

This is used everywhere: build tools, test runners, file watchers, CLI utilities. If any of those tools accept user-supplied glob patterns—even indirectly—the expansion logic is reachable from attacker-controlled input.

The Original Mitigation (CVE-2026-14257)

The patch for CVE-2026-14257 added a guard roughly equivalent to:

if (expansions.length > MAX_LENGTH) {
  throw new RangeError('Brace expansion too large');
}

This check fires when the final expanded array grows beyond a threshold. Reasonable in theory—but the expansion algorithm builds intermediate arrays for each nested brace group before assembling the final result.

How CVE-2026-69152 Bypasses the Mitigation

Consider a crafted pattern like:

{0..9}{0..9}{0..9}{0..9}{0..9}{0..9}{0..9}{0..9}

Each {0..9} segment generates a 10-element intermediate array. When the algorithm combines them via a Cartesian product, the intermediate result after the first two groups is 100 elements, after three groups 1,000 elements, and so on—reaching 100,000,000 elements after eight groups. The final-output length check is only applied after all this intermediate memory has already been allocated.

An attacker who can supply any string that reaches brace-expansion's expand() function can trigger this growth curve. The process heap fills up, Node.js throws an out-of-memory error (or simply hangs while the garbage collector thrashes), and the application becomes unavailable—a classic ReDoS-style resource exhaustion attack, but targeting memory allocation rather than regex backtracking.

Real-World Reachability in This Repository

The scanner flagged brace-expansion in pnpm-lock.yaml at version 1.1.16. In the dependency tree, brace-expansion is consumed by tooling that processes file globs. If any part of the build pipeline, dev server, or test harness accepts externally-influenced path patterns (e.g., from environment variables, config files committed by contributors, or CLI arguments in CI), the vulnerable expansion path is reachable. Even in a pure build-tool context, a malicious contributor or a compromised upstream package could trigger the DoS during CI, blocking deployments.


The Fix

What Changed

The fix has two parts:

1. Upgrading brace-expansion across all affected version lines

The PR upgrades to the following patched releases:
- 1.x1.1.18
- 2.x2.1.4
- 3.x3.0.6
- 5.x5.0.9

Each of these releases adds bounds checking on the intermediate arrays produced during Cartesian product assembly, not just the final output. This closes the bypass that CVE-2026-69152 exploits.

2. Pinning the safe version in package.json

The diff adds an explicit override in package.json:

- "serialize-javascript": "7.0.5"
+ "serialize-javascript": "7.0.5",
+ "brace-expansion": "1.1.18"

This pnpm override (under the pnpm.overrides or resolutions field) forces every transitive dependency that pulls in brace-expansion to resolve to 1.1.18 or higher, regardless of what version range they declare. Without this pin, a future pnpm install could silently re-introduce a vulnerable version if a transitive dependency specifies a range that resolves to an older release.

Before and After

Beforepnpm-lock.yaml resolved brace-expansion to 1.1.16:

brace-expansion@1.1.16:
  resolution: {integrity: sha512-...}

After — resolves to 1.1.18:

brace-expansion@1.1.18:
  resolution: {integrity: sha512-...}

The patched version introduces intermediate-array size tracking. Internally, the expansion loop now checks the running size of the Cartesian product before allocating the next batch of intermediate strings, throwing a RangeError early if the expansion would exceed a safe threshold—rather than waiting until the final array is assembled.

Why Both Files Matter

  • pnpm-lock.yaml records the exact resolved version installed on disk. Updating it ensures the current install is safe.
  • package.json records the intent for future installs. Without the explicit pin there, pnpm install after a lockfile reset could pull a vulnerable version again from a transitive dependency's loose semver range.

Key Takeaways

  • CVE-2026-69152 is a bypass, not a new bug class: The intermediate-array growth vector was always present; the CVE-2026-14257 patch simply didn't cover it. Never assume a single patch fully closes a resource-exhaustion vector.
  • Pinning in package.json is as important as updating pnpm-lock.yaml: The lockfile records today's state; the override in package.json protects future installs from regressing.
  • brace-expansion is a deeply transitive dependency: It appears in the dependency trees of glob, minimatch, fast-glob, and many CLI tools. Any Node.js project using file globbing is likely affected.
  • Intermediate-array bounds checking is the correct fix: The patched versions (1.1.18, 2.1.4, 3.0.6, 5.0.9) add size checks during Cartesian product assembly, not only at the end—this is the architectural change that closes the bypass.
  • Defense-in-depth matters: Even with the patched library, validating the length and structure of user-supplied glob patterns before expansion reduces exposure to any future bypasses.

How Orbis AppSec Detected This

  • Source: User-influenced or contributor-supplied brace-expansion pattern strings reaching the expand() function via glob-processing toolchain dependencies recorded in pnpm-lock.yaml.
  • Sink: The brace-expansion package's internal Cartesian product loop, which allocates intermediate arrays without an upper-bound check in versions prior to 1.1.18 / 2.1.4 / 3.0.6 / 5.0.9.
  • Missing control: No intermediate-array size limit in the expansion algorithm; the only existing guard (added for CVE-2026-14257) checked the final output array length, which is reached too late to prevent memory exhaustion.
  • CWE: CWE-400 – Uncontrolled Resource Consumption.
  • Fix: Upgraded brace-expansion to 1.1.18 in pnpm-lock.yaml and added an explicit version pin in package.json to prevent transitive dependency resolution from regressing to a vulnerable release.

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-2026-69152 is a sharp reminder that security patches have a shelf life and that mitigation bypasses are a real threat model. The brace-expansion library's original CVE-2026-14257 fix was a reasonable first step, but it left an exploitable gap in the intermediate expansion phase. The patched versions (1.1.18, 2.1.4, 3.0.6, 5.0.9) close that gap by adding bounds checks where the memory actually gets allocated.

For developers, the lesson is practical: keep SCA scanning in your CI pipeline, use package manager overrides to enforce safe versions across your entire dependency tree, and treat "bypass CVE" advisories with the same urgency as original findings. A vulnerability that was "already patched" is not necessarily safe.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #61

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.