Hardening: bound the decompress-to-temp size before the extraction caps run #6

Closed
opened 2026-06-17 18:32:29 +00:00 by stephen · 0 comments
Owner

Why

Follow-up from the review of #5 (which closed #2). The size/entry caps in assertSafeTar protect the destination: an over-cap archive is refused before tar -xf writes anything to destDir. But for a compressed artifact, extractArchive first decompresses the whole object to a temp .tar in os.tmpdir() so the header walk has random access:

src/lib/artifact-tar.js  (extractArchive)
  gzip -dc <archive> > <tmp>.tar   # full uncompressed size lands here
  assertSafeTar(<tmp>.tar, ...)    # caps evaluated only now

So a high-ratio decompression bomb (tiny object, huge expansion) is still written in full to /tmp before the byte cap rejects it. It fails closed (a full /tmp aborts the pipeline and nothing reaches the destination), but a single hostile artifact can still exhaust the runner's temp disk, which is a DoS on co-scheduled jobs.

Acceptance criteria

  • The decompression step is bounded so it cannot write more than the configured size cap (plus a small margin) to temp before aborting. Options: cap via the streamed byte count (e.g. abort the gzip -dc/zstd -dc pipe once it exceeds RASTER_ARTIFACTS_MAX_TOTAL_BYTES), or validate headers off the decompression stream without a full temp materialization.
  • 0/disabled cap keeps current behavior.
  • A test proves a bomb whose decompressed size exceeds the cap is refused without writing the full payload to temp.
  • No regression to the existing destination caps (#2) or digest verification (#1).

Scope note

cache-action does not share this exposure: its restore streams gzip -dc | tar -tvP for the listing and gzip -dc | tar -xpP for extraction, and enforces the size cap from the streamed listing, so it never materializes a decompressed temp tar. This issue is download-artifact-action-specific (it needs random access for the manual header walk).

Refs #5, #2.

## Why Follow-up from the review of #5 (which closed #2). The size/entry caps in `assertSafeTar` protect the **destination**: an over-cap archive is refused before `tar -xf` writes anything to `destDir`. But for a compressed artifact, `extractArchive` first decompresses the whole object to a temp `.tar` in `os.tmpdir()` so the header walk has random access: ``` src/lib/artifact-tar.js (extractArchive) gzip -dc <archive> > <tmp>.tar # full uncompressed size lands here assertSafeTar(<tmp>.tar, ...) # caps evaluated only now ``` So a high-ratio decompression bomb (tiny object, huge expansion) is still written in full to `/tmp` before the byte cap rejects it. It fails closed (a full `/tmp` aborts the pipeline and nothing reaches the destination), but a single hostile artifact can still exhaust the runner's temp disk, which is a DoS on co-scheduled jobs. ## Acceptance criteria - The decompression step is bounded so it cannot write more than the configured size cap (plus a small margin) to temp before aborting. Options: cap via the streamed byte count (e.g. abort the `gzip -dc`/`zstd -dc` pipe once it exceeds `RASTER_ARTIFACTS_MAX_TOTAL_BYTES`), or validate headers off the decompression stream without a full temp materialization. - `0`/disabled cap keeps current behavior. - A test proves a bomb whose decompressed size exceeds the cap is refused without writing the full payload to temp. - No regression to the existing destination caps (#2) or digest verification (#1). ## Scope note cache-action does **not** share this exposure: its restore streams `gzip -dc | tar -tvP` for the listing and `gzip -dc | tar -xpP` for extraction, and enforces the size cap from the streamed listing, so it never materializes a decompressed temp tar. This issue is download-artifact-action-specific (it needs random access for the manual header walk). Refs #5, #2.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rasterstate/download-artifact-action#6
No description provided.