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.

Prevention & Best Practices

1. Use Dependency Overrides / Resolutions for Transitive Vulnerabilities

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

// package.json (pnpm)
{
  "pnpm": {
    "overrides": {
      "brace-expansion": ">=1.1.18"
    }
  }
}
// package.json (yarn)
{
  "resolutions": {
    "brace-expansion": "1.1.18"
  }
}

2. Validate User-Supplied Glob Patterns

If your application accepts glob or brace-expansion patterns from users, apply a length and complexity check before passing them to any expansion library:

const MAX_PATTERN_LENGTH = 256;

function safeExpand(pattern) {
  if (typeof pattern !== 'string' || pattern.length > MAX_PATTERN_LENGTH) {
    throw new Error('Pattern too long or invalid');
  }
  return braceExpansion(pattern);
}

This defense-in-depth measure limits blast radius even if a future bypass is discovered.

3. Run SCA Scans in CI

Integrate a Software Composition Analysis tool (Trivy, Snyk, npm audit, or OWASP Dependency-Check) into your CI pipeline so vulnerable transitive dependencies are caught before they reach production:

# GitHub Actions example
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: '.'
    severity: 'HIGH,CRITICAL'
    exit-code: '1'

4. Watch for "Bypass" CVEs on Previously Patched Packages

CVE-2026-69152 is a textbook example of a mitigation bypass. When a package receives a CVE patch, monitor its advisory feed for follow-on CVEs that describe bypasses of the original fix. Subscribe to GitHub Security Advisories for packages you depend on.

5. Relevant Standards

  • CWE-400: Uncontrolled Resource Consumption — the root CWE for this vulnerability class.
  • OWASP A06:2021 – Vulnerable and Outdated Components: Keeping dependencies current and scanning for known CVEs is a core OWASP Top 10 control.

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.


References

Frequently Asked Questions

What is a DoS via unbounded intermediate arrays in brace-expansion?

It is a vulnerability where specially crafted brace-expansion patterns (e.g., `{a,b}{c,d}...` repeated many times) cause the library to construct intermediate arrays that grow exponentially, consuming all available memory and CPU before any output length check can stop it.

How do you prevent unbounded array expansion DoS in JavaScript?

Pin dependencies to patched versions, add overrides/resolutions in package.json to force safe versions across transitive dependencies, and validate or limit the length and complexity of any user-supplied glob or brace-pattern strings before passing them to expansion libraries.

What CWE is this vulnerability?

CWE-400 – Uncontrolled Resource Consumption ("Resource Exhaustion"), because the library allocates memory proportional to the expansion of the input without an effective upper bound.

Is the CVE-2026-14257 mitigation enough to prevent this DoS?

No. CVE-2026-69152 specifically bypasses the output-length check introduced for CVE-2026-14257 by exploiting unbounded growth of intermediate arrays created during the expansion process, before the final length check is reached.

Can static analysis detect this vulnerability?

Yes. Trivy and similar SCA (Software Composition Analysis) scanners detect this by matching the installed package version against the CVE advisory database. Orbis AppSec uses Trivy to flag the vulnerable brace-expansion version in pnpm-lock.yaml and automatically opens a remediation PR.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #61

Related Articles

critical

How Information Disclosure via Malformed Cache-Control Directives Happens in Node.js and How to Fix It

A critical vulnerability (CVE-2026-13697) was discovered in the undici HTTP client library, allowing attackers to exploit malformed Cache-Control directives for information disclosure and denial of service. This fix upgrades undici from version 7.25.0 to 7.29.0 using npm overrides to ensure all nested dependencies receive the patched version.

critical

How Unsandboxed Plugin Execution Happens in Node.js and How to Fix It

A critical vulnerability (CVE-2026-54466) was discovered in the `websocket-driver` dependency (version 0.7.4), which handles WebSocket protocol framing and I/O. The fix upgrades the package to version 0.7.5 via an npm override in `package.json` and an updated lockfile, closing a WebSocket frame-parsing flaw that could allow attackers to inject or manipulate WebSocket traffic. This dependency-level fix is essential because the vulnerable library sits in the application's dependency tree and proce

high

How Missing Minimum Release Age Configuration in pnpm Workspaces Happens and How to Fix It

A Node.js library's pnpm workspace configuration lacked the `minimumReleaseAge` setting, leaving it vulnerable to malicious or unstable newly-published packages. By adding a 7-day waiting period (10,080 minutes) along with additional hardening measures like `blockExoticSubdeps` and `trustPolicy`, the project now has robust defense against supply chain attacks targeting its dependencies.

critical

How CORS Misconfiguration happens in Node.js with Hono and how to fix it

CVE-2026-54290 is a HIGH severity CORS misconfiguration in the Hono web framework where the CORS middleware incorrectly reflects any `Origin` header back to the client — including credentials — when the `origin` option defaults to a wildcard. Upgrading `hono` from `4.12.16` to `4.12.34` in `package-lock.json` and pinning the version via `overrides` in `package.json` closes the vulnerability. Left unpatched, this flaw could allow malicious cross-origin sites to make credentialed requests and read

high

How Denial of Service via Infinite Loop happens in Node.js and how to fix it

A critical Denial of Service vulnerability (CVE-2026-67213) in the nanoid package allowed attackers to trigger infinite loops during random ID generation. This fix upgrades nanoid from version 3.3.11 to 3.3.18 using npm overrides, eliminating the infinite loop condition in the customAlphabet function that could crash Node.js applications.

high

How insecure-use-string-copy-fn happens in C and how to fix it

A high-severity vulnerability was identified in `plugin/bin/install.c` where `strcpy()` and `strncpy()` were used to handle path strings without proper bounds checking or guaranteed null-termination. The fix replaces `strcpy()` with direct character assignment and `strncpy()` with `snprintf()`, eliminating both buffer overflow and missing null-terminator risks in the plugin installation workflow.