Introduction
In a Node.js project's package-lock.json, we discovered a high-severity Denial of Service vulnerability lurking in a transitive dependency: brace-expansion version 1.1.12. This package — used by minimatch and other glob-matching libraries — is deeply embedded in the Node.js ecosystem, powering file path matching in build tools, test runners, and application code alike.
The vulnerability (CVE-2026-14257) allows an attacker to provide a specially crafted brace pattern that triggers unbounded expansion, causing exponential memory consumption and ultimately crashing the process with an out-of-memory error. Because brace-expansion often processes patterns derived from user input (file paths, glob patterns in APIs, configuration strings), this isn't just a theoretical concern — it's an exploitable denial-of-service vector.
The locked dependency in package-lock.json pinned brace-expansion at version 1.1.12:
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="
}
This matters for any developer relying on glob patterns, file matching, or build tooling in their Node.js applications — which is nearly everyone.
The Vulnerability Explained
How brace-expansion works
The brace-expansion library takes a string like {a,b,c} and expands it into an array ['a', 'b', 'c']. It also handles nested and sequential patterns: {a,b}{1,2} becomes ['a1', 'a2', 'b1', 'b2'].
The exponential blowup
The problem in version 1.1.12 is that there is no limit on the number of expansions generated. Consider a pattern like:
{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}
Each pair of braces doubles the output. With 20 pairs, you get 2²⁰ = 1,048,576 strings. With 30 pairs, you get over a billion. The library in version 1.1.12 will attempt to compute and store all of these in memory, with no safeguard.
Attack scenario specific to this project
This project (identified by its package.json metadata as a KETI-authored project related to oneM2M IoT standards) likely processes resource identifiers or path patterns. An attacker could:
- Submit a crafted resource path or query parameter containing deeply nested brace patterns
- The application's glob-matching logic (via
minimatch→brace-expansion) processes the malicious input - Memory consumption spikes exponentially
- The Node.js process crashes with an OOM error, denying service to all users
Even if the code path isn't directly user-facing, any route where untrusted strings reach minimatch or similar glob utilities creates an attack surface.
Why the locked version was dangerous
The package-lock.json explicitly resolved brace-expansion to version 1.1.12:
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
This version lacks the expansion length limits introduced in 1.1.16, meaning every npm install would faithfully install the vulnerable code.
The Fix
The fix involves two coordinated changes across package.json and package-lock.json:
1. Adding npm overrides in package.json
Before:
{
"author": "KETI",
"license": "BSD-3-Clause"
}
After:
{
"author": "KETI",
"license": "BSD-3-Clause",
"overrides": {
"brace-expansion": "1.1.16"
}
}
The overrides field is critical. Because brace-expansion is a transitive dependency (pulled in by minimatch, which is pulled in by other packages), simply upgrading a direct dependency wouldn't guarantee the fix propagates. The overrides field forces npm to resolve brace-expansion to version 1.1.16 everywhere in the dependency tree, regardless of what version ranges other packages specify.
2. Updating package-lock.json
Before:
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="
}
After:
"node_modules/brace-expansion": {
"version": "1.1.16",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="
}
Why version 1.1.16 fixes the issue
Version 1.1.16 of brace-expansion introduces bounds checking on the expansion output. Before generating results, it validates that the total number of expansions won't exceed a safe threshold. If the expansion would produce an unreasonable number of results, it short-circuits and returns the input unexpanded rather than consuming unbounded memory.
Why both files needed to change
package.json: Theoverridesfield ensures futurenpm installruns always resolve to the safe version, even if upstream packages haven't updated their dependency ranges.package-lock.json: The lockfile must reflect the actual resolved version so that CI/CD pipelines and other developers get the patched version immediately without needing to runnpm installwith--force.
Prevention & Best Practices
1. Audit your dependency tree regularly
npm audit
npx trivy fs --scanners vuln .
These commands catch known CVEs in both direct and transitive dependencies.
2. Use npm overrides (or yarn resolutions) proactively
When a transitive dependency has a vulnerability and the intermediate package hasn't released an update, overrides lets you force the fix:
{
"overrides": {
"vulnerable-package": "^patched.version"
}
}
3. Validate input before glob processing
If your application accepts user input that eventually reaches glob-matching functions, validate it first:
// Limit brace nesting depth before passing to minimatch
function isSafePattern(pattern) {
const braceCount = (pattern.match(/\{/g) || []).length;
return braceCount <= 5; // Reasonable limit for legitimate use
}
4. Set resource limits
Use Node.js --max-old-space-size flags and container memory limits to prevent a single request from taking down your entire infrastructure.
5. Pin and lock dependencies
Always commit package-lock.json and review dependency changes in PRs. A version bump in a lockfile should trigger the same scrutiny as a code change.
Relevant standards
- CWE-400: Uncontrolled Resource Consumption
- CWE-1333: Inefficient Regular Expression Complexity
- OWASP: Application Denial of Service
Key Takeaways
brace-expansion1.1.12 has no expansion limit — a pattern with N brace pairs generates 2^N outputs, enabling trivial OOM crashes- Transitive dependencies require
overridesto fix — updating your direct dependencies won't help ifminimatchstill pulls in the oldbrace-expansion - The
package-lock.jsonintegrity hash change (fromsha512-9T9UjW3r...tosha512-IDw48K2...) confirms the actual binary content of the package changed, not just metadata - IoT/oneM2M projects processing resource identifiers are particularly at risk since path patterns may be influenced by external devices or APIs
- A two-file fix (
package.json+package-lock.json) is the minimum — the override ensures persistence, and the lockfile ensures immediate effect
How Orbis AppSec Detected This
- Source: Transitive dependency resolution in
package-lock.jsonpullingbrace-expansion@1.1.12into the project'snode_modules - Sink: Any code path invoking
minimatch()or similar glob utilities that internally callbrace-expansion'sexpand()function with potentially unbounded input - Missing control: No expansion length limit in
brace-expansion1.1.12; no npm override enforcing a patched version - CWE: CWE-400 (Uncontrolled Resource Consumption)
- Fix: Added
"overrides": { "brace-expansion": "1.1.16" }topackage.jsonand updatedpackage-lock.jsonto resolve version 1.1.16, which enforces expansion bounds
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
This vulnerability is a textbook example of how algorithmic complexity attacks exploit unbounded computation in seemingly innocuous utility libraries. The brace-expansion package is downloaded millions of times per week and sits deep in the dependency trees of most Node.js projects. Version 1.1.12's lack of expansion limits meant that any application processing untrusted glob patterns was one crafted string away from a complete denial of service.
The fix — upgrading to 1.1.16 via npm overrides — is minimal in code change but maximal in security impact. It demonstrates that modern application security isn't just about the code you write; it's about the entire supply chain of packages you depend on. Regular dependency auditing, lockfile hygiene, and automated vulnerability detection are essential practices for any Node.js project.