Blind Trust: The Cosign Bundle Swap (CVE-2026-22703)
Jan 13, 2026·7 min read
Executive Summary (TL;DR)
Cosign verification logic had a massive loophole: if the Transparency Log entry (Rekor) looked valid, it stopped checking anything else. Attackers could sign malware, grab a log entry from a legitimate project (like Kubernetes), and staple them together. Cosign would see the valid log entry, give a thumbs up, and fail to notice that the log entry didn't match the malware. Fixed in 2.6.2 and 3.0.4.
A critical logic flaw in Sigstore Cosign allowed attackers to decouple transparency log entries from their artifacts. By exploiting a premature return statement in the verification logic, an attacker with a valid signing key could attach a stolen, valid Rekor log entry from a high-trust project to their own malicious artifact, effectively bypassing transparency audits and fooling the verification engine.
The Hook: The Notary with Amnesia
In the modern software supply chain, we have built a religion around "Transparency." We don't just trust signatures anymore; we demand proof that the signature was recorded in a public, immutable ledger. Enter Sigstore and its transparency log, Rekor. The promise is simple: if it's signed, it's logged. If it's logged, everyone can see it. It creates a "paper trail" that prevents developers (or attackers who stole their keys) from signing malicious code in the dark.
Cosign is the tool we use to verify these promises. It checks the signature, checks the artifact, and checks the log entry. Ideally, it ensures that all three of these things form a tight, cryptographic trinity. A binds to B, B binds to C.
But prior to version 2.6.2, Cosign developed a severe case of amnesia. It would look at the log entry, admire its cryptographic beauty, verify that the log itself signed the entry, and then... just go home. It forgot to check if the log entry actually belonged to the artifact in front of it.
This is the digital equivalent of a bouncer checking your ID, seeing that it is a valid government-issued driver's license, and letting you into the club—completely ignoring the fact that the photo on the license is of a 60-year-old woman named Margaret, and you are a 20-year-old dude named Steve. The document was valid; the binding to the holder was broken.
The Flaw: Premature verification
The vulnerability lies in a classic programming blunder: the premature return. In security engineering, order of operations is everything. You typically want to perform the cheapest checks first (syntax) and the most critical checks last (semantics/trust).
In pkg/cosign/verify.go, the VerifyBundle function is responsible for ensuring a bundle (which contains the signature and the log entry) is valid. The function had a specific block of code designed to handle TrustedMaterial—basically, when the user provides a root of trust to verify the Rekor log against.
Here is where the logic fell apart. The code verified the Signed Entry Timestamp (SET). The SET is a promise from Rekor saying, "Yes, I received this entry at this time." If the SET verified successfully against the trusted root, the function immediately returned true.
True. No errors. Success.
The problem? The code responsible for checking if the content of that SET matched the signature on the artifact was located after that return statement (or was bypassed by the flow). The function effectively said: "Is this a valid receipt from Rekor? Yes? Okay, you're good." It never asked: "Is this receipt actually for the item you just bought?"
The Code: The Smoking Gun
Let's look at the diff, because nothing explains a vulnerability better than the patch required to fix it. The vulnerable code in VerifyBundle looked something like this (simplified for clarity):
// VULNERABLE CODE FLOW
func VerifyBundle(sig, co) {
// ... setup ...
if co.TrustedMaterial != nil {
// Check if the Rekor entry is cryptographically valid
if err := tlog.VerifySET(entry, co.TrustedMaterial.RekorLogs()); err != nil {
return false, err
}
// CRITICAL FLAW: returning early without checking content binding
return true, nil
}
// ... comparisons of signatures and public keys happened down here ...
if err := compareSigs(...); err != nil { return err }
}See that return true, nil? That is the trap door. If you provided a valid SET, you skipped compareSigs and comparePublicKey.
The fix involved hoisting the consistency checks to the very top of the function, ensuring they run before any trust anchors are evaluated:
// PATCHED CODE FLOW
func VerifyBundle(sig, co) {
// FIXED: Verify the binding first!
if err := compareSigs(bundle.Payload.Body, sig); err != nil {
return false, err
}
if err := comparePublicKey(bundle.Payload.Body, sig, co); err != nil {
return false, err
}
// Now we can check the trust anchor
if co.TrustedMaterial != nil {
if err := tlog.VerifySET(...); err != nil {
return false, err
}
return true, nil
}
}By moving the checks up, the developers ensured that even if the log entry is valid, it must mathematically match the artifact signature before the function can return success.
The Exploit: Bundle Swapping
This vulnerability enables a "Bundle Swap" attack. To pull this off, you need to be an attacker who has compromised a signing key (or you are a malicious insider), but you don't want your malicious activity to appear in the transparency log where security teams might audit it.
Here is the play-by-play execution:
- The Setup: You have a compromised private key for a project. You want to sign a backdoored version of the application.
- The Theft: You query the public Rekor log for a completely innocent, high-reputation entry. Let's say you find the log entry for the latest official Kubernetes release. You download that bundle.
- The Switch: You sign your backdoored binary with the compromised key. Then, you construct a Cosign bundle. inside this bundle, you place your signature, but you embed the Kubernetes Rekor entry.
- The Bypass: You send this bundle to the victim. The victim runs
cosign verify --trusted-root ....
Cosign unpacks the bundle. It looks at the Rekor entry. It checks the signature on the entry against the Rekor public keys. It validates perfectly (because it is a valid entry for Kubernetes!). Cosign hits the return true line and reports the artifact as "Verified".
The victim thinks the software is safe and logged. In reality, the software is malicious, and the log entry points to a completely different file. The audit trail is broken.
The Impact: Why we should panic (mildly)
The CVSS score is a 5.5 (Medium), which might make you roll your eyes. "Only a medium?" That's because the attacker still needs a valid signing key to generate the signature on the artifact itself. This isn't a magic bypass that lets anyone sign anything.
However, in the context of Supply Chain Security, this is catastrophic. The entire value proposition of Sigstore is non-repudiation and discoverability.
If an attacker compromises your CI/CD pipeline keys (a very common scenario), they usually have to worry about the Rekor log. If they sign malware, it gets logged. Security researchers monitoring the log will see "Acme Corp signed 'evil.exe'" and raise the alarm.
With CVE-2026-22703, the attacker can use your stolen keys to sign malware without creating a new log entry. They can reuse an old entry. Your security monitors will verify the bundle, see it's "logged," but the log entry they are verifying effectively points to empty air (or someone else's code). It blinds the very mechanism designed to catch compromised keys.
The Fix: Putting the cart behind the horse
Remediation is straightforward: Update Cosign. The patched versions are 2.6.2 and 3.0.4.
If you are a library consumer using github.com/sigstore/cosign/v2, you need to bump your go.mod immediately. The patch is tiny but critical. It simply enforces that compareSigs runs before the function exits.
[!NOTE] For Policy Enforcers: If you are using policy engines like Kyverno or Gatekeeper that rely on Cosign libraries under the hood, you must ensure the underlying image or library is updated. A policy engine running the vulnerable version of Cosign will admit these "Frankenstein" bundles into your cluster without hesitation.
For researchers looking to bypass the fix: The new logic is strict. It forces a byte-for-byte comparison of the base64-decoded body of the Rekor entry against the signature provided. Unless you can find a hash collision in the artifact digest (good luck with SHA256) or break the ECDSA signature verification itself, the bundle swap path is closed.
Fix Analysis (2)
Technical Appendix
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
cosign Sigstore | < 2.6.2 | 2.6.2 |
cosign Sigstore | < 3.0.4 | 3.0.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-345 |
| Attack Vector | Local (requires crafted bundle) |
| CVSS Score | 5.5 (Medium) |
| Impact | Integrity Verification Bypass |
| Exploit Status | Proof of Concept (Theoretical) |
| EPSS Score | 0.00004 |
MITRE ATT&CK Mapping
The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data.
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.