Trust, But Verify (Your Paths): Inside the Sigstore Path Traversal
Jan 23, 2026·7 min read·3 visits
Executive Summary (TL;DR)
Sigstore's legacy TUF client failed to sanitize target names during disk caching. By crafting a malicious TUF repository with path traversal sequences in target filenames (e.g., `../../etc/passwd`), an attacker can break out of the cache directory and overwrite arbitrary files on the victim's system. Fixed in v1.10.4 by URL-encoding target paths.
A deep dive into a path traversal vulnerability in Sigstore's legacy TUF client that allows malicious repositories to overwrite arbitrary files on a client's machine.
The Hook: When Security Tools Become the Attack Vector
Sigstore has become the darling of the software supply chain security world. It’s the magic behind "keyless" signing, the transparency logs, and the general feeling that maybe, just maybe, we won't all get backdoor'd by a rogue NPM package tomorrow. Central to Sigstore's architecture is TUF (The Update Framework), a complex but robust specification designed to secure software updates against practically every attack vector imaginable—freeze attacks, rollback attacks, you name it.
But here's the irony: the very mechanism designed to verify the integrity of files—the TUF client—had a hole in its own integrity checks. Specifically, the legacy client implementation in sigstore/sigstore included a disk caching mechanism. Caching is great for performance, but it's a notorious breeding ground for security bugs. If you blindly trust the data coming from the remote server to tell you where to store a file, you're gonna have a bad time.
This vulnerability (CVE-2026-24137) is a classic "Zip Slip" style path traversal, lurking in a library that is literally designed to prevent tampering. It allows a malicious TUF repository (or a compromised one) to write files anywhere the client process has permissions. It’s like installing a high-security bank vault door but leaving the drywall next to it made of paper.
The Flaw: Go's `filepath.Join` is a Footgun
To understand this bug, you have to understand how Go handles file paths. The standard library function path/filepath.Join is deceptive. It sounds like it should just concatenate strings, but it actually performs a Clean operation on the result. This removes redundant separators and, crucially, resolves .. elements. This feature is useful for normalization, but fatal for security if you aren't paying attention.
In the vulnerable component, the diskCache struct takes a base directory (where cache files should live) and joins it with a p (the path/name of the target file derived from TUF metadata). The code looked something like filepath.Join(d.base, p). If d.base is /tmp/tuf-cache and p is ../../home/user/.ssh/authorized_keys, Go resolves this mathematically.
Instead of resulting in /tmp/tuf-cache/../../home/user/..., filepath.Join cleans it up to /home/user/.ssh/authorized_keys. The function did exactly what it was told to do: resolve the path. The failure wasn't in the function, but in the lack of a boundary check after the resolution. The code never asked, "Hey, is the resulting path still inside /tmp/tuf-cache?" It just assumed it was, handed the path to os.WriteFile, and let the filesystem take the hit.
The Code: A Tale of Missing Validation
Let's look at the smoking gun in pkg/tuf/client.go. The vulnerability existed in both the Get and Set methods of the diskCache implementation. Here is the vulnerable Set method, simplified for clarity:
// BEFORE: Vulnerable Code
func (d *diskCache) Set(p string, b []byte) error {
// p is controlled by the remote repository metadata
fp := filepath.FromSlash(filepath.Join(d.base, p))
// If p is "../../evil", fp becomes "/evil" (outside d.base)
if err := os.MkdirAll(filepath.Dir(fp), 0o700); err != nil {
return fmt.Errorf("creating targets dir: %w", err)
}
// Arbitrary file write occurs here
return os.WriteFile(fp, b, 0o600)
}The fix implemented in commit 8ec410a is elegantly simple. Instead of trying to implement complex path validation logic (which is prone to bypasses), the developers opted to neutralize the directory traversal characters entirely. They introduced a safePath helper that URL-encodes the target name before joining it.
// AFTER: Patched Code
func (d *diskCache) safePath(p string) string {
// url.PathEscape turns "/" into "%2F" and ".." into "%2E%2E"
return filepath.FromSlash(filepath.Join(d.base, url.PathEscape(p)))
}
func (d *diskCache) Set(p string, b []byte) error {
fp := d.safePath(p)
// ... rest of the logic
}By running url.PathEscape(p), a payload like ../../bin/sh becomes ..%2F..%2Fbin%2Fsh. When this is passed to the filesystem, it is treated as a single filename containing weird characters, rather than a directory traversal instruction. The file is safely written inside the cache directory, harmlessly named with percent signs.
The Exploit: Weaponizing Metadata
Exploiting this requires a specific position of power: you need to control the TUF repository metadata. In the public Sigstore infrastructure, this is incredibly difficult because it requires a quorum of signers to validate metadata. However, in private deployments or corporate mirrors—where one person might hold the keys to the kingdom—this is a viable attack vector.
The Attack Chain:
- Repo Setup: The attacker sets up a malicious TUF repository or compromises the signing keys for an existing one.
- Metadata Poisoning: The attacker generates a
targets.jsonfile. Instead of listing valid binaries, they insert a record where the target path is../../../../root/.ssh/authorized_keys. - Payload Delivery: The content/hash associated with this target is the attacker's public SSH key.
- The Trigger: The victim (a developer or CI/CD pipeline) runs a vulnerable version of
cosignor a tool usingsigstoreto verify an artifact. The tool initializes the TUF client, connects to the malicious repo, and sees a new target. - Execution: The client attempts to cache this "new target". The
diskCache.Setmethod triggers, the path traversal occurs, and the victim'sauthorized_keysfile is overwritten with the attacker's key.
Boom. The next time the victim checks their server, they're locked out, and the attacker has root access via SSH. No fancy memory corruption, just good old-fashioned logic abuse.
The Impact: From File Write to RCE
The CVSS score of 5.8 (Medium) might lull you into a false sense of security. "Oh, it requires high privileges (control of the repo), so it's fine." Don't fall for that trap. In supply chain security, the trust is the vulnerability. If a developer trusts a compromised repository, the impact is catastrophic.
While the primitive is "Arbitrary File Write," the consequence is almost always Remote Code Execution (RCE). If I can overwrite your files, I can:
- Overwrite binary executables in your
$PATH. - Overwrite
.bashrcor.zshrcto execute commands on your next login. - Overwrite configuration files for other services to weaken security.
The only thing saving the internet from a massive wormable event here is the requirement for the attacker to sign the metadata. This is a targeted assassination weapon, not a cluster bomb. It is designed to compromise specific clients consuming specific private repositories.
The Fix: Escaping the Trap
The remediation is straightforward: stop using the vulnerable code. If you are importing github.com/sigstore/sigstore in your Go project, you need to upgrade to v1.10.4 immediately. This version includes the patch that sanitizes paths before writing to disk.
If you cannot upgrade immediately, there is a configuration workaround. The legacy TUF client respects an environment variable that disables caching entirely. By setting:
export SIGSTORE_NO_CACHE=true
...you effectively bypass the vulnerable diskCache logic. The client will fetch metadata from the network every time (slower), but it won't try to write it to your disk using the broken logic (safer). Long term, developers should look at migrating to the newer, more robust TUF implementation in github.com/sigstore/sigstore-go, as the legacy client in the main repo is effectively on life support.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:N/I:H/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
github.com/sigstore/sigstore Sigstore | <= 1.10.3 | 1.10.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 (Path Traversal) |
| CVSS v3.1 | 5.8 (Medium) |
| Attack Vector | Network (Malicious Repo) |
| Impact | Arbitrary File Write / Integrity Loss |
| Exploit Status | PoC Available |
| Privileges Required | High (Repo Signing Key) |
MITRE ATT&CK Mapping
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.