CVE-2026-24408

Identity Crisis: Bypassing OIDC Checks in Sigstore Python (CVE-2026-24408)

Alon Barad
Alon Barad
Software Engineer

Jan 26, 2026·6 min read·6 visits

Executive Summary (TL;DR)

The `sigstore-python` client failed to verify the `state` parameter during OIDC authentication callbacks. This allowed attackers to perform a Localhost CSRF attack, feeding their own authorization codes to a victim's signing process. As a result, a victim could unknowingly sign code with an attacker's identity, creating falsified provenance records.

A critical flaw in the Sigstore Python client's OpenID Connect implementation allows attackers to force victims to sign software artifacts using the attacker's identity. This 'Login CSRF' vulnerability undermines the core promise of software supply chain provenance by allowing identity misbinding.

The Hook: When Trust is a One-Way Street

In the modern software supply chain, we have moved past simply asking 'is this binary not malware?' to asking 'who exactly built this binary?'. Sigstore has become the gold standard for answering that question, acting as a certificate authority that binds OpenID Connect (OIDC) identities (like your email) to cryptographic keys. It’s the digital notary of the open-source world.

But a notary is only as good as their ID verification process. CVE-2026-24408 is a vulnerability in sigstore-python—the official Python client for interacting with Sigstore—that effectively allows someone to swap the ID card just before the notary stamps the document.

This isn't a buffer overflow or a remote code execution exploit in the traditional sense. It's a logic flaw in the authentication handshake. Specifically, it's a "Login CSRF" (Cross-Site Request Forgery). By exploiting this, an attacker doesn't steal your keys; they force their keys into your hands. You do the work, you run the build, but the resulting signature claims it was done by them. It turns the concept of non-repudiation on its head.

The Flaw: OIDC State of Confusion

To understand this bug, you need to understand the OAuth 2.0 and OIDC dance. When you run sigstore sign, the tool spins up a temporary local web server (a callback listener) and opens your browser to an Identity Provider (IdP) like Google or GitHub. To prevent attacks where a malicious site forces you to log in, the protocol uses a state parameter—a random, unguessable string generated by the client.

Here is how it is supposed to work:

  1. Client generates state=XYZ123.
  2. Client sends user to IdP with ?state=XYZ123.
  3. IdP authenticates user and redirects back to client with ?code=AUTH_CODE&state=XYZ123.
  4. Client verifies that the returned state matches XYZ123.

If the state matches, the client knows the response corresponds to the request it just made. If it doesn't, someone is trying to interfere.

The developers of sigstore-python did steps 1, 2, and 3 perfectly. They generated a cryptographically strong state and sent it along. However, they completely forgot step 4. When the callback hit the local listener, the code simply grabbed the authorization code and proceeded to exchange it for a token. It treated the state parameter as decorative garnish rather than a security control.

This omission meant the client had no way to distinguish between a legitimate response from the user's browser and a forced request injected by a malicious script running in a background tab.

The Code: The Missing Link

The vulnerability resided in sigstore/oidc.py, specifically within the _OAuthSession handling. The code was parsing the callback parameters but lacked the crucial assertion logic.

Here is a reconstruction of the logic flow before the fix. Notice how the state is present in the response dictionary but never compared against the session's stored state:

# VULNERABLE CODE (Simplified)
class OAuthRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # ... parses query params into auth_response ...
        
        # The code blindly trusts the response
        # self.server.auth_response populated from URL params
        code = self.server.auth_response["code"][0]
        
        # Proceed to exchange code for token...

The fix, introduced in commit 5e77497fe8f0b202bdd118949074ec2f20da69aa, adds the necessary guardrail. It's a textbook example of defensive coding for OIDC:

# PATCHED CODE
# Check if the state returned by the IdP matches what we sent
if self.server.auth_response["state"][0] != self.server.oauth_session.state:
    raise IdentityError("OAuth state mismatch")
 
code = self.server.auth_response["code"][0]

By adding these two lines, the client now rejects any unsolicited authentication attempts. If the state doesn't match the one stored in oauth_session, the tool throws an IdentityError and aborts the signing process immediately.

The Exploit: Hijacking the Signing Ceremony

