Back to Blog
critical SEVERITY8 min read

How Denial of Service via Gzip Bombs happens in Node.js and how to fix it

A critical vulnerability (CVE-2026-59873) in node-tar versions prior to 7.5.19 allowed attackers to trigger a Denial of Service through specially crafted gzip bombs. The harness-remote-web application was exposed through its dependency on tar 7.5.15, which lacked proper decompression ratio validation. Upgrading to tar 7.5.21 in web/package-lock.json implements safeguards against malicious compressed archives.

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

Answer Summary

CVE-2026-59873 is a critical Denial of Service vulnerability in node-tar (versions before 7.5.19) that allows attackers to crash applications using crafted gzip bombs—small compressed files that expand to enormous sizes. This is a CWE-409 (Improper Handling of Highly Compressed Data) issue affecting Node.js applications. The fix requires upgrading the tar dependency from version 7.5.15 to 7.5.21 or later, which implements decompression ratio checks and resource limits to detect and reject malicious archives before they consume system resources.

Vulnerability at a Glance

cweCWE-409 (Improper Handling of Highly Compressed Data)
fixUpgrade to tar 7.5.21 which implements compression ratio monitoring and resource limits
riskAttackers can crash the application by submitting small malicious archives that expand to gigabytes
languageJavaScript/Node.js
root causenode-tar 7.5.15 lacked decompression ratio validation for gzip archives
vulnerabilityDenial of Service via Gzip Bomb

Introduction

In the harness-remote-web application, we discovered a critical Denial of Service vulnerability in web/package-lock.json that could allow attackers to crash the entire application with a single malicious file. The application depended on node-tar version 7.5.15, which contained CVE-2026-59873—a flaw that fails to detect gzip bombs during archive extraction.

The vulnerability resided in the tar package's decompression logic, which processed compressed archives without validating the expansion ratio. An attacker could submit a seemingly innocent 10KB .tar.gz file that would expand to 10GB when extracted, exhausting all available memory and bringing down the application. This is particularly dangerous for web applications that accept user-uploaded archives or process tar files from untrusted sources.

The Vulnerability Explained

A gzip bomb (also called a decompression bomb or zip bomb) is a malicious archive file engineered to exploit the compression algorithm's efficiency. These attacks work by creating files with extremely high compression ratios—for example, a 42KB compressed file that expands to 4.5 petabytes.

In node-tar version 7.5.15, the extraction process looked like this:

// Vulnerable code pattern in tar 7.5.15
async extract(entry) {
  const data = await this.readCompressed(entry);
  // No validation of decompression ratio!
  const uncompressed = await gunzip(data);
  await writeFile(entry.path, uncompressed);
}

The critical flaw: no validation of the decompression ratio between the compressed input and uncompressed output. The library would happily decompress a 1MB file into 100GB of data, consuming all available memory in the process.

Real-World Attack Scenario

Here's how an attacker could exploit this in the harness-remote-web application:

  1. Craft the malicious archive: Create a tar.gz file containing highly compressible data (like repeated null bytes or patterns)
  2. Submit through upload: If the application accepts tar archives for deployment packages, configuration backups, or user content, upload the gzip bomb
  3. Trigger extraction: When the application calls tar.extract() to process the uploaded file
  4. Resource exhaustion: The Node.js process consumes all available RAM trying to decompress the massive payload
  5. Application crash: The process is killed by the OS or becomes completely unresponsive

The dependency tree in web/package-lock.json showed:

"node_modules/tar": {
  "version": "7.5.15",
  "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
  "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="
}

This meant any code path in the application that processed tar archives—whether for build artifacts, backup restoration, or package installations—was vulnerable to this attack.

Impact Assessment

For the harness-remote-web application specifically:

  • Availability: Complete application downtime when a gzip bomb is processed
  • Resource costs: Potential cloud infrastructure costs from CPU/memory spike before crash
  • Cascading failures: If running in a container orchestration system, repeated crashes could trigger cascading pod restarts
  • Service disruption: All users affected when the web service becomes unavailable

The Trivy scanner flagged this as CRITICAL severity because:
- The vulnerability is trivially exploitable (just upload a file)
- The impact is severe (complete service disruption)
- The attack surface is broad (any tar processing functionality)

The Fix

The fix involved upgrading the tar dependency from version 7.5.15 to version 7.5.21, which implements comprehensive protections against gzip bomb attacks.

Before: Vulnerable Version

"node_modules/tar": {
  "version": "7.5.15",
  "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
  "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="
}

After: Patched Version

"node_modules/tar": {
  "version": "7.5.21",
  "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz",
  "integrity": "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA=="
}

What Changed in tar 7.5.21

The patched version implements several critical safeguards:

  1. Decompression ratio monitoring: Tracks the ratio between compressed input and decompressed output
  2. Maximum size limits: Enforces configurable limits on extracted file sizes
  3. Early termination: Stops extraction immediately when suspicious ratios are detected
  4. Resource accounting: Monitors memory usage during extraction operations

The improved extraction logic now includes:

// Patched code pattern in tar 7.5.21
async extract(entry, options = {}) {
  const maxSize = options.maxSize || MAX_SAFE_EXTRACTION_SIZE;
  let totalUncompressed = 0;

  const data = await this.readCompressed(entry);
  const compressionRatio = data.length / entry.compressedSize;

  // Detect suspicious compression ratios
  if (compressionRatio > MAX_COMPRESSION_RATIO) {
    throw new Error('Potential gzip bomb detected: compression ratio exceeds safe threshold');
  }

  const uncompressed = await gunzip(data);
  totalUncompressed += uncompressed.length;

  // Enforce maximum extraction size
  if (totalUncompressed > maxSize) {
    throw new Error('Extraction size limit exceeded');
  }

  await writeFile(entry.path, uncompressed);
}

Why This Fix Works

The upgrade to 7.5.21 solves the vulnerability by:

  1. Preventing unbounded decompression: The library now rejects archives with abnormal compression ratios before consuming resources
  2. Enforcing resource limits: Maximum extraction sizes prevent memory exhaustion even if ratio checks are bypassed
  3. Fail-fast behavior: Suspicious archives are rejected immediately, not after resources are exhausted
  4. Maintaining compatibility: Valid tar.gz files continue to work normally—only malicious bombs are blocked

The changes in web/package-lock.json also updated the application version from 2.9.0 to 2.11.1, indicating this was part of a broader security update cycle.

Prevention & Best Practices

1. Dependency Management

Always keep archive processing libraries up to date:

# Regular security audits
npm audit

# Update vulnerable packages
npm update tar

# Lock to secure versions
npm install tar@^7.5.21

2. Input Validation

Implement multiple layers of defense when processing archives:

const tar = require('tar');
const { stat } = require('fs/promises');

async function safeExtract(archivePath, destDir) {
  // Layer 1: Validate compressed file size
  const stats = await stat(archivePath);
  if (stats.size > 100 * 1024 * 1024) { // 100MB limit
    throw new Error('Archive too large');
  }

  // Layer 2: Use library's built-in protections
  await tar.extract({
    file: archivePath,
    cwd: destDir,
    maxSize: 500 * 1024 * 1024, // 500MB extraction limit
    strict: true,
    onwarn: (code, message) => {
      throw new Error(`Archive warning: ${message}`);
    }
  });

  // Layer 3: Verify extracted size
  const extractedSize = await calculateDirectorySize(destDir);
  if (extractedSize > 500 * 1024 * 1024) {
    throw new Error('Extracted content exceeds size limit');
  }
}

3. Resource Limits

Configure OS-level and runtime protections:

// Set Node.js memory limits
// node --max-old-space-size=2048 app.js

// Use worker threads for isolation
const { Worker } = require('worker_threads');

function extractInWorker(archivePath) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./extract-worker.js', {
      workerData: { archivePath },
      resourceLimits: {
        maxOldGenerationSizeMb: 512,
        maxYoungGenerationSizeMb: 128
      }
    });

    worker.on('message', resolve);
    worker.on('error', reject);
    worker.on('exit', (code) => {
      if (code !== 0) reject(new Error('Worker stopped'));
    });
  });
}

4. Monitoring & Alerting

Implement runtime detection:

const { performance } = require('perf_hooks');

async function monitoredExtract(archivePath, destDir) {
  const startTime = performance.now();
  const startMemory = process.memoryUsage().heapUsed;

  try {
    await tar.extract({ file: archivePath, cwd: destDir });

    const duration = performance.now() - startTime;
    const memoryDelta = process.memoryUsage().heapUsed - startMemory;

    // Alert on suspicious patterns
    if (duration > 30000 || memoryDelta > 100 * 1024 * 1024) {
      logger.warn('Suspicious extraction detected', {
        duration,
        memoryDelta,
        archivePath
      });
    }
  } catch (error) {
    logger.error('Extraction failed', { archivePath, error });
    throw error;
  }
}

5. Security Standards

Follow OWASP guidelines for file upload security:

  • Validate file types: Check magic bytes, not just extensions
  • Scan uploads: Use antivirus/malware scanning before processing
  • Isolate processing: Extract archives in sandboxed environments
  • Rate limiting: Prevent attackers from submitting multiple gzip bombs
  • User quotas: Limit total extraction volume per user/session

6. Static Analysis Integration

Configure automated scanning in CI/CD:

# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Trivy scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'

Key Takeaways

  • node-tar 7.5.15 had no decompression ratio validation, allowing attackers to submit small archives that expanded to gigabytes and crashed the application
  • The harness-remote-web dependency tree exposed the application to CVE-2026-59873 through its package-lock.json configuration
  • Upgrading to tar 7.5.21 implements compression ratio monitoring and extraction size limits that detect and block gzip bombs before resource exhaustion
  • File size validation alone is insufficient—you must validate decompression ratios since gzip bombs are designed to have small compressed sizes
  • Defense in depth is essential: Combine library updates with input validation, resource limits, worker thread isolation, and runtime monitoring to protect against decompression attacks

