Cap extraction size/count and strip setuid bits (#2) #5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/2-extraction-caps-setuid"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What changed
Entry validation already refused traversal, escaping links, and special files, but two gaps in safe extraction remained, so #2 stayed open. A decompression bomb could still expand into a destination-filling tree, and a setuid/setgid binary inside an artifact would be written through verbatim on a runner that extracts as root.
assertSafeTarbounds the summed uncompressed size and the header count (defaults 10 GiB and 100000 entries), overridable viaRASTER_ARTIFACTS_MAX_TOTAL_BYTESandRASTER_ARTIFACTS_MAX_ENTRIES(0 disables a cap). An archive over a cap is refused and nothing is written.assertSafeTarreports the entries carrying them;extractArchiveclears the bits after extraction, since tar's own handling varies by version, umask, and privilege.How it was tested
make lintclean (actionlint/yq not installed locally; skipped)make test(offline suite) green, 26 testsassertSafeTarand cleared bystripSetidCHANGELOG updated? yes
Closes #2.
VERDICT: APPROVE (posted as a comment; the review API blocks a formal approve from the PR-author identity this CI tooling runs as. Over to you for the non-author approve + merge.)
Reviewed
feat/2-extraction-caps-setuid@77e7e22. CI green (unit + e2e).make testgreen locally (26 tests). Diff is confined tosrc/lib/artifact-tar.js+ tests + CHANGELOG/README.What I verified
Size/count caps refuse before any write to the destination. Confirmed with real
tar-built archives, not just the test harness:RASTER_ARTIFACTS_MAX_ENTRIES=3is refused and the destination stays empty.RASTER_ARTIFACTS_MAX_TOTAL_BYTES=64KiB, is refused (uncompressed size exceeds cap) with nothing written to dest.assertSafeTarbeforetar -xf, andsizeis summed from each header's declared size, which is exactly what tar would write.0/non-positive disables a cap (documented, tested).setuid/setgid handling. Built a real
0o4755file, archived and extracted:assertSafeTarflags it viasetidPaths,stripSetidclears0o6000after extraction, ordinary perms (owner-exec) and contents are preserved. The strip is unconditional post-extraction (re-stat + chmod), so it also covers a root runner where tar would keep the bits. Operator precedence inparseMode(block) & SETID_BITSis correct (&&binds looser than&).No regression to the merged work. Digest verification (#1) lives in
download-impl.js(verifyDigestruns beforetar.extractArchive) and is not touched by this diff; theextractArchive(archive, dest, method)public signature is unchanged (caps are an internal default param). The #2-core entry validation (absolute/../escaping link/special file) is preserved invalidateEntry; only its return value changed (null vs resolved path), and the existing safety tests still pass.Tests are real. Over-cap-by-size, over-cap-by-count, disabled-cap, setuid-stripped-on-extract, plus direct
assertSafeTar(reports setid) andstripSetid(clears on disk) unit tests. The suite honestly notes that non-root tar drops setuid on its own and adds the direct strip-path test to cover the root case.Non-blocking observation (follow-up, not for this PR): for compressed archives,
extractArchivedecompresses the whole object to a temp.tarinos.tmpdir()before the byte cap is evaluated, so a true high-ratio bomb is still fully written to/tmp(then refused before the destination). This is pre-existing (random-access validation needs a seekable tar) and still fails closed (a full/tmpaborts the pipeline). Worth a later issue to cap/stream the decompression itself, but out of scope here and it does not weaken the destination guarantee this PR makes.LGTM. Not merging (non-author); over to you.