A compressed request or uploaded archive can look harmless when its byte count is small. The server may discover a very different workload after decompression: far more output bytes, memory use, disk writes, or nested work than the original input suggested.

That gap matters whenever an attacker can supply compressed data. If the application limits only the compressed input size, a small request can still force expensive expansion and exhaust resources needed by other users. The defensive goal is not to guess which compressed files are malicious. It is to put a hard boundary around the work the application is willing to perform.

The mental model is simple: compressed size limits network input; decompressed limits bound the resulting workload. You often need both.

Compression changes the meaning of a size limit

Suppose an import endpoint accepts a compressed file and rejects uploads larger than 20 MB. It is tempting to conclude that each request can consume at most roughly 20 MB of storage or memory.

That conclusion does not follow. Compression represents repeated or predictable data compactly, so the decompressed representation can be much larger than the compressed bytes. The exact expansion depends on the format and content.

The security boundary therefore needs to follow the resource you are protecting:

compressed bytes received <= input limit
decompressed bytes produced <= output limit
processing work          <= resource budget

These limits answer different questions. The first bounds request transfer and initial storage. The second bounds expansion. The third covers work that is not proportional to byte count, such as parsing many entries or spending excessive CPU on a complex representation.

State the threat model before choosing limits

This control is intended to reduce resource-exhaustion attacks against services that decompress attacker-influenced data. The attacker does not need code execution. They need only a way to make the server perform substantially more work than the request size suggests.

Resources at risk can include memory, temporary disk space, CPU time, worker capacity, and downstream parser capacity. Which one matters most depends on the application architecture.

Decompression limits do not make the decompressor or later parser free from vulnerabilities. They also do not validate the semantic contents of an archive, prevent path traversal during extraction, or decide whether an uploaded file type is appropriate. Those are separate controls.

For trusted, internally generated compressed data, a simple input limit may be enough if the producer and maximum expansion are tightly controlled. Once less-trusted parties can influence the bytes, explicit expansion limits become much more valuable.

Enforce the limit while output is produced

A weak design decompresses everything and checks the final size afterward:

output = decompress(all_input)
if size(output) > MAX_OUTPUT:
    reject

The check happens after the expensive event. By the time size(output) is evaluated, the process may already have allocated memory or written disk space far beyond the intended boundary.

Instead, count output as decompression proceeds and stop before the configured budget is exceeded:

written = 0

for chunk in decompressor(input):
    if written + size(chunk) > MAX_OUTPUT:
        reject_and_stop()

    write(chunk)
    written += size(chunk)

This pseudocode illustrates the control, not a production API. Real decompression libraries differ in how they stream output, report errors, buffer internally, and handle truncated input. Use the library’s documented streaming or bounded-output facilities where available.

The important property is that the application does not need to materialize unbounded output before deciding it is too large.

Archives need more than one counter

A single compressed stream has one obvious expanded-byte count. Archive formats can add another dimension: many entries, metadata records, directories, or nested containers.

Imagine a service that permits a reasonable total extracted size but accepts an enormous number of tiny files. Even if the byte limit holds, creating and scanning those entries can consume filesystem metadata, CPU time, and worker capacity.

For archive processing, useful independent limits can include:

  • total expanded bytes;
  • maximum size of one entry;
  • maximum number of entries;
  • maximum path or metadata sizes where the format and library expose them;
  • processing time or another execution budget appropriate to the platform.

Do not copy arbitrary numbers from another application. A document preview service and a backup ingestion system have very different legitimate workloads. Choose limits from the largest workload the product intends to support, then test the operational cost near that boundary.

Nested compression deserves explicit policy too. If the service extracts an archive and automatically extracts archives found inside it, each layer can multiply work. The simplest policy is often not to recurse unless the product actually needs recursive extraction. If recursion is required, apply a total budget across the whole operation rather than giving every nested object a fresh full allowance.

Do not rely on a compression-ratio threshold alone

A compression ratio compares expanded size with compressed size. It can be a useful signal, but it is a poor universal security boundary.

