Security review of 52b1e65:
1. (HIGH) tarfile's stream could enumerate differently from GNU tar. Now
extract.sh captures tar's exit code (`|| TAR_RC=$?`), ALWAYS runs the
post-extract verify on whatever tar wrote, and any failure (tar,
verify, either pre-extract scan) removes $DEST before exiting non-zero.
The classifier also refuses (exit 3) on any stream exception and when
its member count differs from GNU tar's own escape-quoted listing.
Measured: a lone zero block mid-archive stops BOTH readers at the same
member (no -i is used), so this is a guard, not a known differential.
2. (MEDIUM) os.walk swallowed unreadable directories. verify now passes
onerror (recorded => fail) and refuses any directory that is not
readable+searchable. 52b1e65 exited 0 on the unreadable fixture.
3. (MEDIUM) a refused tree stayed in the bind-mounted output. fail_extract
opens up 0000 dirs (find -P -type d, never follows links), then
rm -rf -- "$DEST".
Fixtures: cpmove-bad-{unreadable,tarfail,truncated}; a fake tar that lists
one extra member proves the count check. 14 checks fail on 52b1e65.
Docker: unreadable / tarfail runs end with status failed, failed_stage
extract and no extract-work left in /host/sanitized/<id>.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>