Skip to content

Harden archive path handling - #419

Merged
stuartwdouglas merged 1 commit into
block:fix-reject-path-traversalfrom
stuartwdouglas:fix-reject-path-traversal-archive
Oct 29, 2026
Merged

stuartwdouglas merged 1 commit into
block:fix-reject-path-traversalfrom
stuartwdouglas:fix-reject-path-traversal-archive

Conversation

@stuartwdouglas

Copy link
Copy Markdown
Contributor

No description provided.

@stuartwdouglas
stuartwdouglas requested a review from a team as a code owner October 29, 2026 01:08
@stuartwdouglas
stuartwdouglas requested review from worstell and removed request for a team October 29, 2026 01:08
@stuartwdouglas
stuartwdouglas merged commit 0bf817e into block:fix-reject-path-traversal Oct 29, 2026
3 of 5 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T01:22:42.667064Z 1026461 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1026461094

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/archive.go
if err != nil {
return err
}
if err := rejectSymlink(linkTarget); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject symlinked parents of hard-link sources

When extracting into an existing tree containing pivot -> /outside, an archive hard-link entry whose Linkname is pivot/secret passes safeArchivePath and this check because Lstat follows the parent symlink and sees the outside regular file; os.Link then creates an in-tree hard link to that outside inode. A later build step that writes the restored path can therefore modify a file outside directory, so validate every source component or restrict hard links to previously extracted in-root regular entries.

Useful? React with 👍 / 👎.

Comment thread client/archive.go
return errors.Errorf("not a directory: %s", baseDir)
}
for _, path := range includePaths {
if strings.Contains(path, "\\") || !filepath.IsLocal(path) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject include paths through symlinked parents

When baseDir/link is a symlink to an outside directory, link/secret is lexically local and passes this check; os.Stat and the Linux GNU tar 1.35 command used below resolve the intermediate symlink, archiving the outside file as link/secret. Since this cache can back onto remote instances, the containment check can still leak files outside baseDir; walk the parent components with Lstat before invoking tar (while allowing a final symlink if desired).

AGENTS.md reference: AGENTS.md:L1-L5

Useful? React with 👍 / 👎.

Comment thread client/archive.go
}
*dirs = append(*dirs, dirMode{path: target, mode: mode})
case tar.TypeReg, 0:
return extractRegularFile(directory, rel, target, mode, r)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore archived modification times

When cached artifacts are consumed by timestamp-sensitive tools such as Make, Ninja, or Git's index, this regular-file branch forwards only mode and contents, so each new file gets extraction time instead of hdr.ModTime. In the Linux GNU tar 1.35 path replaced here, -m/--touch is documented as “don't extract file modified time” and was not passed, so this changes every restored mtime and can trigger rebuilds or incorrect freshness decisions; apply the header mtime after writing.

AGENTS.md reference: AGENTS.md:L1-L2

Useful? React with 👍 / 👎.

Comment thread client/archive.go
Comment on lines +125 to +126
case errors.Is(err, io.EOF):
return applyDirModes(dirs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject EOF before any tar framing

When a cache object is a valid zstd frame whose decompressed payload is empty (or only one truncated zero block), archive/tar.Reader.Next returns io.EOF immediately and this branch reports success, so Client.Restore can report a hit while restoring nothing. The previous Linux GNU tar 1.35 extraction rejects zero-byte input as not a tar archive; track decompressed framing or byte count and distinguish a real tar terminator from EOF before any framing.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant