Zstandard is often described as a compression format, but a decoder also implements a resource policy. The frame header can describe a window, a dictionary identifier, a content size, and optional metadata. A service that accepts attacker-supplied frames and attacker-supplied dictionaries has to bound each of those inputs before it allocates memory or writes output.
The frame is a contract
RFC 8878 defines Zstandard frames and skippable frames. A decoder can reject a frame that requests a memory window beyond its authorized range, and the dictionary identifier tells the decoder which dictionary is required, while the mechanism for obtaining that dictionary remains outside the format specification [1]. The project's format document describes the frame header fields and the general structure in more detail [2].
The Frame_Content_Size field is useful metadata, but it is not a trustworthy allocation limit when the input is adversarial. A decoder should enforce its own maximum output, maximum window, and maximum dictionary size. It should also decide how many concatenated frames are acceptable in one stream. Each frame can be small while the aggregate output is large.
A dictionary is not just a performance tuning parameter. It can influence the decoder's matching state and memory use. If the service loads dictionaries by identifier from a shared cache, an attacker may be able to select a dictionary the caller did not intend, or cause cache churn by requesting many identifiers. The application needs a clear mapping from authenticated tenant or content type to an allowed dictionary set.
Three resource boundaries
The first boundary is input size. Limit the compressed request body before the decoder sees it. The second is decoder state. Set a maximum window and dictionary size that the process can afford under concurrency, not just in a single request. The third is output. Enforce a total decompressed byte limit across frames and nested archive layers, and stop writing when the limit would be exceeded.
A separate boundary is frame count. A stream with many skippable frames or small legitimate frames can consume parser time even when the total output is small. Set a maximum number of frames, a maximum metadata size, and a deadline. These limits belong in code that every caller shares; one endpoint with stricter settings does not protect another endpoint that uses the same library directly.
Some APIs expose a streaming decoder precisely so the application can process content without a large allocation. That only helps when the application honors the streaming limits and does not accumulate the full output in memory afterward. Review the application's own buffer, cache, and file writer as part of the same decision.
Dictionary trust and caching
Use an allowlist of dictionary identifiers and hashes. A content type or tenant should map to a small set, not to every dictionary in the cache. Validate the dictionary's source and integrity before use, and do not let a request choose an arbitrary file path. If dictionaries are generated from training data, treat them as code-like artifacts that require versioning and review.
Do not infer tenant isolation from compression. A shared decoder process that handles multiple tenants needs per-request state and output buffers. A dictionary cache shared across tenants needs an authorization check before lookup. A decoded result must be associated with the request that created it, not with a global cache key supplied by the attacker.
Test behavior
Build benign test frames with a high declared window, a large output, multiple concatenated frames, an unknown dictionary identifier, and a truncated header. Confirm that each limit rejects before excessive allocation and that the error is distinguishable from a malformed frame. Test a valid frame from each accepted dictionary and a frame whose identifier is valid but not allowed for that tenant.
For production monitoring, record compressed size, declared window, dictionary identifier, output size, frame count, duration, and outcome. Avoid storing full content. A spike in dictionary identifiers or frame counts can be a useful signal even when individual requests look small.
Conclusion
Zstandard's format is well specified, but its safety depends on the decoder's budgets. Treat dictionaries as trusted configuration, set explicit limits for input, state, output, and frames, and test the negative cases. A decoder that can read a valid frame is not automatically ready for hostile input.