Skip to content

fix: prevent zip-slip/tar-slip path traversal in gcs-fetcher#1086

Open
mohammadmseet-hue wants to merge 1 commit intoGoogleCloudPlatform:masterfrom
mohammadmseet-hue:fix/gcs-fetcher-zip-slip
Open

fix: prevent zip-slip/tar-slip path traversal in gcs-fetcher#1086
mohammadmseet-hue wants to merge 1 commit intoGoogleCloudPlatform:masterfrom
mohammadmseet-hue:fix/gcs-fetcher-zip-slip

Conversation

@mohammadmseet-hue
Copy link
Copy Markdown

Summary

The archive extraction functions in gcs-fetcher/pkg/fetcher/fetcher.go use filepath.Join(dest, untrustedName) without verifying the result stays within the destination directory. Archive entries with ../ in the filename escape the extraction directory and write files to arbitrary paths.

Affected Code (3 vectors)

Vector Line Code Source of untrusted name
ZIP 777 filepath.Join(dest, file.Name) ZIP central directory
tar.gz 887 filepath.Join(gf.DestDir, h.Name) tar header
Manifest 289 filepath.Join(dest, j.filename) Manifest JSON

None of these validate that the resolved path remains under the base directory.

Why filepath.Join does NOT prevent this

filepath.Join("/workspace", "../../../etc/cron.d/evil")
// Returns: "/etc/cron.d/evil"  ← outside /workspace

filepath.Join cleans paths but does NOT constrain them. This is the well-known Zip Slip class (CVE-2018-1002200).

The Fix

Adds a safeJoin() function that validates the resolved path stays under the base directory using strings.HasPrefix after filepath.Clean. Applied to all 3 vectors.

This matches the pattern used in Google's own go-containerregistry (mutate.go:298-320).

Impact

gcs-fetcher runs as the first step in Google Cloud Build pipelines. A malicious source archive (zip/tar.gz) with traversal entries can write files outside /workspace/, overwriting build scripts or injecting code into downstream build steps that execute with the build service account's credentials.

The archive extraction functions in fetcher.go use filepath.Join(dest,
untrustedName) without verifying that the result stays within the
destination directory. An archive entry named "../../../etc/cron.d/evil"
resolves to /etc/cron.d/evil, escaping the extraction directory.

Three independent vectors are affected:
- unzip() line 777: ZIP entry file.Name
- fetchFromTarGz() line 887: tar header h.Name
- fetchObject() line 289: manifest j.filename

This adds a safeJoin() helper that validates the resolved path remains
under the base directory, matching the pattern used in Google's own
go-containerregistry (mutate.go:298-320).

Ref: CVE-2018-1002200 (Zip Slip class)
@google-cla
Copy link
Copy Markdown

google-cla Bot commented Mar 23, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@mohammadmseet-hue mohammadmseet-hue force-pushed the fix/gcs-fetcher-zip-slip branch from 94376fa to a674346 Compare March 25, 2026 18:14
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