Feb 15, 2026·6 min read·4 visits
The go-tuf library failed to enforce a minimum signature threshold of 1. By setting a delegation threshold to 0, a compromised repository or malicious insider could force clients to accept unsigned, malicious metadata and artifacts, completely bypassing the framework's integrity protections.
A critical logic flaw in the go-tuf library allowed The Update Framework (TUF) repositories to bypass signature verification by setting the delegation threshold to zero. This effectively turned the 'paranoia-first' security model into an open door for compromised or malicious updates.
The Update Framework (TUF) is the gold standard for software supply chain security. It is built on a foundation of extreme paranoia. It assumes your repository will be hacked, your keys will be stolen, and your network is hostile. It uses a complex hierarchy of cryptographic delegations to ensure that even if an attacker compromises a specific role, the damage is contained.
But security is a chain, and in CVE-2026-23992, that chain snapped because of a single integer. The vulnerability lies in go-tuf, a popular Go implementation of this framework. For all its cryptographic complexity—key rotation, snapshot isolation, timestamp freshness—the library forgot the most basic rule of access control: you actually have to ask for ID.
Through a logic flaw that feels almost too simple to be true, a repository could tell a client, 'Hey, for this critical software update, I require exactly zero signatures to verify it.' And the client, following its code to the letter, would nod, say 'Zero is greater than or equal to zero,' and happily install malware. It’s the digital equivalent of a bank vault that unlocks if you simply promise you aren't a robber.
To understand this bug, you have to look at how TUF handles Delegations. In TUF, the Targets role can delegate trust to other roles (e.g., packages/linux or packages/windows). Each delegation comes with a policy: 'You trust this role if it is signed by N of these M keys.' This N is the threshold.
The logic inside go-tuf's verification function was designed to count valid signatures and compare them against this threshold. The pseudo-code logic looked effectively like this:
if valid_signatures_count >= configured_threshold {
return success
}Do you see the problem? In Go, integers default to 0. If a delegation is misconfigured, or maliciously crafted to have a threshold of 0, the equation becomes:
if 0 >= 0 { return success }
The check passes immediately. The function doesn't care that no keys were checked. It doesn't care that the payload is radioactive. It sees that the requirement (zero) has been met (with zero signatures), and it opens the gate. This is a classic CWE-347 (Improper Verification of Cryptographic Signature), but with a twist: the verification logic itself was sound, but the boundary constraints on the configuration were non-existent.
Let's look at the actual code change in metadata/metadata.go. The fix is embarrassingly simple, highlighting just how fragile logic can be. The developers had to explicitly tell the computer that 'zero' is not a valid number of signatures.
Here is the vulnerable logic flow before the patch:
// BEFORE: The naive check
func (meta *Metadata[T]) VerifyDelegate(...) error {
// ... key lookup logic ...
// If roleThreshold was 0 in the JSON, we skipped straight to success
// implicitly later in the loop or logic flow depending on implementation details.
// The critical missing piece was a sanity check on the inputs.
}And here is the patch (Commit b38d91fdbc...):
// AFTER: The sanity check
func (meta *Metadata[T]) VerifyDelegate(delegatedRole string, delegatedMetadata ...) error {
// ...
if len(roleKeyIDs) == 0 {
return &ErrValue{Msg: fmt.Sprintf("no delegation found for %s", delegatedRole)}
}
// THE FIX: Explicitly forbid thresholds < 1
if roleThreshold < 1 {
return &ErrValue{Msg: fmt.Sprintf("insufficient threshold (%d) configured for %s",
roleThreshold,
delegatedRole)}
}
// ...
}Without those four lines of code, the library assumed that a threshold would always be positive. It's a reminder that int types in Go (and many languages) are dangerous if you don't treat 0 as a hostile value in security contexts.
How would an attacker weaponize this? This isn't a remote code execution you fire at a server; it's a supply chain attack vector. The attacker needs to influence the metadata the client consumes. This requires either a compromise of the repository (to modify targets.json) or a Man-in-the-Middle (MitM) attack if the initial root of trust allows it (though TUF is resilient to MitM usually, this bug breaks that resilience).
The Attack Chain:
targets.json (or a delegated parent).role: "malicious-updates") and explicitly sets "threshold": 0.0.The CVSS score is 5.9 (Medium), primarily because the Attack Complexity is rated as High (AC:H). The reasoning is that you need to modify the repository metadata first. But don't let the score fool you into complacency.
In a TUF ecosystem, the assumption is that keys will be compromised. The framework is designed so that if a lower-level key is stolen, the impact is limited. However, this vulnerability allows a partial compromise to become total. If an attacker steals a key for a delegated role, they can use this bug to create infinite sub-delegations that require no keys at all.
It destroys the property of Surviving Compromise. If a repository administrator accidentally sets a threshold to 0 (misconfiguration), or if a rogue insider does it, every client connected to that repo is instantly vulnerable to any arbitrary payload. There is no cryptographic backstop. The safety net has a hole in it exactly the size of the entire ocean.
The fix is straightforward: Update go-tuf to version 2.3.1 immediately.
If you are maintaining a TUF repository, you should also run an audit on your metadata. Grep your targets.json and all delegated metadata files for "threshold": 0. If you find any, you have a misconfiguration (or an active compromise) that needs immediate rectification.
For developers using go-tuf or similar libraries: never assume valid input. In the world of cryptography, 0 is rarely a valid number for keys, thresholds, or salt lengths. Treat 0 as an error state unless proven otherwise.
> [!NOTE]
> If you are using go-securesystemslib, ensure it is updated to v0.10.0+ as well, as it shares similar logic patterns.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
go-tuf theupdateframework | >= 2.0.0, < 2.3.1 | 2.3.1 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-23992 |
| CWE ID | CWE-347 (Improper Verification of Cryptographic Signature) |
| CVSS Score | 5.9 (Medium) |
| Attack Vector | Network |
| Impact | Integrity Violation / Security Bypass |
| Exploit Maturity | PoC Available |
The product does not verify, or incorrectly verifies, the cryptographic signature for data, allowing the integrity of the data to be compromised.