Summary
The Anthropic OAuth PKCE flow in agent/anthropic_adapter.py reuses the PKCE code_verifier as the OAuth state parameter. Per RFC 6749 §10.12 and RFC 7636, these serve fundamentally different purposes and should be independent cryptographic values.
Affected Code
agent/anthropic_adapter.py:665-675:
verifier, challenge = _generate_pkce()
# ...
params = {
"response_type": "code",
"code_challenge": challenge,
"code_challenge_method": "S256",
"state": verifier, # <-- verifier reused as state
# ...
}
And on the callback path (anthropic_adapter.py:717-723):
"state": state,
"code_verifier": verifier, # same value sent as both
Why this matters
The state parameter appears in the authorization URL — visible in:
- Browser address bar and history
- HTTP
Referer headers on any navigation away from the authorization page
- Authorization server access logs
- Browser extensions with URL access
The code_verifier is meant to remain secret — known only to the client and used in the token exchange to prove the same client that initiated the flow is completing it. By reusing it as state, the verifier's secrecy is compromised.
Additionally, the state value is never validated against the original on callback. At line 712, the callback parses state from user input but does not compare it to the verifier that was originally generated. This means the CSRF protection that state is supposed to provide (RFC 6749 ��10.12) is absent.
Impact
- Verifier leakage: An attacker who obtains the authorization URL (browser history, logs, referrer) learns the PKCE verifier and can complete the token exchange
- No CSRF protection: Without state validation on callback, an attacker can craft a malicious authorization response and have it accepted
Suggested fix
Generate a separate cryptographic random value for state and validate it on callback:
import secrets
verifier, challenge = _generate_pkce()
state = secrets.token_urlsafe(32) # independent value
params = {
"code_challenge": challenge,
"state": state, # separate from verifier
# ...
}
# On callback:
if received_state != expected_state:
raise ValueError("OAuth state mismatch — possible CSRF")
Note
This is distinct from the broader OAuth client_id design choice discussed elsewhere. The PKCE state/verifier conflation is a spec-level implementation issue independent of how the OAuth client identifies itself.
Summary
The Anthropic OAuth PKCE flow in
agent/anthropic_adapter.pyreuses the PKCEcode_verifieras the OAuthstateparameter. Per RFC 6749 §10.12 and RFC 7636, these serve fundamentally different purposes and should be independent cryptographic values.Affected Code
agent/anthropic_adapter.py:665-675:And on the callback path (
anthropic_adapter.py:717-723):Why this matters
The
stateparameter appears in the authorization URL — visible in:Refererheaders on any navigation away from the authorization pageThe
code_verifieris meant to remain secret — known only to the client and used in the token exchange to prove the same client that initiated the flow is completing it. By reusing it asstate, the verifier's secrecy is compromised.Additionally, the
statevalue is never validated against the original on callback. At line 712, the callback parsesstatefrom user input but does not compare it to the verifier that was originally generated. This means the CSRF protection thatstateis supposed to provide (RFC 6749 ��10.12) is absent.Impact
Suggested fix
Generate a separate cryptographic random value for
stateand validate it on callback:Note
This is distinct from the broader OAuth client_id design choice discussed elsewhere. The PKCE state/verifier conflation is a spec-level implementation issue independent of how the OAuth client identifies itself.