Skip to content

x-pack/osquerybeat: prevent path traversal in .pkg CPIO extraction - #51446

Merged
marc-gr merged 5 commits into
elastic:mainfrom
marc-gr:osquerybeat-pkg-path-traversal
Jul 21, 2026
Merged

x-pack/osquerybeat: prevent path traversal in .pkg CPIO extraction#51446
marc-gr merged 5 commits into
elastic:mainfrom
marc-gr:osquerybeat-pkg-path-traversal

Conversation

@marc-gr

@marc-gr marc-gr commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Proposed commit message

The macOS .pkg installer for osquery is extracted by expandPayload() in x-pack/osquerybeat/internal/pkgutil/expand.go, which reads CPIO entries and joins each entry's FilePath directly onto the destination directory. The entry name comes unsanitized from the archive, so a crafted .pkg containing ../ components could write files outside the destination directory (CWE-22). This path is only reachable on macOS, since .pkg/CPIO is the macOS osquery distribution format (Linux uses tar.gz, Windows uses msi/zip), and the sibling tar and zip extractors already validate entry paths — this brought CPIO extraction in line with them.

WHAT: Reject any entry whose name is not local using filepath.IsLocal before joining it with the destination directory, returning an error for traversal attempts.

WHY: Prevents arbitrary file write outside the intended directory from a malicious archive. filepath.IsLocal is the purpose-built stdlib check; it also rejects absolute paths and (on Windows) reserved names, making it stricter than a manual prefix comparison while keeping the intent self-documenting.

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have made corresponding change to the default configuration files
  • I have added tests that prove my fix is effective or that my feature works. Where relevant, I have used the stresstest.sh script to run them under stress conditions and race detector to verify their stability.
  • I have added an entry in ./changelog/fragments using the changelog tool.

Disruptive User Impact

None. Well-formed osquery .pkg artifacts continue to extract unchanged; only entries whose paths escape the destination directory are now rejected.

How to test this PR locally

cd x-pack/osquerybeat
go test ./internal/pkgutil/...

The new tests in internal/pkgutil/expand_test.go cover normal extraction plus several path-traversal attempts (../escape.txt, ../../escape-dir, a/../../escape.txt), asserting extraction fails and nothing is written outside the destination directory.

Related issues

  • Relates elastic/security#11747

Use cases

Extraction of osquery's macOS .pkg distribution by osquerybeat during install/update.

The macOS .pkg installer for osquery is extracted by expandPayload() in
pkgutil/expand.go, which reads CPIO entries and joins each entry's
FilePath directly onto the destination directory. The entry name comes
unsanitized from the archive, so a crafted .pkg containing "../"
components could write files outside the destination directory (CWE-22).
This was inconsistent with the sibling tar and zip extractors, which
already validate entry paths.

Reject any entry whose name is not local using filepath.IsLocal before
joining it with the destination directory. IsLocal also rejects absolute
paths and (on Windows) reserved names, making it stricter than a manual
prefix check while keeping the intent obvious.

Add unit tests covering normal extraction and several path-traversal
attempts to lock in the behavior.

Assisted-By: Cursor
@marc-gr
marc-gr requested a review from a team as a code owner June 23, 2026 10:22
@botelastic botelastic Bot added the needs_team Indicates that the issue/PR needs a Team:* label label Jun 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 GitHub comments

Just comment with:

  • run docs-build : Re-trigger the docs validation. (use unformatted text in the comment!)
  • /test : Run the Buildkite pipeline.

@mergify

mergify Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

