Back to Blog
high SEVERITY7 min read

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.

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

Answer Summary

CVE-2026-69152 is a Denial of Service vulnerability (CWE-400: Uncontrolled Resource Consumption) in the `brace-expansion` npm package affecting versions before 1.1.18, 2.1.4, 3.0.6, and 5.0.9. Attackers supply a specially crafted brace-expansion pattern that causes the library to allocate unbounded intermediate arrays during expansion, exhausting process memory and crashing the application — even on builds that applied the earlier CVE-2026-14257 patch. The fix is a version upgrade: pin `brace-expansion` to 1.1.18 (or the appropriate major-version equivalent) in `package-lock.json` and lock the transitive `minimatch` dependency to consume only the patched release.

Vulnerability at a Glance

cweCWE-400
fixUpgrade brace-expansion to 1.1.18 in frontend/package-lock.json and pin minimatch's dependency to the patched version
riskAn attacker can crash or hang a Node.js process by supplying a malicious glob/brace-expansion pattern
languageJavaScript / Node.js
root causebrace-expansion 1.1.14 failed to bound intermediate array growth during recursive pattern expansion, bypassing the CVE-2026-14257 mitigation
vulnerabilityDenial of Service via Unbounded Intermediate Array Allocation

How Denial of Service via Unbounded Intermediate Arrays Happens in JavaScript and How to Fix It

Introduction

The frontend/package-lock.json file governs every transitive JavaScript dependency the frontend build installs — including low-level utility libraries that most developers never think about. One of those utilities, brace-expansion, is responsible for expanding shell-style brace patterns like {a,b,c} or {1..100} into flat string arrays. It is pulled in automatically by minimatch, which is itself a dependency of many popular build tools.

In this project, brace-expansion was pinned at version 1.1.14. That version contains a high-severity Denial of Service vulnerability tracked as CVE-2026-69152: a carefully crafted input pattern causes the library to build intermediate arrays with no upper bound on their size, consuming all available heap memory and crashing the Node.js process. What makes this CVE particularly interesting is that it is not a brand-new class of bug — it is a bypass of the mitigation that was already shipped for the earlier CVE-2026-14257. The attacker community found a way around the guard rail, and 1.1.14 never received the follow-up patch.


The Vulnerability Explained

What brace-expansion does

brace-expansion takes a string such as "file.{js,ts,tsx}" and returns ["file.js", "file.ts", "file.tsx"]. For numeric ranges like "{1..10000}" it generates every integer in that range. Internally the library builds intermediate arrays at each expansion step before concatenating them into the final result.

Where 1.1.14 falls short

The CVE-2026-14257 patch added a check to limit the total number of final expanded strings. However, the intermediate arrays assembled during the recursive expansion steps were not subject to the same limit. An attacker can craft a nested pattern where the intermediate arrays grow exponentially before the final-count guard is ever evaluated:

# Example of a pathological pattern (illustrative)
"{a,b,c,d,e,f,g,h}{a,b,c,d,e,f,g,h}{a,b,c,d,e,f,g,h}...{...}"

Each nested brace group multiplies the size of the intermediate result array. Because 1.1.14's guard only inspects the output length, not the working memory during expansion, the process can allocate gigabytes of heap before the check fires — or before the check fires at all if the pattern is designed to stay just under the output threshold while maximising intermediate allocations.

The vulnerable dependency declaration in package-lock.json

Before the fix, the node_modules/brace-expansion block read:

"node_modules/brace-expansion": {
  "version": "1.1.14",
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
  "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
  "dependencies": {
    "balanced-match": "^1.0.0",
    "concat-map": "0.0.1"
  }
}

And the legacy "dependencies" section of the lockfile mirrored the same version. Meanwhile, minimatch declared a loose range:

"minimatch": {
  "requires": {
    "brace-expansion": "^1.1.7"
  }
}

