Skip to content
>_ TrueFileSize.com
··8 min read

Preventing Zip Bomb Attacks — Detection and Safe Extraction

A zip bomb is a malicious archive file designed to crash or disable the program or system that opens it. A 42KB ZIP file can expand to 4.5 petabytes. Here's how they work and how to protect your application.

What Is a Zip Bomb?

A zip bomb exploits the compression ratio of ZIP files. Highly repetitive data compresses extremely well:

  • 42.zip — 42 KB compressed → 4.5 PB (4,500,000 GB) when fully extracted (nested ZIPs inside ZIPs)
  • Flat zip bomb — A single ZIP with one file that's 10 GB of zeros → compresses to ~10 MB
  • Quine zip — A ZIP file that contains itself recursively

When your server extracts a user-uploaded ZIP without size limits, a zip bomb can:

  • Fill all disk space (denial of service)
  • Exhaust memory (OOM kill)
  • Overwhelm antivirus scanners
  • Crash your application

How to Detect Zip Bombs

Method 1: Check Uncompressed Size Before Extracting

const AdmZip = require('adm-zip');

function isZipBomb(zipPath, maxUncompressedMB = 500) {
  const zip = new AdmZip(zipPath);
  const entries = zip.getEntries();

  let totalUncompressed = 0;
  for (const entry of entries) {
    totalUncompressed += entry.header.size; // Uncompressed size from header

    if (totalUncompressed > maxUncompressedMB * 1024 * 1024) {
      return true; // Zip bomb detected
    }
  }

  return false;
}

Limitation: The header size can be falsified. A sophisticated zip bomb reports a small uncompressed size in the header but expands to much more.

Method 2: Check Compression Ratio

Normal files compress 2-10x. A zip bomb compresses 1000x+.

function checkCompressionRatio(zipPath, maxRatio = 100) {
  const zip = new AdmZip(zipPath);
  const compressedSize = require('fs').statSync(zipPath).size;

  let totalUncompressed = 0;
  for (const entry of zip.getEntries()) {
    totalUncompressed += entry.header.size;
  }

  const ratio = totalUncompressed / compressedSize;
  if (ratio > maxRatio) {
    throw new Error(`Suspicious compression ratio: ${ratio.toFixed(0)}:1`);
  }
}

Method 3: Extract with Running Size Limit

The safest approach — stop extraction if the running total exceeds a threshold:

const { createWriteStream } = require('fs');
const yauzl = require('yauzl');

function safeExtract(zipPath, destDir, maxBytes = 500 * 1024 * 1024) {
  return new Promise((resolve, reject) => {
    let totalExtracted = 0;

    yauzl.open(zipPath, { lazyEntries: true }, (err, zipFile) => {
      if (err) return reject(err);

      zipFile.readEntry();
      zipFile.on('entry', (entry) => {
        zipFile.openReadStream(entry, (err, stream) => {
          if (err) return reject(err);

          stream.on('data', (chunk) => {
            totalExtracted += chunk.length;
            if (totalExtracted > maxBytes) {
              stream.destroy();
              zipFile.close();
              reject(new Error('Zip bomb detected: extraction size limit exceeded'));
            }
          });

          const dest = createWriteStream(/* safe path */);
          stream.pipe(dest);
          dest.on('finish', () => zipFile.readEntry());
        });
      });

      zipFile.on('end', resolve);
    });
  });
}

Method 4: Count Nesting Depth

Nested zip bombs (ZIP inside ZIP) are caught by limiting recursion:

# Python
import zipfile

def safe_extract(zip_path, dest, max_depth=3, current_depth=0):
    if current_depth > max_depth:
        raise ValueError("Maximum nesting depth exceeded — possible zip bomb")

    with zipfile.ZipFile(zip_path) as zf:
        for info in zf.infolist():
            if info.file_size > 500 * 1024 * 1024:  # 500MB limit per file
                raise ValueError(f"File too large: {info.filename}")
            zf.extract(info, dest)

            # Check for nested ZIP
            if info.filename.endswith('.zip'):
                nested = os.path.join(dest, info.filename)
                safe_extract(nested, dest, max_depth, current_depth + 1)

Defense Summary

| Defense | What It Catches | Bypassed By | |---------|----------------|-------------| | Header size check | Flat bombs | Falsified headers | | Compression ratio | High-ratio bombs | Normal-looking ratios | | Running extraction limit | All size bombs | Nothing (safest) | | Nesting depth limit | Recursive bombs | Flat bombs | | File count limit | Many-file bombs | Single large file |

Use all five together for comprehensive protection.

Test Your Protection

| Test | File | Expected | |------|------|----------| | Valid ZIP | sample-1mb.zip | Extracts normally | | Many files | sample-many-files.zip | 1000 files, should pass | | Nested ZIP | sample-nested.zip | Depth limit test | | Corrupted ZIP | sample-corrupt.zip | Graceful rejection | | Password ZIP | sample-with-password.zip | Password handling |

See also: ZIP Extraction Errors · Archive Formats Cheat Sheet