Confine cache restore to the requested paths (#3) #6

Merged
stephen merged 1 commit from feat/3-confine-restore into main 2026-06-16 23:36:02 +00:00
Owner

What changed

Restore extracts with tar -xpP, which trusts absolute member paths, so a poisoned cache object could write or link outside the requested cache paths (#3). Restore now enumerates the archive with tar -tvP and confines every member, symlink, and hardlink target to the roots derived from the path: patterns before extracting.

  • Absolute paths outside the roots, .. traversal, and escaping symlinks/hardlinks abort the restore before anything is written.
  • Entries within the requested paths restore unchanged.
  • The listing parser fails closed: a line it cannot understand refuses the restore rather than extracting blind.
  • glob.restoreRoots derives the allowlist from the literal prefix of each pattern (negations ignored; they only shrink what was saved).

How it was tested

  • make lint clean (actionlint/yq absent locally and skipped)
  • make test green (55 tests)
  • new behavior has a test: poisoned archives (absolute escape, .. traversal, escaping symlink) assert refusal with nothing written, plus unit coverage for the listing parser, the confinement check (hardlinks included), and root derivation
  • e2e workflow green on a runner

CHANGELOG updated? yes

**What changed** Restore extracts with `tar -xpP`, which trusts absolute member paths, so a poisoned cache object could write or link outside the requested cache paths (#3). Restore now enumerates the archive with `tar -tvP` and confines every member, symlink, and hardlink target to the roots derived from the `path:` patterns before extracting. - Absolute paths outside the roots, `..` traversal, and escaping symlinks/hardlinks abort the restore before anything is written. - Entries within the requested paths restore unchanged. - The listing parser fails closed: a line it cannot understand refuses the restore rather than extracting blind. - `glob.restoreRoots` derives the allowlist from the literal prefix of each pattern (negations ignored; they only shrink what was saved). **How it was tested** - [x] `make lint` clean (actionlint/yq absent locally and skipped) - [x] `make test` green (55 tests) - [x] new behavior has a test: poisoned archives (absolute escape, `..` traversal, escaping symlink) assert refusal with nothing written, plus unit coverage for the listing parser, the confinement check (hardlinks included), and root derivation - [ ] e2e workflow green on a runner **CHANGELOG updated?** yes
Confine cache restore to the requested paths (#3)
All checks were successful
test / unit (pull_request) Successful in 24s
test / e2e (pull_request) Successful in 5s
test / e2e-post-save (pull_request) Successful in 5s
f34bdf887b
Restore extracts with `tar -xpP`, which trusts absolute member paths, so a
poisoned cache object could write or link outside the requested cache paths.

Enumerate the archive with `tar -tvP` before extracting and confine every
member, symlink, and hardlink target to the roots derived from the `path:`
patterns. Absolute paths outside the roots, `..` traversal, and escaping links
abort the restore before anything is written; entries within the requested
paths restore unchanged. The listing parser fails closed on lines it cannot
understand.

Tests build poisoned archives (absolute escape, `..` traversal, escaping
symlink) and assert refusal with nothing written, plus unit coverage for the
listing parser, the confinement check (including hardlinks), and root
derivation.
Sign in to join this conversation.
No reviewers
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/cache-action!6
No description provided.