The ^1.1.7 range permits any 1.x release ≥ 1.1.7, but npm install had resolved it to 1.1.14 and frozen that in the lockfile — meaning the vulnerable version was locked in place and would not auto-upgrade.

Real-world attack scenario

If any part of this frontend's build pipeline or server-side rendering layer passes user-controlled strings to a function that internally calls minimatch or brace-expansion (e.g., a file-glob API, a template engine, or a search filter that supports glob syntax), an attacker can send a POST body containing a malicious pattern. The Node.js process expands the pattern, intermediate arrays balloon unchecked, the heap limit is hit, and the process crashes or becomes unresponsive — a classic CWE-400 Uncontrolled Resource Consumption scenario.

Even if the application does not expose glob inputs directly today, the vulnerability lives in the dependency tree and can be triggered by future feature additions or by tooling that runs in CI/CD pipelines.


The Fix

Upgrading brace-expansion to 1.1.18

The pull request makes two targeted changes to frontend/package-lock.json (and a corresponding update to frontend/package.json).

Change 1 — the node_modules/brace-expansion block:

 "node_modules/brace-expansion": {
-  "version": "1.1.14",
-  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
-  "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+  "version": "1.1.18",
+  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+  "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
+  "license": "MIT",
   "dependencies": {
     "balanced-match": "^1.0.0",
     "concat-map": "0.0.1"
   }
 }

Change 2 — the legacy "dependencies" section:

 "brace-expansion": {
-  "version": "1.1.14",
-  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
-  "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+  "version": "1.1.18",
+  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+  "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
   "requires": {
     "balanced-match": "^1.0.0",
     "concat-map": "0.0.1"
   }
 }

Change 3 — pinning minimatch's transitive requirement:

 "minimatch": {
   "requires": {
-    "brace-expansion": "^1.1.7"
+    "brace-expansion": "1.1.18"
   }
 }

Why each change matters

Change Purpose
Bump node_modules/brace-expansion to 1.1.18 Ensures npm ci installs the patched version in the module tree
Bump the legacy lockfile brace-expansion entry Keeps both lockfile formats consistent; older npm clients read the flat section
Pin minimatch → brace-expansion to exact 1.1.18 Prevents a future npm install from resolving the loose ^1.1.7 range back to a vulnerable version

Version 1.1.18 introduces bounds checking on the intermediate arrays during recursive expansion, not just on the final output count. This closes the bypass that made CVE-2026-14257's mitigation ineffective.


Prevention & Best Practices

1. Treat lockfiles as security artifacts

package-lock.json is not just a performance optimisation — it is the authoritative record of every dependency version your application will install. Commit it, review it in PRs, and never .gitignore it.

2. Run a vulnerability scanner in CI

Trivy (used here), npm audit, Snyk, and Socket.dev all maintain databases of known-vulnerable package versions. Add one as a required CI step so that new CVEs are caught before they reach production.

# Minimal CI gate using npm audit
npm audit --audit-level=high

3. Use exact version pins for security-sensitive transitive deps

The minimatch fix demonstrates a useful technique: when a transitive dependency has a known-vulnerable range, override it with an exact version pin inside the lockfile. This prevents accidental regression during routine npm install runs.

4. Enable Dependabot or Renovate

Automated dependency update bots create PRs when new versions are published, keeping the gap between a CVE disclosure and your upgrade as small as possible.

5. Limit glob/pattern inputs from users

If your application passes any user-controlled string to a glob-matching function, validate or sanitise the input first:

// Reject patterns that could cause exponential expansion
function isSafeGlobPattern(pattern) {
  // Limit total length
  if (pattern.length > 256) return false;
  // Limit nesting depth of braces
  const braceDepth = (pattern.match(/\{/g) || []).length;
  if (braceDepth > 3) return false;
  return true;
}

Relevant standards

  • CWE-400: Uncontrolled Resource Consumption — https://cwe.mitre.org/data/definitions/400.html
  • OWASP — Denial of Service: https://owasp.org/www-community/attacks/Denial_of_Service

Key Takeaways

  • The CVE-2026-14257 mitigation in brace-expansion 1.1.14 was incomplete: it guarded final output size but left intermediate array allocation unbounded, creating a bypass that CVE-2026-69152 exploits.
  • Lockfile pinning matters: the loose "brace-expansion": "^1.1.7" range in minimatch's requires block would have allowed npm to resolve back to a vulnerable version; the fix pins it to the exact safe release 1.1.18.
  • Transitive dependencies carry real risk: brace-expansion is three levels deep in the dependency tree, yet a single malicious string passed to minimatch can crash the process.
  • Both lockfile sections must be updated: package-lock.json contains both a node_modules/ tree section and a legacy flat "dependencies" section; updating only one leaves the other out of sync and can cause inconsistent installs across npm versions.
  • Trivy caught what manual review would likely miss: no developer routinely audits the integrity hash of a three-level-deep transitive dependency — automated scanning is the practical defence here.

How Orbis AppSec Detected This

  • Source: The brace-expansion package version string ("1.1.14") declared in frontend/package-lock.json — data that originates from the npm registry resolution of user-installed dependencies.
  • Sink: Any call site within the application's dependency tree that invokes brace-expansion's internal array-building logic with an attacker-controlled pattern string (reachable through minimatch and any library that depends on it).
  • Missing control: No upper bound on intermediate array allocation during recursive brace expansion; the existing CVE-2026-14257 guard only checked final output length, not working memory.
  • CWE: CWE-400 — Uncontrolled Resource Consumption.
  • Fix: brace-expansion was upgraded from 1.1.14 to 1.1.18 in both sections of frontend/package-lock.json, and minimatch's transitive requirement was pinned to the exact 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-2026-69152 is a sharp reminder that security patches are not always final. The brace-expansion maintainers shipped a fix for CVE-2026-14257, but an attacker-visible bypass remained in the intermediate array allocation path — and versions that never received the follow-up patch (like 1.1.14) stayed vulnerable. Upgrading to 1.1.18 closes both the original issue and the bypass in one step.

For JavaScript developers, the lesson is clear: keep lockfiles up to date, scan them automatically in CI, and treat transitive dependencies with the same security scrutiny you apply to first-party code. A three-line change to package-lock.json is all it takes to eliminate a high-severity DoS vector.


References

Frequently Asked Questions

What is a Denial of Service via unbounded intermediate arrays?

It is an attack where a library allocates progressively larger arrays during input processing with no upper bound, eventually exhausting available memory and crashing the process.

How do you prevent unbounded array growth DoS in JavaScript?

Keep third-party parsing libraries up to date, enforce input length limits before passing data to expansion functions, and use lockfiles that pin transitive dependencies to patched versions.

What CWE is this vulnerability?

CWE-400 — Uncontrolled Resource Consumption ("Resource Exhaustion"), which covers scenarios where an application does not adequately limit the resources consumed during processing.

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

No. CVE-2026-69152 is an explicit bypass of that earlier mitigation. Only upgrading to brace-expansion ≥ 1.1.18 (or the equivalent patched minor for v2/v3/v5) fully closes the attack surface.

Can static analysis detect this vulnerability?

Yes. Trivy flagged this exact pattern by matching the installed version of brace-expansion in package-lock.json against its vulnerability database, as shown in this PR.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #464

Related Articles

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.

high

How Path Traversal happens in PostCSS Source Map Loading and how to fix it

A path traversal vulnerability in PostCSS versions before 8.5.18 allowed malicious `sourceMappingURL` comments in CSS files to trick PostCSS into loading arbitrary `.map` files from the filesystem. The fix upgrades PostCSS from 8.5.15 to 8.5.18 in `frontend/package-lock.json` and pins the version via an override in `frontend/package.json`, closing the file disclosure vector before it could be chained with other weaknesses.