VYPR
Low severityOSV Advisory· Published Jan 26, 2026· Updated Jan 27, 2026

sigstore has CSRF possibility in OIDC authentication during signing

CVE-2026-24408

Description

sigstore-python is a Python tool for generating and verifying Sigstore signatures. Prior to version 4.2.0, the sigstore-python OAuth authentication flow is susceptible to Cross-Site Request Forgery. _OAuthSession creates a unique "state" and sends it as a parameter in the authentication request but the "state" in the server response seems not not be cross-checked with this value. Version 4.2.0 contains a patch for the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
sigstorePyPI
< 4.2.04.2.0

Affected products

1

Patches

1
5e77497fe8f0

Merge commit from fork

https://github.com/sigstore/sigstore-pythonJussi KukkonenJan 26, 2026via ghsa
4 files changed · +69 0
  • CHANGELOG.md+3 0 modified
    @@ -10,6 +10,9 @@ All versions prior to 0.9.0 are untracked.
     
     ### Fixed
     
    +* Add state validation to OIDC flow to prevent Cross-site request forgery
    +  during OIDC authorization
    +  ([GHSA-hm8f-75xx-w2vr](https://github.com/sigstore/sigstore-python/security/advisories/GHSA-hm8f-75xx-w2vr))
     * verification now ensures that artifact digest documented in bundle and the real digest match
       (this is a bundle consistency check: bundle signature was always verified over real digest)
       ([#1652](https://github.com/sigstore/sigstore-python/pull/1652))
    
  • sigstore/_internal/oidc/oauth.py+4 0 modified
    @@ -182,6 +182,10 @@ def __init__(self, client_id: str, client_secret: str, issuer: Issuer):
                 base64.urlsafe_b64encode(os.urandom(32)).rstrip(b"=").decode()
             )
     
    +    @property
    +    def state(self) -> str:
    +        return self._state
    +
         @property
         def code_challenge(self) -> str:
             return B64Str(
    
  • sigstore/oidc.py+4 0 modified
    @@ -312,6 +312,10 @@ def identity_token(  # nosec: B107
                         raise IdentityError(
                             f"Error response from auth endpoint: {auth_error[0]}"
                         )
    +
    +                if server.auth_response["state"][0] != server.oauth_session.state:
    +                    raise IdentityError("OAuth state mismatch")
    +
                     code = server.auth_response["code"][0]
                 else:
                     # In the out-of-band case, we wait until the user provides the code
    
  • test/unit/internal/oidc/test_issuer.py+58 0 modified
    @@ -12,6 +12,8 @@
     # See the License for the specific language governing permissions and
     # limitations under the License.
     
    +from unittest.mock import MagicMock, patch
    +
     import pytest
     
     from sigstore.oidc import IdentityError, Issuer, IssuerError
    @@ -34,3 +36,59 @@ def test_get_identity_token_bad_code(monkeypatch):
         monkeypatch.setattr("builtins.input", lambda _: "hunter2")
         with pytest.raises(IdentityError, match=r"^Token request failed with .+$"):
             Issuer("https://oauth2.sigstage.dev/auth").identity_token(force_oob=True)
    +
    +
    +def test_identity_token_csrf_protection():
    +    """
    +    Verify that identity_token() raises IdentityError when the returned state
    +    does not match the session state (CSRF protection).
    +    """
    +    with (
    +        patch("sigstore.oidc.webbrowser.open"),
    +        patch("sigstore._internal.oidc.oauth._OAuthFlow") as MockOAuthFlow,
    +        patch("sigstore.oidc.requests.Session") as MockSession,
    +        patch("sigstore.oidc.IdentityToken"),
    +    ):
    +        # Setup the mock server returned by _OAuthFlow context manager
    +        mock_server = MagicMock()
    +        MockOAuthFlow.return_value.__enter__.return_value = mock_server
    +
    +        # Simulate a mismatching state
    +        original_state = "original-secure-state"
    +        malicious_state = "malicious-state"
    +
    +        # The session has the original state (we mock the property access)
    +        # Since we added a property 'state', we need to make sure the mock returns it.
    +        # But here we are mocking the whole server object.
    +        # server.oauth_session.state
    +        mock_server.oauth_session.state = original_state
    +
    +        mock_server.is_oob.return_value = False
    +        mock_server.base_uri = "http://localhost:12345"
    +        mock_server.redirect_uri = "http://localhost:12345/callback"
    +
    +        # The auth response simulates what the redirect handler receives
    +        mock_server.auth_response = {
    +            "code": ["fake-code"],
    +            "state": [malicious_state],
    +        }
    +
    +        # Mock responses for Issuer initialization and token exchange
    +        mock_session_instance = MockSession.return_value
    +
    +        # Mock .well-known/openid-configuration response
    +        mock_config_response = MagicMock()
    +        mock_config_response.json.return_value = {
    +            "authorization_endpoint": "https://auth.example.com",
    +            "token_endpoint": "https://token.example.com",
    +        }
    +        mock_config_response.raise_for_status.return_value = None
    +
    +        mock_session_instance.get.side_effect = [mock_config_response]
    +
    +        # Initialize Issuer
    +        issuer = Issuer("https://issuer.example.com")
    +
    +        # Call identity_token() and expect IdentityError due to state mismatch
    +        with pytest.raises(IdentityError, match="OAuth state mismatch"):
    +            issuer.identity_token()
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.