Fix path traversal in local tar base-image manifest parsing (CWE-22)#4522
Conversation
|
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. |
There was a problem hiding this comment.
Code Review
This pull request introduces path traversal validation in LocalBaseImageSteps.java by resolving manifest-declared paths (such as the config file and layers) against the extraction directory and verifying they do not escape it. The review feedback suggests optimizing this validation by pre-computing the canonical destination path once and passing it to the resolution helper, rather than recalculating it repeatedly (especially inside loops). Additionally, it is recommended to make the helper method package-private and annotate it with @VisibleForTesting to facilitate unit testing.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…s/LocalBaseImageSteps.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…s/LocalBaseImageSteps.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…s/LocalBaseImageSteps.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…s/LocalBaseImageSteps.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Summary
Fixes a path traversal issue (CWE-22) in
LocalBaseImageSteps.cacheDockerImageTar, reported to Google's OSS VRP as issue 536528963 (closed there only because Jib's current reward tier makes product vulnerabilities ineligible for a monetary reward — the maintainers did not dispute the finding and invited a direct PR).The problem
When Jib containerizes using a local tar file as the base image (
Jib.from(TarImage.at(path)), or thetar://image reference), it parses the tar'smanifest.jsonand resolves theConfigandLayersstring fields directly against the extraction directory viadestination.resolve(...), with no path canonicalization or bounds-checking:LocalBaseImageSteps.java— theConfigpath resolutionLocalBaseImageSteps.java— the gzip-sniff of the first layerLocalBaseImageSteps.java— eachLayersentry inside the processing loopSince
manifest.jsonis part of the tar's own (potentially untrusted) contents, a crafted tar can set aLayersentry to an absolute path or a path containing../segments, causing Jib to read an arbitrary file elsewhere on the host filesystem, gzip-compress it, and embed it as a real layer blob in the output image — with no check that the content matches the declareddiffId. If that output image is later pushed to a registry, the file's contents are exposed.This mirrors the "Zip Slip" bug class, except the malicious path arrives via a manifest field trusted verbatim rather than an archive entry name. Notably, tar extraction in this same code path (
TarExtractor.extract) already defends against exactly this kind of escape — this fix brings the manifest-parsing step up to the same standard.The fix
Adds a
resolveManifestPath(Path destination, String manifestDeclaredName)helper that resolves the manifest-declared name and then canonicalizes and bounds-checks the result against the destination directory, throwingIOExceptionif it would escape — the same patternTarExtractor.extractalready uses for tar entry names. All three unguardedresolve()call sites now go through this helper.Testing
Manually verified with a proof-of-concept malicious tar whose
manifest.jsonLayersarray pointed at an absolute path to a file outside the tar. Before this fix, that file's contents were read and embedded in the output image. After this fix,cacheDockerImageTarthrowsIOException: Illegal file name in manifest.json, potential path traversal: ...instead.Happy to add a JUnit test alongside this if preferred — didn't want to guess at the project's existing test fixture conventions without seeing them first.