Representative interview topic

Backend interview: How would you safely process archives that may contain decompression bombs?

BackendHard
Offer.cc Editorial TeamPublished Updated

Question

Users upload ZIP or TAR.GZ archives and the service extracts documents inside them. Design a backend that resists decompression bombs, path traversal, nested archives, and resource exhaustion.

Prompt and context

Users upload ZIP or TAR.GZ archives for background virus scanning, text extraction, or batch import. An attacker may upload a tiny compressed file that expands enormously, recursive nested archives, or entry paths that escape the target directory. Explain upload limits, format parsing, resource budgets, isolation, failure states, and cleanup.

The focus is resource safety during extraction. An upload-size limit is not the complete defense; compression ratio, entry count, and expanded output must be part of the design.

What the interviewer tests

The interviewer expects a distinction between compressed bytes and extraction cost. Include files, directory depth, nesting, CPU, memory, disk, and wall-clock budgets. OWASP recommends considering size after decompression; the OWASP WSTG describes a zip bomb as an archive that exhausts disk or memory to cause denial of service.

Strong answers cover traversal, symlinks, duplicate names, false size declarations, parser vulnerabilities, and cleanup after cancellation. An isolated worker, task directory, atomic publication, and telemetry keep malicious input inside one bounded task.

30-second answer

“I would treat every archive as untrusted. The edge limits compressed bytes, format, quota, and request rate. A worker in a restricted container streams entries into a per-task temporary directory while enforcing entry paths, symlink policy, entry count, nesting depth, and an expanded-byte budget. It also has CPU, memory, disk, and wall-clock limits. Any limit stops extraction and cleans up. Only scanned, type-checked results are atomically published. Responses expose safe status, while audit events keep reason codes and usage without raw content.”

Step-by-step design

Step 1: Establish input and task boundaries

Limit compressed size, per-user concurrency, total quota, and request rate. Assign a task ID, tenant, source object, format, and state. Store the original in isolated non-executable storage; a worker receives short-lived read access and never treats upload data as code or configuration.

Step 2: Identify formats and choose parsers

Do not trust only an extension or Content-Type; inspect magic bytes and an allowlist. Choose maintained libraries for ZIP, TAR, and GZIP and pin their versions. Entry size metadata helps preflight but cannot be the only authority; continue counting bytes while reading. Encrypted archives, unknown methods, and malformed headers should be rejected or reviewed.

Step 3: Enforce multidimensional budgets

Define limits for compressed bytes, expanded bytes, file count, single-file size, directory depth, nested archive depth, CPU time, memory, disk, and wall clock. Every task has a hard ceiling. Compression ratio can trigger a warning, but it cannot replace expanded-byte counting because formats and data compress differently.

Step 4: Protect paths and filesystem objects

Normalize each entry name and reject absolute paths, traversal, and null bytes. Extract into a directory unique to the task and verify every final path remains inside it. Reject symlinks, hard links, and device files by default. Define a duplicate-name policy, usually rejection, to avoid overwrite-order surprises.

Step 5: Isolate extraction and scanning

Run the worker in a low-privilege container or sandbox with a read-only root, temporary-disk quota, and no or minimal network access. Give extraction and scanning separate budgets so one archive cannot consume both silently. Write scan results, text, and thumbnails as new non-executable objects, not directly as browser downloads.

Step 6: Bound nesting, recursion, and cancellation

Reject nested archives when the product does not need them. If nesting is required, set a maximum depth, shared total budget, and path checks at every layer; recursion must not receive a fresh quota. Timeouts, cancellation, worker crashes, and tenant deletion all trigger idempotent cleanup so temporary files do not remain.

Step 7: Use a state machine for publication and recovery

States can be uploaded, inspecting, extracting, scanning, published, rejected, and cleanup_failed. Publish atomically only after every entry passes checks and scanning. Safe user status can include the next action; internal events retain structured rejection codes, budget usage, and library errors. Retrying must not skip budgets or publish twice.

Step 8: Test and observe the limits

Test high compression ratios, nested archives, traversal, symlinks, duplicate names, malformed headers, huge entries, bad CRCs, encrypted files, and mid-task cancellation. Measure expanded bytes, file count, maximum depth, CPU, rejection reason, cleanup latency, temporary-disk waterline, and worker restarts. Use synthetic malicious samples against the exact library version in production.

Trade-offs, boundaries, and information gain

Metadata preflight is cheap but cannot replace streaming counters; pure streaming is safer but may spend some resources before discovering a limit. Rejecting nesting is safest, while supporting it serves backup workflows at the cost of shared budgets and recursive checks.

An isolated worker lowers main-service risk but adds queue latency and operations. Retaining originals helps investigation and retry, but requires retention limits, access controls, and encryption. An asynchronous queue is not a reason to permit unbounded extraction.

Model high-quality answer

“I would split upload and extraction into two trust boundaries. The edge limits compressed bytes, concurrency, and quota. An asynchronous worker in a low-privilege, networkless container uses a maintained parser and writes only to a task directory. As it reads each entry, it counts expanded bytes and files and enforces single-file size, directory depth, nested depth, CPU, memory, disk, and wall-clock budgets.

Normalized paths reject absolute paths, traversal, symlinks, hard links, and device files. If nesting is needed, every layer shares one budget. Results are scanned before atomic publication; over-limit, timeout, cancellation, and crash all use idempotent cleanup. Telemetry records budget use, rejection reasons, cleanup latency, and disk waterline, with malicious archives in regression tests.”

Common mistakes

  • Limit only compressed upload size. A tiny file can exhaust disk or memory after extraction.
  • Trust declared expanded size. Headers can be incomplete or untrusted.
  • Use compression ratio as the only rule. It is a signal, not a universal safety budget.
  • Extract into a shared directory. Traversal, overwrite, and residue cross tenant boundaries.
  • Allow symlinks or device files. Entries can redirect writes outside the target or to special interfaces.
  • Give every nesting layer a new quota. Recursion multiplies resource consumption.
  • Publish raw content after scanning. Scripts and wrong content types can still attack downloaders.
  • Skip cleanup after cancellation. Residue can create a disk-exhaustion incident.

Follow-up questions and answers

Should scanning happen before extraction?

The archive must be read to scan inner files, but extraction itself needs isolation and budgets. Perform format and metadata checks first, stream extraction in a restricted worker, then scan each result.

At what compression ratio should you reject?

There is no cross-format universal number. Use ratio for alerts or early rejection, while hard limits cover expanded bytes, one file, file count, CPU, disk, and wall clock.

How can nested archives be supported safely?

Set a maximum depth, share one total budget across layers, repeat path and link checks, and keep recursion within the same worker’s time and disk quota.

Can parser-reported entry sizes be trusted?

They are warning signals, not authorization. Continue counting bytes while reading and account for Zip64, malformed headers, data descriptors, and known library limitations.

Should an over-limit task be retried?

A budget violation is deterministic rejection and should not be blindly retried. Retry only transient worker failures, creating a fresh isolated directory with the same budget and idempotent state.

Public sources

Related questions