Jan 26, 2026·6 min read·9 visits
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.
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.
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:
state=XYZ123.?state=XYZ123.?code=AUTH_CODE&state=XYZ123.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 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.
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:
ATTACKER_CODE) tied to their own malicious account (e.g., attacker@evil.com).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:
sigstore sign my-app.exe.sigstore opens the victim's browser to authenticate.http://127.0.0.1:[PORT]/?code=ATTACKER_CODE&state=IRRELEVANT.The Result:
Because the vulnerable client ignores the state mismatch:
sigstore client accepts ATTACKER_CODE.attacker@evil.com.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.
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.
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 sigstoreVerification: Ensure you are running version 4.2.0 or higher:
pip show sigstoreFor 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.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
sigstore-python sigstore | < 4.2.0 | 4.2.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-352 (CSRF) |
| CVSS v3.1 | 6.1 (Medium) |
| Attack Vector | Network (Localhost CSRF) |
| Impact | Identity Misbinding |
| EPSS Score | 0.00039 |
| Exploit Status | PoC Available |
The application does not verify the integrity of the OIDC state parameter, allowing unauthorized authentication responses to be processed.