fix(auth): match the Bearer auth scheme case-insensitively (RFC 7235)

`classify_auth_signals` gated PAYG/OAUTH token-shape detection on
`authorization.startswith("Bearer ")`. RFC 7235 §2.1 defines the auth-scheme
token as case-insensitive, so a client sending `Authorization: bearer sk-...`
skipped the Bearer branch entirely and fell through to the `elif auth:`
OAUTH classification — misclassifying a PAYG API key as OAUTH.

Auth mode drives compression-policy routing and the cost/TOIN `auth_mode`
label, so the misclassification silently changes how a request is compressed
and accounted. Fold only the scheme for the comparison via `str.partition`;
the credential keeps its original case (tokens are case-sensitive).
This commit is contained in:
rajatnagda45 2026-08-23 13:06:02 +05:30
parent 34a5517562
commit 3c1991887f
2 changed files with 40 additions and 2 deletions

View file

@ -71,8 +71,15 @@ def classify_auth_signals(signals: AuthSignals) -> AuthMode:
return AuthMode.SUBSCRIPTION
auth = signals.authorization
if auth.startswith("Bearer "):
token = auth[len("Bearer ") :]
# The auth-scheme token ("Bearer") is case-insensitive per RFC 7235 §2.1,
# so a client sending `Authorization: bearer sk-...` must classify the same
# as `Bearer`. A case-sensitive `startswith("Bearer ")` sent such a request
# to the `elif auth:` (OAUTH) fallthrough, misclassifying a PAYG API key —
# which mis-routes compression policy and mislabels cost/TOIN auth_mode.
# Only the scheme is case-folded; the credential itself stays case-sensitive.
scheme, sep, credentials = auth.partition(" ")
if sep and scheme.lower() == "bearer":
token = credentials
if token.startswith("sk-ant-oat"):
return AuthMode.OAUTH
if token.startswith("sk-ant-api") or token.startswith("sk-"):

View file

@ -60,3 +60,34 @@ def test_codex_stamp_only_for_unidentified_responses_callers() -> None:
is False
)
assert should_stamp_codex_client_signals("/v1/chat/completions", AuthSignals()) is False
def test_bearer_scheme_is_case_insensitive() -> None:
# RFC 7235 §2.1: the auth-scheme token is case-insensitive. A lowercase or
# mixed-case "bearer" carrying a PAYG key must classify as PAYG, not fall
# through to the non-Bearer OAUTH branch.
for scheme in ("bearer", "BEARER", "Bearer", "BeArEr"):
signals = AuthSignals(authorization=f"{scheme} sk-ant-api03-abc")
assert classify_auth_signals(signals) is AuthMode.PAYG, scheme
def test_bearer_case_insensitive_preserves_oauth_and_jwt_shapes() -> None:
jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.signature"
assert classify_auth_signals(AuthSignals(authorization="bearer sk-ant-oat01-abc")) is (
AuthMode.OAUTH
)
assert classify_auth_signals(AuthSignals(authorization=f"bearer {jwt}")) is AuthMode.OAUTH
def test_credential_case_is_not_folded() -> None:
# Only the scheme is case-folded; the token keeps its case, so a PAYG
# `sk-...` prefix still matches exactly (it is lowercase by construction).
signals = AuthSignals(authorization="Bearer sk-PROJ-Abc123")
assert classify_auth_signals(signals) is AuthMode.PAYG
def test_non_bearer_scheme_still_oauth() -> None:
# AWS SigV4 (Bedrock) and any other non-Bearer scheme keep the OAUTH
# passthrough-prefer classification.
signals = AuthSignals(authorization="AWS4-HMAC-SHA256 Credential=AKIA/...")
assert classify_auth_signals(signals) is AuthMode.OAUTH