How Denial of Service via Unbounded Brace Expansion Happens in Node.js Dependencies and How to Fix It
Introduction
In the CortexKit project's bun.lock file, a critical vulnerability lurked within a transitive dependency: adm-zip version 0.5.17 contained a flaw that could allow attackers to crash the entire application by uploading a single malicious ZIP file. The vulnerability (CVE-2026-39244) stemmed from unbounded brace expansion during ZIP file decompression—a pattern-matching operation that, without proper limits, could expand exponentially and consume all available system memory.
This wasn't a theoretical risk. The CortexKit CLI and plugin packages (@cortexkit/magic-context, @cortexkit/pi-magic-context, and @cortexkit/opencode-magic-context) all depend on adm-zip for ZIP file processing. Any user uploading a crafted ZIP archive could trigger an out-of-memory (OOM) crash, effectively denying service to all users of the application.
The Vulnerability Explained
What is Brace Expansion in ZIP Processing?
Brace expansion is a shell-like feature that expands patterns into multiple strings. For example:
- file-{1,2,3}.txt expands to file-1.txt, file-2.txt, file-3.txt
- {a,b}{x,y} expands to ax, ay, bx, by (4 combinations)
When processing ZIP file paths, adm-zip 0.5.17 would expand these patterns without enforcing any upper limit on the number of resulting strings. An attacker could craft a ZIP file containing paths like:
{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}...
This creates 16^n possible combinations. With just 10 nested braces, that's over 1 quadrillion potential paths. When adm-zip attempted to expand these during decompression, it would allocate memory for each expanded path, quickly exhausting available RAM and crashing the process.
The Attack Scenario
Consider a CortexKit user uploading a ZIP file through the CLI:
magic-context process-archive malicious.zip
The malicious ZIP contains:
archive/
├── {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}...
When adm-zip 0.5.17 processes this, the expansion logic runs without bounds:
// Pseudocode of vulnerable behavior in adm-zip 0.5.17
function expandBraces(pattern) {
// No limit on expansion size!
const expanded = [];
// Expands {a,b,c}...{x,y,z} into millions/billions of strings
for (let i = 0; i < combinations; i++) {
expanded.push(generateCombination(i)); // Memory grows unbounded
}
return expanded;
}
Each expanded path is stored in memory. With exponential combinations, memory usage skyrockets from kilobytes to gigabytes in seconds, causing:
- Process crash due to OOM
- Application unavailability
- Potential cascading failures in dependent services
Why This Matters for CortexKit
The CortexKit project handles user-supplied files in:
- CLI package (packages/cli): Processes archives uploaded by end users
- PI Plugin (packages/pi-magic-context): Integrates with other systems that may accept ZIP files
- OpenCode Plugin (packages/plugin): Processes code archives
Each of these packages explicitly lists adm-zip as a dependency. Without the fix, any user interaction with ZIP files could trigger a denial-of-service attack.
The Fix
The security team addressed this vulnerability by upgrading adm-zip from 0.5.17 to 0.6.0. This wasn't a minor patch—it was a targeted security release that implemented critical bounds checking.
What Changed in bun.lock
The bun.lock file shows the precise change:
- "adm-zip": ["adm-zip@0.5.17", "", {}, "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ=="],
+ "adm-zip": ["adm-zip@0.6.0", "", {}, "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg=="],
Additionally, an explicit override was added to ensure all packages use the fixed version:
+ "overrides": {
+ "adm-zip": "0.6.0",
+ },
How adm-zip 0.6.0 Fixes the Issue
The 0.6.0 release implements maximum expansion length limits. Instead of allowing unbounded expansion, it now:
- Enforces a maximum expansion size: Expansion operations that would exceed a threshold (e.g., 1000 combinations) are rejected or truncated
- Validates input before processing: Checks for suspicious patterns before attempting expansion
- Implements resource limits: Caps memory allocation during decompression operations
The fix transforms the vulnerable code path:
// OLD (0.5.17): No bounds checking
function expandBraces(pattern) {
const expanded = [];
// Expands infinitely without limits
for (let combination of generateAllCombinations(pattern)) {
expanded.push(combination);
}
return expanded;
}
// NEW (0.6.0): With bounds checking
function expandBraces(pattern) {
const MAX_EXPANSION_SIZE = 1000; // Hard limit
const expanded = [];
let count = 0;
for (let combination of generateAllCombinations(pattern)) {
if (count >= MAX_EXPANSION_SIZE) {
throw new Error("Brace expansion exceeds maximum allowed size");
}
expanded.push(combination);
count++;
}
return expanded;
}
Why All Three Package Versions Were Bumped
The PR shows version bumps across three packages:
- "version": "0.37.0",
+ "version": "0.39.0",
This occurred in:
- packages/cli
- packages/pi-plugin
- packages/plugin
These version bumps indicate that the CortexKit maintainers released new versions of their own packages to signal that they now include the fixed dependency. This follows semantic versioning best practices: a security fix in a critical dependency warrants at least a minor version bump.
Prevention & Best Practices
1. Dependency Scanning in CI/CD
The vulnerability was detected using Trivy, a static vulnerability scanner. Integrate Trivy or similar tools into your CI/CD pipeline:
trivy fs . --severity HIGH,CRITICAL
This catches known vulnerabilities before code reaches production.
2. Automated Dependency Updates
Use tools like Dependabot or Renovate to automatically open pull requests for security updates:
- Renovate can auto-merge security patches
- Dependabot provides detailed vulnerability reports
- Both integrate with GitHub, GitLab, and other platforms
3. Resource Limits During File Processing
Even with updated dependencies, implement application-level safeguards:
// Limit memory usage during ZIP processing
const { Worker } = require('worker_threads');
const vm = require('vm');
function processZipWithTimeout(zipPath, timeoutMs = 5000) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('ZIP processing timeout'));
}, timeoutMs);
try {
const zip = new AdmZip(zipPath);
clearTimeout(timeout);
resolve(zip);
} catch (err) {
clearTimeout(timeout);
reject(err);
}
});
}
4. Input Validation
Before processing ZIP files, validate:
- File size limits: Reject files exceeding a threshold
- Entry count limits: Limit the number of files in a ZIP
- Path length validation: Reject entries with suspiciously long paths
function validateZipFile(zipPath, maxSize = 100 * 1024 * 1024) {
const fs = require('fs');
const stats = fs.statSync(zipPath);
if (stats.size > maxSize) {
throw new Error(`ZIP file exceeds maximum size of ${maxSize} bytes`);
}
const zip = new AdmZip(zipPath);
if (zip.getEntries().length > 10000) {
throw new Error('ZIP file contains too many entries');
}
for (const entry of zip.getEntries()) {
if (entry.entryName.length > 500) {
throw new Error(`Entry name exceeds maximum length: ${entry.entryName}`);
}
}
}
5. References to Security Standards
This vulnerability relates to:
- CWE-400: Uncontrolled Resource Consumption ('Resource Exhaustion')
- OWASP A01:2021: Broken Access Control (applies to resource exhaustion scenarios)
- OWASP DoS Prevention Cheat Sheet: Covers resource limit strategies
Key Takeaways
- Brace expansion in adm-zip 0.5.17 had no upper bounds, allowing attackers to craft ZIP files that expand into billions of paths and exhaust application memory
- The fix enforces maximum expansion limits: adm-zip 0.6.0 rejects or truncates expansions exceeding a threshold, preventing memory exhaustion
- Dependency scanning caught this before production: Trivy detected CVE-2026-39244 in the dependency tree, enabling proactive remediation
- Explicit overrides ensure consistency: The
"overrides"entry inbun.lockguarantees all transitive dependencies use the fixed version - Resource limits are a defense-in-depth strategy: Application-level timeouts and file validation provide additional protection even with updated dependencies
How Orbis AppSec Detected This
Source: ZIP file paths within user-uploaded archives (untrusted input from file system operations)
Sink: Brace expansion logic in adm-zip's path processing during decompression
Missing control: Bounds checking on expansion size; no maximum limit on the number of resulting path combinations
CWE: CWE-400 (Uncontrolled Resource Consumption)
Fix: Upgrade adm-zip from 0.5.17 to 0.6.0, which implements maximum expansion length limits and validates input before decompression
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
The adm-zip DoS vulnerability (CVE-2026-39244) illustrates how even well-maintained open-source libraries can harbor resource exhaustion vulnerabilities. By implementing unbounded brace expansion without limits, adm-zip 0.5.17 created a denial-of-service vector that attackers could exploit with a single malicious ZIP file.
The CortexKit project's rapid upgrade to adm-zip 0.6.0 demonstrates best practices in secure dependency management:
1. Monitor for vulnerabilities using automated scanning (Trivy)
2. Update promptly when security releases are available
3. Communicate the fix through version bumps and explicit overrides
4. Implement defense-in-depth with application-level resource limits
For developers working with ZIP file processing, file uploads, or archive handling, this vulnerability serves as a reminder: always validate resource consumption during decompression, implement timeout mechanisms, and keep your dependencies updated. Unbounded operations on untrusted input are a recipe for denial-of-service vulnerabilities.
References
- CWE-400: Uncontrolled Resource Consumption ('Resource Exhaustion')
- OWASP Denial of Service Prevention Cheat Sheet
- CVE-2026-39244 Details
- adm-zip GitHub Repository
- Trivy Vulnerability Scanner Documentation
- GitHub PR: fix: upgrade adm-zip to 0.6.0 (CVE-2026-39244)
- Semgrep Rule for Resource Exhaustion Detection