Safe Decompress

Operator-facing decompression primitive for gzip / deflate / deflate-raw (RFC 1951) / brotli / Z_NO_COMPRESSION-wrapped variants. Replaces ad-hoc zlib.gunzipSync(buf) / zlib. inflateRawSync(buf) calls in operator code with a single primitive that bounds OUTPUT BYTES + EXPANSION RATIO at the refuse boundary so a malicious peer can't ship a kilobyte of compressed input that explodes into gigabytes before the size check fires.

Algorithms accepted (allowlist — adding to the list is an operator-explicit opt-in to a new bomb-class surface):

- "gzip"zlib.gunzipSync (RFC 1952) - "deflate"zlib.inflateSync (RFC 1950 zlib wrapper) - "deflate-raw"zlib.inflateRawSync (RFC 1951 deflate bytes without the zlib wrapper; SAML / WebSocket permessage-deflate / status-list) - "brotli"zlib.brotliDecompressSync (RFC 7932)

Refused with safe-decompress/unsupported-algorithm: - "zstd" — Node's zlib doesn't expose zstd in v24 LTS; operators pin to a Node version when it lands AND wire through the framework's algorithm allowlist. - Any algorithm not in the allowlist (including operator-typo'd).

Refusal posture: - safe-decompress/decompress-failed — bomb-by-absolute-size (zlib's own maxOutputLength refuses before alloc; the throw is caught and surfaced under this code) - safe-decompress/ratio-exceeded — expansion > maxRatio (zlib accepted the bytes; our post-decompress ratio check refuses, freeing the bytes immediately) - safe-decompress/decompress-failed — malformed input; zlib's own RFC-grammar refusal surfaces here - safe-decompress/empty-input — zero-byte input - safe-decompress/oversized-input — pre-decompression compressed-input cap exceeded

Each refusal can emit a safe-decompress.refused audit event when operators wire opts.audit. The event metadata names the algorithm, compressedBytes, refusal reason — no decompressed bytes ever cross the audit boundary on the bomb-class path.

Threat model: - **Decompression bomb** (CWE-409 — improper handling of highly compressed data; the classic 42.zip nested-bomb expands to petabytes from kilobytes) across gzip / deflate / brotli — the bounded-output cap + expansion-ratio cap refuse before the allocation, so no decompressed bytes are ever materialized past the cap. - **Efail-class** (CVE-2017-17688 / 17689) — operators decrypting MIME parts compose b.safeDecompress on the inner deflate streams; the bounded-output posture defeats the unbounded- allocation arm of the attack.

Composes: - b.audit.safeEmit — bomb-refusal audit event (drop-silent per rule §5) - b.constants.BYTES.* — operator-facing byte-size constants

RFC / CVE citations: - [RFC 1950](https://www.rfc-editor.org/rfc/rfc1950) zlib - [RFC 1951](https://www.rfc-editor.org/rfc/rfc1951) deflate - [RFC 1952](https://www.rfc-editor.org/rfc/rfc1952) gzip - [RFC 7932](https://www.rfc-editor.org/rfc/rfc7932) brotli - [CWE-409](https://cwe.mitre.org/data/definitions/409.html) improper handling of highly compressed data (decompression bomb)

b.safeDecompress(input, opts) #

stable0.11.5hipaapci-dssgdprsoc2
{
  algorithm:           "gzip" | "deflate" | "deflate-raw" | "brotli",
  maxOutputBytes:      number,        // required; zlib refuses pre-alloc
  maxCompressedBytes:  number,        // optional; default 4 MiB input cap
  maxRatio:            number,        // optional; default 50:1 expansion
  windowBits:          number,        // optional; per-algorithm zlib opt
  audit:               object,        // optional b.audit handle for refusal events
  ctx:                 string,        // optional caller identifier (logged on refusal)
}

Decompress input (Buffer / Uint8Array) under opts.algorithm with bounded output bytes and bounded expansion ratio. Refuses bomb-class input BEFORE allocating the expanded buffer via zlib's own maxOutputLength; refuses ratio-bomb shapes AFTER decompression by checking out.length / input.length against opts.maxRatio and dropping the buffer if the ratio is exceeded.

var b = require("@blamejs/core");
var compressed = Buffer.from("...", "base64");
try {
  var bytes = b.safeDecompress(compressed, {
    algorithm:       "gzip",
    maxOutputBytes:  b.constants.BYTES.mib(32),
    maxRatio:        100,
  });
} catch (e) {
  if (e.code === "safe-decompress/ratio-exceeded") {
    // bomb-class shape; audit + refuse upstream
  } else {
    throw e;
  }
}

Last updated 2026-08-08T16:39:49.652Z by seeder.