Brotli

rusty-js-brotli is Cruft's Brotli codec, the code that decompresses br-encoded HTTP responses and DecompressionStream input. It decodes and encodes real Brotli and caps output at 256 MiB (configurable via decode_with_limit) so a few hundred compressed bytes cannot name gigabytes.

When something on the wire arrives Brotli-compressed, an HTTP response, a DecompressionStream, a cached asset, the crate that turns it back into bytes is rusty-js-brotli. A decode call hands back a Vec<u8> or a Buffer, and the open question is how big that Vec is allowed to get: a few hundred bytes of compressed input can name gigabytes of output. This page covers what the crate guarantees versus what it merely attempts.

Alpha, unaudited. rusty-js-brotli (Cruft 0.0.10) stands in for the crates.io brotli crate. It has near-zero external audit and limited interop/conformance testing, and it is not production-ready. Trust it only where this page cites an in-tree test, and not against adversarial input.

Correctness and safety are two separate claims

"It decompresses Brotli" is really two claims: (1) it produces the correct bytes for well-formed streams (a correctness/interop claim), and (2) it stays safe when the stream is hostile (a security claim). The crates.io brotli crate has years of fuzzing and production traffic behind both. rusty-js-brotli is young, so the two claims are worth weighing separately.

Correctness (tested, but narrow). The crate implements a real Brotli codec: encode (encode) and decode (decode), with meta-block parsing, canonical/complex prefix codes, LZ77 back-references, and the static dictionary with word transforms (StaticDictionaryWord, DictionaryTransform). In-tree tests cover literal-length round-trips (roundtrips_literal_lengths_one_through_512), large uncompressed blocks (roundtrips_large_uncompressed_blocks), and cross-check tests that decode generated streams the way Node would (generated_two_compressed_meta_blocks_decode_under_node, second_generated_meta_block_static_dictionary_uses_stream_history). Round-tripping the crate's own output and a set of generated streams is covered. What is not covered is broad conformance against the upstream Brotli test-vector corpus or arbitrary real-world br payloads, so do not assume "byte-identical to the reference decoder on any input."

Safety (the decompression-bomb axis). This is where a fresh Brotli decoder is most likely to be weak, so it is worth examining closely.

Memory safety leaves allocation unbounded

Rust rules out out-of-bounds reads here, but that is not the interesting property. A decompression bomb never reads out of bounds; it just keeps allocating. The subtle version of the flaw is a decoder that bounds each meta-block (say mlen <= 65536) but puts no ceiling on the total accumulated output across the meta-block loop: a crafted stream with a small insert and a large copy length fills a block from a few input bits, and if nothing caps the number of blocks, a handful of bytes can name multiple gigabytes. That path is reachable from the DecompressionStream brotli route and any Content-Encoding: br handler through compression::brotli_decode.

That distinction, memory-safe but unbounded, is why the crates.io brotli maturity baseline still matters: the reference decoder has had bomb defenses and fuzz coverage for a long time. This crate had to grow them, and it has.

The output cap

The bomb defense is the kind you can point at:

  • A MAX_OUTPUT default of 256 MiB, mirroring the deflate core's cap.
  • A caller-configurable entry point decode_with_limit(data, max_output); the default decode is just decode_with_limit(data, MAX_OUTPUT).
  • The limit is checked before growth, not after: the generated meta-block loop does a checked_add(mlen) against the limit and returns BrotliError::OutputTooLarge before extending out, and the same pre-growth check guards the literal path and the uncompressed-block path.
  • Two hostile tests cover it: decode_with_limit_rejects_literal_output_bomb and decode_with_limit_rejects_accumulated_meta_block_output_bomb, the latter checking that two successive meta-blocks are stopped when their combined output crosses a caller limit while the same stream decodes cleanly under the default.

The sibling compression crate inherits this: brotli_decode_with_limit is exposed and the default brotli_decode wraps the capped decoder. So the Content-Encoding: br and DecompressionStream routes flow through a bounded decoder.

Limitations

  • The default cap is high for a shared server. 256 MiB is a per-call ceiling. Concurrent hostile br requests each get their own budget, so N simultaneous requests can name up to N times 256 MiB. If you host this behind a request handler, pass a smaller limit via decode_with_limit / brotli_decode_with_limit; do not lean on the default. The cap prevents the unbounded bomb, not memory pressure from concurrency.
  • Interop breadth is unproven. The round-trip and generated-stream tests cover correctness on the crate's own and constructed streams. There is no coverage of the full upstream Brotli conformance vectors or arbitrary production br bodies byte-for-byte. The crates.io brotli crate wins decisively on this maturity axis, and on years of production and fuzz exposure.
  • No external audit, narrow fuzzing. The fuzz lane lives in the compression facade and is scaffolded, not a long-running campaign. The crate itself has no fuzz target in-tree. large_window is a BrotliParams flag, not an exercised decode path.
  • What it does NOT claim. It does not claim constant-time behavior, it does not claim streaming-incremental decode as a hardened surface, and it does not claim parity with the reference encoder's ratios at a given quality.

For everything above the bomb cap, the present-tense statement is "attempts it; unverified against a broad corpus; do not rely."