How Unbounded Brace-Expansion DoS Happens in Node.js and How to Fix It
The Silent Threat in Your Build Pipeline
In the packages/icons-scripts project, a seemingly innocent dependency was silently creating a critical vulnerability. The brace-expansion library, used extensively by glob pattern matching tools throughout the build pipeline, contained a flaw that allowed attackers to crash the entire application through carefully crafted input. This wasn't a complex exploit—it was a simple matter of sending the right string to the wrong function.
The vulnerability lived in yarn.lock, where versions 2.1.2 and 5.0.6 of brace-expansion were locked in place. These versions lacked a critical safeguard: limits on how many strings a brace pattern could expand into. An attacker could provide a pattern like {1..999999999} or nested expansions that would cause the library to attempt generating billions of strings, consuming gigabytes of memory in seconds and crashing the process.
Understanding the Vulnerability: Brace-Expansion Without Boundaries
Brace expansion is a shell feature that expands patterns like {a,b,c} into multiple strings: a b c. It's incredibly useful for glob patterns in build tools, but it's also dangerous when unbounded.
The core problem:
The vulnerable versions of brace-expansion (2.1.2 and 5.0.6) processed brace patterns without any limits on expansion size. Consider this attack vector:
// Vulnerable code path in brace-expansion 2.1.2
const expand = require('brace-expansion');
// Attacker provides this input through a glob pattern
const maliciousPattern = '{1..999999999}';
const result = expand(maliciousPattern); // Attempts to create 999,999,999 strings!
When this pattern is processed, the library attempts to create an array containing nearly 1 billion string elements. Each string consumes memory, and the process quickly exhausts available RAM. The application crashes with an out-of-memory error, creating a perfect denial-of-service condition.
Real-world attack scenario:
In the icons-scripts package, glob patterns are used to process icon files during build time. If an attacker could influence the glob pattern—through a malicious configuration file, environment variable, or package.json field—they could trigger this vulnerability:
# Attacker creates a package with a malicious glob pattern
glob("icons/{1..999999999}/*.svg", callback)
# Process crashes before any icons are processed
The impact extends beyond just the build process. Any tool in the dependency chain that uses brace-expansion becomes a potential attack vector. Since @swc/cli (a Rust-based JavaScript compiler used in this project) depends on brace-expansion, the vulnerability existed in the production build pipeline.
The Fix: Enforcing Expansion Limits
The pull request addressed this vulnerability by upgrading brace-expansion to patched versions that implement hard limits on expansion:
diff --git a/yarn.lock b/yarn.lock
index 12e0b938..062cb151 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1583,20 +1583,20 @@ __metadata:
linkType: hard
"brace-expansion@npm:^2.0.2":
- version: 2.1.2
- resolution: "brace-expansion@npm:2.1.2"
+ version: 2.1.3
+ resolution: "brace-expansion@npm:2.1.3"
dependencies:
balanced-match: "npm:^1.0.0"
- checksum: 10/0e0f9b0df1f0809e9c326470e022eeb24334ddb54c43b1ac39f1d38f3cbaeb940794de1db826f5491843ac00d7932c43f10350a65ccd51fe7d05da627152f204
+ checksum: 10/72a6d8c6be638a883dc3adfb88a41f2c34130512e0255bb845f6804f0f577b83d2f84247eec33c32b3adedd4849ccc53b84c54a8267594fabc1f8293ebc7ae02
languageName: node
linkType: hard
"brace-expansion@npm:^5.0.2":
- version: 5.0.6
- resolution: "brace-expansion@npm:5.0.6"
+ version: 5.0.8
+ resolution: "brace-expansion@npm:5.0.8"
dependencies:
balanced-match: "npm:^4.0.2"
- checksum: 10/a7acf120fefa79e9d7c9c92898114f57c07596a3920197f3c5917e6a628b04220a5f7f9618c30bdd973a6576a32113b99f9c3f1c8245ccc399dd2a9a718d81d8
+ checksum: 10/75d2d2ebc8f5f65156d16706216d23e48f499a1164e4b045e0ca4045d3ce6b16ced11ea2e4c76e3670b6c840980aea9a35e061
languageName: node
linkType: hard
What changed:
-
brace-expansion 2.1.2 → 2.1.3: The patch version update includes a fix that prevents unbounded expansion by implementing a maximum expansion limit. Patterns that would exceed this limit are now rejected or truncated safely.
-
brace-expansion 5.0.6 → 5.0.8: The newer major version also includes the expansion limit fix, along with improvements to the dependency on balanced-match (upgraded from ^1.0.0 to ^4.0.2), which handles pattern validation more robustly.
-
Dependency reorganization: The PR also moved
@swc/clifromdependenciestodevDependenciesinpackage.json, since it's only needed during build time, not at runtime. This reduces the attack surface by excluding it from production installations.
How the fix works:
The patched versions implement a configurable maximum expansion size (typically around 32,000-65,000 strings). When a pattern would expand beyond this limit, the library:
// Fixed behavior in brace-expansion 2.1.3+
const expand = require('brace-expansion');
const maliciousPattern = '{1..999999999}';
const result = expand(maliciousPattern);
// Returns: error or safely truncated result
// Does NOT crash the process
Instead of attempting to create billions of strings, the library now:
- Detects the expansion would exceed limits
- Throws a controlled error or returns a safe default
- Allows the process to continue gracefully
- Prevents memory exhaustion
Prevention & Best Practices
1. Keep Dependencies Updated
The most critical step is maintaining up-to-date dependencies. Use tools like:
- npm audit to identify known vulnerabilities
- yarn upgrade to update packages safely
- Dependabot or Renovate to automate security updates
# Check for vulnerabilities
npm audit
# Upgrade to latest versions
npm update brace-expansion balanced-match
2. Validate Glob Patterns
Never pass untrusted input directly to glob or brace-expansion functions:
// ❌ UNSAFE: User-controlled input
const pattern = userInput; // Could be '{1..999999999}'
glob(pattern, callback);
// ✅ SAFE: Validate and sanitize
const allowedPatterns = ['*.js', '*.css', '*.html'];
if (!allowedPatterns.includes(pattern)) {
throw new Error('Invalid glob pattern');
}
glob(pattern, callback);
3. Use Allowlists for Glob Patterns
Restrict glob patterns to a predefined set:
const ALLOWED_GLOBS = {
'icons': 'icons/**/*.svg',
'styles': 'styles/**/*.css',
'scripts': 'src/**/*.js'
};
function safeGlob(key) {
if (!ALLOWED_GLOBS[key]) {
throw new Error('Unknown glob key');
}
return glob(ALLOWED_GLOBS[key]);
}
4. Implement Resource Limits
Use Node.js process limits to prevent memory exhaustion:
// Set maximum memory usage
const maxMemory = 512 * 1024 * 1024; // 512MB
require('v8').setFlagsFromString(`--max-old-space-size=${maxMemory / 1024 / 1024}`);
5. Monitor Dependency Updates
Subscribe to security advisories:
- Node Security Database
- npm Security Advisories
- GitHub Security Alerts
Key Takeaways
-
Unbounded expansion is exponential: A pattern like
{1..N}doesn't create N strings—it can create exponential growth with nested patterns, making attacks devastating. -
brace-expansion 2.1.2 and 5.0.6 lack safeguards: These specific versions had no limits on expansion size, making them vulnerable to trivial DoS attacks through glob patterns.
-
Patched versions enforce limits: Upgrading to 2.1.3 and 5.0.8 adds hard limits that prevent expansion beyond safe thresholds, eliminating the DoS vector entirely.
-
Supply chain matters: Even though brace-expansion is a transitive dependency (pulled in by @swc/cli), it directly impacts application security. One vulnerable library in your dependency tree can crash your entire build pipeline.
-
Validation alone isn't enough: While input validation helps, the real fix requires the library itself to enforce limits—which is why upgrading is non-negotiable.
How Orbis AppSec Detected This
Source: The vulnerability entered through the yarn.lock file, where the brace-expansion dependency was locked to vulnerable versions (2.1.2 and 5.0.6).
Sink: The dangerous code path exists in any call to brace-expansion functions that process glob patterns without expansion limits. In this project, the sink is the glob pattern processing used by @swc/cli during the build process.
Missing control: The vulnerable versions lacked any mechanism to limit the number of strings generated during expansion. There was no check to prevent patterns like {1..999999999} from attempting to allocate billions of strings.
CWE: CWE-400 (Uncontrolled Resource Consumption / Resource Exhaustion)
Fix: Upgrade brace-expansion from 2.1.2 to 2.1.3 and from 5.0.6 to 5.0.8, which implement hard limits on expansion size to prevent memory exhaustion attacks.
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-14257 demonstrates how even small, utility libraries can pose significant security risks when they lack proper resource constraints. The brace-expansion vulnerability wasn't a complex code injection or authentication bypass—it was a straightforward resource exhaustion flaw that could crash production builds with a single malicious glob pattern.
By upgrading to versions 2.1.3 and 5.0.8, the project eliminated this denial-of-service vector entirely. The fix shows that sometimes the best security practice is simply keeping your dependencies updated. Automated tools like Orbis AppSec make this easier by continuously scanning for known vulnerabilities and submitting patches automatically.
As developers, we must remember that security isn't just about preventing attackers from stealing data—it's also about preventing them from disrupting service. Every dependency in your supply chain is a potential attack surface. Stay vigilant, keep your packages updated, and validate untrusted input at every boundary.