How Orbis AppSec Detected This

  • Source: The vulnerable tar 7.5.15 dependency in web/package-lock.json, which could process untrusted archive files from user uploads, deployment packages, or external data sources
  • Sink: The tar.extract() and gunzip() operations that decompress archives without validating compression ratios or enforcing extraction size limits
  • Missing control: No decompression ratio validation, no maximum extraction size enforcement, and no resource consumption monitoring during archive processing
  • CWE: CWE-409 (Improper Handling of Highly Compressed Data / 'Zip Bomb')
  • Fix: Upgraded the tar dependency from version 7.5.15 to 7.5.21, which implements compression ratio checks and resource limits to detect and reject malicious gzip bombs

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-59873 demonstrates how a single outdated dependency can expose your entire application to trivial Denial of Service attacks. The node-tar library's lack of decompression ratio validation in version 7.5.15 meant that any code path processing tar archives was vulnerable to gzip bomb attacks.

The fix—upgrading to tar 7.5.21—was straightforward but critical. The updated library implements multiple layers of protection: compression ratio monitoring, extraction size limits, and early termination for suspicious archives. These safeguards prevent attackers from weaponizing the compression algorithm against your application.

For developers, this vulnerability reinforces several key security principles: maintain up-to-date dependencies, implement defense in depth for file processing operations, and use static analysis tools to catch vulnerable dependencies before they reach production. A small dependency update can be the difference between a stable application and one that crashes from a single malicious upload.

References

Frequently Asked Questions

What is a gzip bomb Denial of Service attack?

A gzip bomb is a maliciously crafted compressed archive that appears small (kilobytes) but expands to an enormous size (gigabytes or terabytes) when decompressed, consuming all available memory and CPU resources to crash the application.

How do you prevent gzip bomb attacks in Node.js?

Use updated versions of archive libraries (tar 7.5.19+, unzipper 0.10.14+) that implement decompression ratio checks, set memory limits for extraction operations, validate archive sizes before processing, and implement timeouts for decompression operations.

What CWE is gzip bomb vulnerability?

Gzip bomb vulnerabilities are classified as CWE-409 (Improper Handling of Highly Compressed Data) and relate to CWE-400 (Uncontrolled Resource Consumption), as they exploit the lack of validation on compression ratios to exhaust system resources.

Is file size validation enough to prevent gzip bomb attacks?

No, validating the compressed file size is insufficient because gzip bombs are specifically designed to have small compressed sizes (often under 1MB) while expanding to massive uncompressed sizes. You must validate the decompression ratio or set strict limits on extracted content size.

Can static analysis detect gzip bomb vulnerabilities?

Yes, static analysis tools like Trivy, Snyk, and npm audit can detect vulnerable versions of archive libraries in dependency trees. However, they identify the vulnerable dependency rather than the specific code path—runtime validation and library updates are still required for complete protection.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #272

Related Articles

high

How Information Disclosure and DoS via malformed Cache-Control directives happens in Node.js undici and how to fix it

A high-severity vulnerability (CVE-2026-13697) in the undici HTTP client library allowed attackers to trigger information disclosure and denial of service through malformed Cache-Control directives. The @jackwener/opencli project upgraded undici from version 7.24.6 to 7.29.0, eliminating the vulnerability in their dependency chain and protecting downstream consumers from exploitation.

high

How Client-Side Denial of Service happens in Node.js FTP clients and how to fix it

CVE-2026-44240 is a client-side Denial of Service vulnerability in the `basic-ftp` Node.js package (versions prior to 5.3.1) caused by improper handling of unterminated multiline FTP server responses. An attacker controlling an FTP server—or capable of intercepting FTP traffic—could send a malformed response that causes the client to hang indefinitely. Upgrading `basic-ftp` to 5.3.1 and adding a package override in `package.json` closes the attack surface entirely.

high

How Route Guard Bypass via Path Traversal happens in Fastify and how to fix it

A high-severity path traversal vulnerability (CVE-2026-15074) in @fastify/static version 9.0.0 allowed attackers to bypass route guards and access restricted files. The agentchatbus-ts service was upgraded from @fastify/static 9.0.0 to 10.1.2, which includes proper path normalization to prevent directory traversal attacks.

high

How Regular Expression Denial of Service happens in JavaScript and how to fix it

CVE-2026-33671 is a Regular Expression Denial of Service (ReDoS) vulnerability in the picomatch glob-matching library, triggered by specially crafted extglob patterns that cause catastrophic regex backtracking. The fix upgrades picomatch to version 4.0.4 (with overrides pinning all transitive copies) in the client's dependency tree, eliminating the vulnerable regex evaluation path. Left unpatched, any code path that passes user-influenced glob patterns to picomatch could be weaponized to stall a

critical

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the shell-quote npm package version 1.8.3, where unescaped line terminators could allow attackers to execute arbitrary code. This fix upgrades shell-quote to version 1.9.0 using npm overrides to ensure all instances in the dependency tree are patched, eliminating the attack vector across the entire application.

critical

How Distributed Lock Takeover Happens in Node.js and How to Fix It

A critical vulnerability in `redis-lock/server.mjs` allowed any authenticated client to release another client's lock by guessing predictable holder identifiers like process IDs or hostnames. The fix implements cryptographically random `lockId` values that are minted on lock acquisition and validated on release, eliminating the exploit primitive entirely.