Exploiting this requires a bit of timing, but it is highly reproducible. The attack vector targets the ephemeral local web server that sigstore spins up to receive the OIDC callback.

The Setup:

  1. Attacker Preparation: The attacker starts an OIDC flow on their own machine but pauses right before the final redirect. They capture a valid authorization code (ATTACKER_CODE) tied to their own malicious account (e.g., attacker@evil.com).
  2. The Trap: The attacker hosts a malicious website containing JavaScript designed to hit http://localhost:[PORT]. Since sigstore often tries a predictable range of ports or the attacker can use aggressive port scanning via JS, finding the listener is feasible.

The Execution:

  1. The victim runs sigstore sign my-app.exe.
  2. sigstore opens the victim's browser to authenticate.
  3. While the victim is authenticating (or if they have other tabs open), they visit the attacker's site.
  4. The attacker's JavaScript fires a request to http://127.0.0.1:[PORT]/?code=ATTACKER_CODE&state=IRRELEVANT.

The Result: Because the vulnerable client ignores the state mismatch:

  1. The victim's sigstore client accepts ATTACKER_CODE.
  2. It exchanges this code for an ID token belonging to attacker@evil.com.
  3. The client generates a signing certificate for the attacker's identity.
  4. The victim's software (my-app.exe) is signed and uploaded to the Rekor transparency log, but the log entry says it was signed by attacker@evil.com.

This is a subtle sabotage. If the victim has automated policy checks that enforce "Binaries must be signed by victim@corp.com", the deployment will fail. Alternatively, this could be used for reputation attacks, making it look like the attacker is the author of the victim's software.

The Impact: Why This Matters

This vulnerability breaks the chain of custody. Sigstore is designed to provide non-falsifiable proof of authorship. By enabling identity misbinding, we introduce a mechanism to corrupt that proof.

While this doesn't allow an attacker to steal the victim's private key or sign malicious code as the victim (which would be a much higher severity issue), it causes Identity Confusion.

Consider an automated build pipeline where a developer signs an artifact before pushing to production. If an attacker can race the OIDC callback and inject their own identity, the artifact enters the pipeline with the wrong metadata. This leads to Denial of Service (if the pipeline rejects the signature) or, more insidiously, a false audit trail. If an attacker injects their identity into a malicious artifact on their own machine, that's normal. But forcing a trusted machine to sign trusted code with an untrusted identity creates chaos in provenance-based policy enforcement systems.

Mitigation: Closing the Loop

The remediation is straightforward: update the client. The logic flaw is entirely contained within the client-side library, so no server-side changes are required from the Sigstore infrastructure.

Immediate Action: Execute the following command to pull the patched version:

pip install --upgrade sigstore

Verification: Ensure you are running version 4.2.0 or higher:

pip show sigstore

For developers integrating sigstore-python as a library, ensure your requirements.txt or pyproject.toml pins the version to >=4.2.0. If you have built custom tooling around the OIDC flow, review your own code to ensure you are strictly validating the state parameter in any OAuth 2.0 implementation. This vulnerability serves as a potent reminder: cryptographic protocols are fragile; skipping even a single validation step can render the entire handshake insecure.

Fix Analysis (1)

Technical Appendix

CVSS Score
6.1/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:L
EPSS Probability
0.04%
Top 88% most exploited

Affected Systems

sigstore-python < 4.2.0Python applications importing sigstore.oidc

Affected Versions Detail

Product
Affected Versions
Fixed Version
sigstore-python
sigstore
< 4.2.04.2.0
AttributeDetail
CWE IDCWE-352 (CSRF)
CVSS v3.16.1 (Medium)
Attack VectorNetwork (Localhost CSRF)
ImpactIdentity Misbinding
EPSS Score0.00039
Exploit StatusPoC Available
CWE-352
Cross-Site Request Forgery (CSRF)

The application does not verify the integrity of the OIDC state parameter, allowing unauthorized authentication responses to be processed.

Vulnerability Timeline

Vulnerability Discovered
2026-01-26
Patch Developed & Merged
2026-01-26
Public Disclosure & Release 4.2.0
2026-01-26

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.