Highly repetitive legitimate data can compress extremely well. Conversely, an attacker does not need an extraordinary ratio if the service accepts many requests or already permits large compressed inputs.

An absolute decompressed-output limit answers the operational question more directly: how much expanded data is this operation allowed to create? Rate controls and concurrency limits can then bound how many such operations one caller or the whole service can run at once.

A ratio threshold may still be useful as an additional policy for a particular format or product, but it should not replace limits on the resources you actually need to protect.

Keep temporary storage inside the same budget

Streaming decompression is not automatically bounded if output is streamed into unlimited temporary storage.

If the workflow writes expanded data to disk, enforce the output limit before each write and clean up partial output when the operation fails. Put temporary files in a location with appropriate filesystem permissions and, where practical, an independent storage quota so one failure path cannot consume the host’s general-purpose disk.

Cleanup needs an operational plan. A process crash can occur before application-level cleanup runs. Periodic removal of abandoned temporary objects, lifecycle policies in object storage, or isolated ephemeral storage can prevent failed jobs from accumulating indefinitely.

The same principle applies to queues and downstream services. If decompression produces work items, a byte limit alone may not prevent a single request from creating an excessive number of messages. Bound the derived resource that matters.

Limit concurrency as well as individual jobs

A per-request limit answers, “How expensive may one operation become?” It does not answer, “How many expensive operations may run simultaneously?”

Suppose one decompression job is intentionally allowed to use a substantial amount of memory. Ten or a hundred concurrent jobs may still exceed the service’s capacity even though every job obeys its individual limit.

Use platform-appropriate concurrency controls, worker pools, queue backpressure, and caller rate controls where the threat model requires them. The exact mechanism is architecture-specific. The security principle is to prevent attacker-controlled parallelism from multiplying a bounded individual cost into an unbounded aggregate cost.

For especially expensive formats, processing in a separate worker with explicit memory, CPU, and storage constraints can provide another containment layer. This does not replace application limits; it reduces the consequences if those limits or the decompression library behave unexpectedly.

Fail predictably when a budget is exceeded

Resource limits are part of normal input handling, so exceeding one should have a deliberate failure path.

Stop decompression, discard or quarantine incomplete output according to the workflow, release resources, and return an error that does not expose internal filesystem paths or implementation details. Do not pass partial decompressed content to later parsers unless the format and product explicitly define partial processing as valid.

Record enough telemetry to distinguish ordinary oversized submissions from sustained abuse. Useful fields can include the operation type, authenticated principal or other appropriate source identifier, compressed bytes consumed, expanded bytes produced before rejection, and which limit was reached. Avoid logging uploaded contents or other sensitive data merely to diagnose a size rejection.

Repeated limit failures can inform rate controls or operational alerts, but a single oversized file is not proof of malicious intent. Legitimate users can misunderstand limits too.

Test the boundary as a resource property

A good test suite does more than verify that a known problematic sample is rejected.

Test an input just below the expanded-size limit and one that crosses it. Confirm that the crossing input is stopped during expansion rather than after the full output is created. Exercise the entry-count limit independently from the byte limit. If nested containers are supported, verify that the total budget is shared as designed.

Observe memory, temporary disk use, processing time, and cleanup behavior during these tests. A logical counter can be correct while a library buffers far more data internally than expected. That is an implementation property worth measuring in the actual runtime.

Also test concurrency near the service’s intended capacity. Per-job correctness does not prove that aggregate resource use remains acceptable under parallel load.

Put the boundary before the expensive work

The reusable lesson is broader than compressed files: validate limits at the point where a small input can create a large amount of work.

For decompression, keep the compressed request limit, but add explicit budgets for expanded bytes and any other derived resources the workflow can multiply. Enforce those budgets while work is happening, not after it finishes. Add aggregate controls when many individually valid jobs could still exhaust the service.

That design does not depend on recognizing a particular “decompression bomb.” It makes the server’s maximum willingness to work an explicit property of the system, which is a much more dependable defensive boundary.