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) #
{
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.