Skip to content

fix: validate tar entry paths against escaping the target directory in bundle Unpack - #2332

Open
pujitha24 wants to merge 2 commits into
shipwright-io:mainfrom
pujitha24:auto/issue-2322
Open

fix: validate tar entry paths against escaping the target directory in bundle Unpack#2332
pujitha24 wants to merge 2 commits into
shipwright-io:mainfrom
pujitha24:auto/issue-2322

Conversation

@pujitha24

@pujitha24 pujitha24 commented Sep 6, 2026

Copy link
Copy Markdown

Motivation:
pkg/bundle/bundle.go's Unpack function extracts tar entries from a
container image/OCI bundle and guarded against path traversal by
checking whether filepath.Join(targetPath, header.Name) contained the
literal substring "/../". filepath.Join calls filepath.Clean, which
resolves ".." segments before that check ever runs, so a tar entry
named e.g. "../../../../etc/passwd" is cleaned down to an absolute
path such as "/etc/passwd" that no longer contains "/../" and the
check never fires. A malicious bundle can therefore write files
anywhere the controller process has permission to write, entirely
outside the intended extraction directory.

Approach:
Replace the substring check with filepath.Rel(targetPath, target).
If the entry cannot be expressed as a path relative to targetPath
without a leading ".." component, the entry escapes the target
directory and is now rejected before anything is written to disk.
Tar entries handled by this function are limited to regular files and
directories (symlink entries already hit the "unsupported file type"
error), so this is the only path-construction site that needed
hardening here.

Validation:
go test ./pkg/bundle/... -race -v
-> all 5 specs pass, including two new table-driven cases that build
a raw tar stream with a "../canary" and a
"../../../../etc/canary" entry (the latter mirroring the exact
proof-of-concept from the report) and assert Unpack rejects both
and creates no file at the resolved escape path.
-> confirmed the new test fails against the old code (reverting only
bundle.go reproduces "Expected an error to have occurred. Got:
nil"), so it is a genuine regression test for this defect.
make build
-> succeeds.
golangci-lint run --timeout=10m ./pkg/bundle/...
-> 0 issues.

Report: #2322
Signed-off-by: Pujitha Paladugu 10557236+pujitha24@users.noreply.github.com
Assisted-by: claude-sonnet-5 (via Claude Code)

Fixes #2322

fix: validate tar entry paths against escaping the target directory in bundle Unpack

…n bundle Unpack

Motivation:
pkg/bundle/bundle.go's Unpack function extracts tar entries from a
container image/OCI bundle and guarded against path traversal by
checking whether filepath.Join(targetPath, header.Name) contained the
literal substring "/../". filepath.Join calls filepath.Clean, which
resolves ".." segments before that check ever runs, so a tar entry
named e.g. "../../../../etc/passwd" is cleaned down to an absolute
path such as "/etc/passwd" that no longer contains "/../" and the
check never fires. A malicious bundle can therefore write files
anywhere the controller process has permission to write, entirely
outside the intended extraction directory.

Approach:
Replace the substring check with filepath.Rel(targetPath, target).
If the entry cannot be expressed as a path relative to targetPath
without a leading ".." component, the entry escapes the target
directory and is now rejected before anything is written to disk.
Tar entries handled by this function are limited to regular files and
directories (symlink entries already hit the "unsupported file type"
error), so this is the only path-construction site that needed
hardening here.

Validation:
go test ./pkg/bundle/... -race -v
  -> all 5 specs pass, including two new table-driven cases that build
     a raw tar stream with a "../canary" and a
     "../../../../etc/canary" entry (the latter mirroring the exact
     proof-of-concept from the report) and assert Unpack rejects both
     and creates no file at the resolved escape path.
  -> confirmed the new test fails against the old code (reverting only
     bundle.go reproduces "Expected an error to have occurred. Got:
     nil"), so it is a genuine regression test for this defect.
make build
  -> succeeds.
golangci-lint run --timeout=10m ./pkg/bundle/...
  -> 0 issues.

Report: shipwright-io#2322
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Assisted-by: claude-sonnet-5 (via Claude Code)
@openshift-ci openshift-ci Bot added the release-note Label for when a PR has specified a release note label Sep 6, 2026
@pull-request-size pull-request-size Bot added the size/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Sep 6, 2026
@openshift-ci

openshift-ci Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign qu1queee for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kaizakin

kaizakin commented Sep 7, 2026

Copy link
Copy Markdown
Member

/ok-to-test

@openshift-ci openshift-ci Bot added the ok-to-test Indicates a non-member PR verified by an org member that is safe to test. label Sep 7, 2026
@kaizakin

kaizakin commented Sep 7, 2026

Copy link
Copy Markdown
Member

Hmm.. I wonder adding this label has no effect

@SaschaSchwarze0 SaschaSchwarze0 added the kind/bug Categorizes issue or PR as related to a bug. label Sep 7, 2026
@SaschaSchwarze0 SaschaSchwarze0 added this to the release-v0.22.0 milestone Sep 7, 2026
@SaschaSchwarze0
SaschaSchwarze0 requested a balanced review from Copilot September 7, 2026 09:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The deep traversal test relies on /etc/canary, making it environment-dependent and potentially unsafe.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Hardens bundle extraction against tar path traversal.

Changes:

  • Validates extraction paths with filepath.Rel.
  • Adds traversal regression tests.
File summaries
File Description
pkg/bundle/bundle.go Rejects paths escaping the target directory.
pkg/bundle/bundle_test.go Tests shallow and deep traversal attempts.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/bundle/bundle_test.go Outdated
Comment on lines +94 to +95
targetDir := filepath.Join(outerDir, "target")
Expect(os.Mkdir(targetDir, os.FileMode(0755))).To(Succeed())
@pujitha24

Copy link
Copy Markdown
Author

/retest

2 similar comments
@pujitha24

Copy link
Copy Markdown
Author

/retest

@pujitha24

Copy link
Copy Markdown
Author

/retest

Nest targetDir four levels under outerDir so the reported
"../../../../etc/canary" payload still escapes targetDir but
resolves inside the withTempDir sandbox instead of the host's real
/etc, per Copilot review feedback.

Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/bug Categorizes issue or PR as related to a bug. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. release-note Label for when a PR has specified a release note size/M Denotes a PR that changes 30-99 lines, ignoring generated files.

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Path traversal in bundle tar extraction allows arbitrary file writes

5 participants