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.iobrotlicrate. 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_OUTPUTdefault of 256 MiB, mirroring the deflate core's cap. - A caller-configurable entry point
decode_with_limit(data, max_output); the defaultdecodeis justdecode_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 returnsBrotliError::OutputTooLargebefore extendingout, 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_bombanddecode_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
brrequests 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 viadecode_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
brbodies byte-for-byte. The crates.iobrotlicrate 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
compressionfacade and is scaffolded, not a long-running campaign. The crate itself has no fuzz target in-tree.large_windowis aBrotliParamsflag, 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."