This pull request does not have a backport label.
If this is a bug or security fix, could you label this PR @marc-gr? 🙏.
For such, you'll need to label your PR with:

  • The upcoming major version of the Elastic Stack
  • The upcoming minor version of the Elastic Stack (if you're not pushing a breaking change)

To fixup this pull request, you need to add the backport labels for the needed
branches, such as:

  • backport-8./d is the label to automatically backport to the 8./d branch. /d is the digit
  • backport-active-all is the label that automatically backports to all active branches.
  • backport-active-8 is the label that automatically backports to all active minor branches for the 8 major.
  • backport-active-9 is the label that automatically backports to all active minor branches for the 9 major.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: bae8bc05-cdb0-4a54-a487-447757c0d296

📥 Commits

Reviewing files that changed from the base of the PR and between a168024 and f3f2d3a.

📒 Files selected for processing (3)
  • changelog/fragments/1782484560-osquerybeat-pkg-path-traversal.yaml
  • x-pack/osquerybeat/internal/pkgutil/expand.go
  • x-pack/osquerybeat/internal/pkgutil/expand_test.go

📝 Walkthrough

Walkthrough

expandPayload in x-pack/osquerybeat/internal/pkgutil/expand.go now calls filepath.IsLocal on each CPIO entry's FilePath before extraction; non-local paths return a formatted error. The full destination path is precomputed once and passed directly to writeFile, whose signature changes from (dstDir string, entry *cpio.Entry) to (fp string, entry *cpio.Entry). A new test file adds helpers to construct gzip-wrapped odc-cpio payloads and two test cases: one for normal extraction, one asserting traversal paths are rejected and nothing escapes the destination. A security changelog fragment records the CWE-22 fix scoped to osquerybeat.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • 🛠️ Update Documentation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@marc-gr marc-gr added macOS Enable builds in the CI for darwin testing Osquerybeat bugfix Team:Security-Windows Platform Windows Platform Team in Security Solution labels Jun 23, 2026
@botelastic botelastic Bot removed the needs_team Indicates that the issue/PR needs a Team:* label label Jun 23, 2026
@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

Pinging @elastic/sec-windows-platform (Team:Security-Windows Platform)

@marc-gr marc-gr added the backport-active-all Automated backport with mergify to all the active branches label Jun 23, 2026
Comment thread x-pack/osquerybeat/internal/pkgutil/expand_test.go Outdated
Comment thread x-pack/osquerybeat/internal/pkgutil/expand_test.go
marc-gr added 2 commits July 6, 2026 09:42
Switch the cpio header helper to fmt.Fprintf(&buf, ...) to satisfy
staticcheck QF1012, and add an absolute-path (/escape.txt) traversal
case to round out the coverage.

Assisted-By: Cursor
@marc-gr
marc-gr requested a review from andrewkroh July 6, 2026 08:10
@mergify

mergify Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@marc-gr
marc-gr merged commit 5041330 into elastic:main Jul 21, 2026
29 checks passed
@marc-gr
marc-gr deleted the osquerybeat-pkg-path-traversal branch July 21, 2026 10:04
@github-actions

Copy link
Copy Markdown
Contributor

@Mergifyio backport 9.5 9.4 9.3 8.19

@mergify

mergify Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

marc-gr added a commit that referenced this pull request Jul 21, 2026
…51446) (#52130)

* x-pack/osquerybeat: prevent path traversal in .pkg CPIO extraction

The macOS .pkg installer for osquery is extracted by expandPayload() in
pkgutil/expand.go, which reads CPIO entries and joins each entry's
FilePath directly onto the destination directory. The entry name comes
unsanitized from the archive, so a crafted .pkg containing "../"
components could write files outside the destination directory (CWE-22).
This was inconsistent with the sibling tar and zip extractors, which
already validate entry paths.

Reject any entry whose name is not local using filepath.IsLocal before
joining it with the destination directory. IsLocal also rejects absolute
paths and (on Windows) reserved names, making it stricter than a manual
prefix check while keeping the intent obvious.

Add unit tests covering normal extraction and several path-traversal
attempts to lock in the behavior.

Assisted-By: Cursor

* osquerybeat: address review feedback on pkg traversal test

Switch the cpio header helper to fmt.Fprintf(&buf, ...) to satisfy
staticcheck QF1012, and add an absolute-path (/escape.txt) traversal
case to round out the coverage.

Assisted-By: Cursor
(cherry picked from commit 5041330)

Co-authored-by: Marc Guasch <marc-gr@users.noreply.github.com>
marc-gr added a commit that referenced this pull request Jul 21, 2026
…51446) (#52129)

* x-pack/osquerybeat: prevent path traversal in .pkg CPIO extraction

The macOS .pkg installer for osquery is extracted by expandPayload() in
pkgutil/expand.go, which reads CPIO entries and joins each entry's
FilePath directly onto the destination directory. The entry name comes
unsanitized from the archive, so a crafted .pkg containing "../"
components could write files outside the destination directory (CWE-22).
This was inconsistent with the sibling tar and zip extractors, which
already validate entry paths.

Reject any entry whose name is not local using filepath.IsLocal before
joining it with the destination directory. IsLocal also rejects absolute
paths and (on Windows) reserved names, making it stricter than a manual
prefix check while keeping the intent obvious.

Add unit tests covering normal extraction and several path-traversal
attempts to lock in the behavior.

Assisted-By: Cursor

* osquerybeat: address review feedback on pkg traversal test

Switch the cpio header helper to fmt.Fprintf(&buf, ...) to satisfy
staticcheck QF1012, and add an absolute-path (/escape.txt) traversal
case to round out the coverage.

Assisted-By: Cursor
(cherry picked from commit 5041330)

Co-authored-by: Marc Guasch <marc-gr@users.noreply.github.com>
marc-gr added a commit that referenced this pull request Jul 21, 2026
…51446) (#52127)

* x-pack/osquerybeat: prevent path traversal in .pkg CPIO extraction

The macOS .pkg installer for osquery is extracted by expandPayload() in
pkgutil/expand.go, which reads CPIO entries and joins each entry's
FilePath directly onto the destination directory. The entry name comes
unsanitized from the archive, so a crafted .pkg containing "../"
components could write files outside the destination directory (CWE-22).
This was inconsistent with the sibling tar and zip extractors, which
already validate entry paths.

Reject any entry whose name is not local using filepath.IsLocal before
joining it with the destination directory. IsLocal also rejects absolute
paths and (on Windows) reserved names, making it stricter than a manual
prefix check while keeping the intent obvious.

Add unit tests covering normal extraction and several path-traversal
attempts to lock in the behavior.

Assisted-By: Cursor

* osquerybeat: address review feedback on pkg traversal test

Switch the cpio header helper to fmt.Fprintf(&buf, ...) to satisfy
staticcheck QF1012, and add an absolute-path (/escape.txt) traversal
case to round out the coverage.

Assisted-By: Cursor
(cherry picked from commit 5041330)

Co-authored-by: Marc Guasch <marc-gr@users.noreply.github.com>
marc-gr added a commit that referenced this pull request Jul 21, 2026
…51446) (#52128)

* x-pack/osquerybeat: prevent path traversal in .pkg CPIO extraction

The macOS .pkg installer for osquery is extracted by expandPayload() in
pkgutil/expand.go, which reads CPIO entries and joins each entry's
FilePath directly onto the destination directory. The entry name comes
unsanitized from the archive, so a crafted .pkg containing "../"
components could write files outside the destination directory (CWE-22).
This was inconsistent with the sibling tar and zip extractors, which
already validate entry paths.

Reject any entry whose name is not local using filepath.IsLocal before
joining it with the destination directory. IsLocal also rejects absolute
paths and (on Windows) reserved names, making it stricter than a manual
prefix check while keeping the intent obvious.

Add unit tests covering normal extraction and several path-traversal
attempts to lock in the behavior.

Assisted-By: Cursor

* osquerybeat: address review feedback on pkg traversal test

Switch the cpio header helper to fmt.Fprintf(&buf, ...) to satisfy
staticcheck QF1012, and add an absolute-path (/escape.txt) traversal
case to round out the coverage.

Assisted-By: Cursor
(cherry picked from commit 5041330)

Co-authored-by: Marc Guasch <marc-gr@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-active-all Automated backport with mergify to all the active branches bugfix macOS Enable builds in the CI for darwin testing Osquerybeat Team:Security-Windows Platform Windows Platform Team in Security Solution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants