the limiter bounds what's held *at once*, and each archive gives its bytes back when it's released. So a recursive bomb (42.zip shape) never trips a limit: every level fits on its own, and the scan just runs for effectively forever. Capping each archive at N× its own size wouldn't help either, since every level of such a bomb is high-ratio, so the work still grows about N-fold per level.
- each top-level archive gets one decompression budget of `max(100 × its size, 1 GiB)`, spent by every archive nested under it at any depth
- the budget is attached to the context when the walk enters a top-level archive, so `Extract` picks it up without a signature change
- once it's spent, the archive being extracted is truncated with a "possible decompression bomb" unknown and the walk moves on. The next top-level archive starts fresh
real trees (a war of jars, a layer of tarballs) expand a few times over, well under the ratio.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
a negative `MaxDepth` really was unbounded, and nothing noticed an archive nested inside a copy of itself. A zip quine at `-1` recursed until memory, disk or file descriptors ran out, running the full sub-pipeline at every level.
- the walk keeps a stack of the digests of the archives it's inside, and an archive identical to one of them is recorded as an unknown instead of being entered
- negative depth now stops at 16 levels (docs and CLI description updated)
- cancellation is checked between candidates, so a cancelled scan stops walking instead of finishing the archive tree
the three unknown-recording blocks in `processArchive` became a one-line helper to stay under `funlen`.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
sub-pipeline catalogers were already run under `runTaskSafely`, but extraction wasn't: the zip, tar and compressor decoders run on untrusted bytes, and a panic in one unwound the whole walk and failed `CreateSBOM` with "failed to run tasks".
- `processArchive` recovers and returns the panic (with its stack) as an error at the archive's coordinates, so it becomes an unknown and the sibling archives are still cataloged
- `Extract` now defers `Cleanup` on failure instead of calling it on each error path, so a panic no longer leaks the limiter charge or the spill file
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
`ArchiveSearchConfig{MaxDepth: 2}` is the natural literal, but zero memory and zero disk meant "forbid both", so every archive was truncated to nothing and only unknowns came out.
- `MaxMemoryBytes` / `MaxDiskBytes` of `0` now mean the default, negative is still unbounded
- the limiter keeps its own zero-forbids semantics internally; it's just no longer reachable from config
- tests that relied on zero to force spilling now pass `archive.Limits` directly, and the two "both limits zero" tests are gone since that config no longer exists
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>