From fcd9011f8d02d30d5f80db1749cbeb8f2d1b3fc3 Mon Sep 17 00:00:00 2001 From: JunghwanNA <70629228+shaun0927@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:26:10 +0900 Subject: [PATCH] fix(security): separate OAuth PKCE state from code_verifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PKCE flow reused the code_verifier as the OAuth state parameter. Per RFC 6749 §10.12 and RFC 7636, these serve different purposes: state is an anti-CSRF token visible in the authorization URL; the code_verifier must remain secret for the token exchange. Generate an independent secrets.token_urlsafe(32) for state and validate it on callback to provide actual CSRF protection. Closes #10693 --- agent/anthropic_adapter.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index 4b1134a4c0b..ccb61dc813e 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -1064,6 +1064,8 @@ def run_hermes_oauth_login_pure() -> Optional[Dict[str, Any]]: import webbrowser verifier, challenge = _generate_pkce() + import secrets as _secrets + oauth_state = _secrets.token_urlsafe(32) params = { "code": "true", @@ -1073,7 +1075,7 @@ def run_hermes_oauth_login_pure() -> Optional[Dict[str, Any]]: "scope": _OAUTH_SCOPES, "code_challenge": challenge, "code_challenge_method": "S256", - "state": verifier, + "state": oauth_state, } from urllib.parse import urlencode @@ -1110,7 +1112,12 @@ def run_hermes_oauth_login_pure() -> Optional[Dict[str, Any]]: splits = auth_code.split("#") code = splits[0] - state = splits[1] if len(splits) > 1 else "" + received_state = splits[1] if len(splits) > 1 else "" + + # Validate state to prevent CSRF (RFC 6749 §10.12) + if received_state != oauth_state: + logger.warning("OAuth state mismatch — possible CSRF, aborting") + return None try: import urllib.request @@ -1119,7 +1126,7 @@ def run_hermes_oauth_login_pure() -> Optional[Dict[str, Any]]: "grant_type": "authorization_code", "client_id": _OAUTH_CLIENT_ID, "code": code, - "state": state, + "state": received_state, "redirect_uri": _OAUTH_REDIRECT_URI, "code_verifier": verifier, }).encode()