fix(auth): compare presented credentials as bytes, not str#1703
Open
tang-vu wants to merge 1 commit into
Open
Conversation
secrets.compare_digest and hmac.compare_digest raise TypeError when either str argument contains non-ASCII characters. Every API-key check compared the caller-supplied token as a str, so an unauthenticated client could present a non-ASCII Bearer token, X-API-Key header, or ods-session cookie and receive an unhandled 500 with a stack trace instead of a clean 401/403. Authorization and Cookie headers are decoded as latin-1, so any byte in 0x80-0xFF reaches these comparisons from the network. session_signer.verify() additionally documents that it returns a reason and never raises; a non-ASCII cookie signature broke that contract on the forward_auth path Caddy calls. privacy-shield already carried the byte-wise form in _token_valid(), whose docstring says it exists so a non-ASCII token is a clean mismatch rather than a TypeError, but its own verify_api_key() never used it. Route that through the helper and apply the same byte-wise compare at the remaining call sites. Strings decoded as latin-1 cannot hold surrogates, so encoding them to UTF-8 here cannot raise in turn.
Collaborator
|
Thanks for tightening the auth preflight paths here; this catches a real unauthenticated error path without changing successful auth semantics. Advisory status: looks merge-ready What looks good:
What I checked:
Thanks again for pushing this forward. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
secrets.compare_digest/hmac.compare_digestraiseTypeErrorwhen either str argument holds non-ASCII characters. Every API-key check in the repo compared the caller-supplied token as astr, so an unauthenticated client can send a non-ASCIIAuthorization: Bearertoken,X-API-Keyheader, orods-sessioncookie and get an unhandled 500 with a stack trace instead of a clean 401/403.AuthorizationandCookieheaders are decoded as latin-1, so any byte0x80–0xFFreaches these comparisons straight off the network.Reproduced against the real modules (not a mock):
session_signer.verify()documents that it returns a reason and never raises. A non-ASCII cookie signature broke that contract on exactly theforward_authpath Caddy calls (GET /api/auth/verify-session).This is not an auth bypass.
forward_authtreats a 500 as non-2xx and still denies the request, and a wrong key never compares equal. The defect is an unhandled exception on unauthenticated paths: wrong status code, stack traces in logs, and a broken documented contract.Why this shape of fix
privacy-shieldalready carried the byte-wise form in_token_valid(), whose docstring says it exists so that…but
verify_api_key()in that same file never used it. The defect class was already recognised here and just applied incompletely. This PR routes privacy-shield through its own helper and applies the same byte-wise compare at the six remaining call sites:dashboard-api/security.pyAuthorization: Bearerdashboard-api/session_signer.pyods-sessioncookie signatureprivacy-shield/proxy.pyAuthorization: Bearer(now via_token_valid)token-spy/main.pyAuthorization: Bearerape/main.pyX-API-Keybin/ods-host-agent.pyAuthorization: Bearerextensions/library/services/open-interpreter/server.pyAuthorization: BearerKeys are encoded at call time rather than precomputed at import, because
tests/test_security.pymonkeypatchessecurity.DASHBOARD_API_KEY.Strings decoded as latin-1 cannot contain surrogates, so the added
.encode("utf-8")cannot itself raise — verified.AI Assistance
Claude Code was used to grep for the defect class across services, write the reproduction script, draft the fix and the regression tests, and run the validation commands below. I read the final diff, confirmed the reproduction before and after, and confirmed the three pre-existing failures listed below are unrelated to this change.
Release Lane
release/2.5.xmainStable hotfix reason:
Changed Surface
Risk And Validation
docs/HIGH_RISK_CHANGE_MAP.mdmaps "Dashboard API auth/setup/host-agent" and "Network binding/proxy routes" to High, requiring focused pytest, security tests, dashboard API smoke.git diff --checkrelease/2.5.xCommands/results:
Operational Change Check
Narrower equivalent, rather than a full fleet run: the change cannot affect install, bootstrap, compose generation, lifecycle, model download, or host mutation — it only replaces
stroperands with their UTF-8 bytes inside seven credential comparisons. Behaviour for a valid key and for a wrong ASCII key is unchanged and is covered by the pre-existing tests in each suite. The only behaviour that changes is non-ASCII input, which previously crashed.Notes For Reviewers
compare_digestonbytesis the documented constant-time path, and nothing short-circuits before it.token-spyandopen-interpreterhave no auth test suite (token-spy/tests/only covers usage reporting;open-interpreterhas notests/), so those two sites are fixed without a new test rather than growing a suite in this PR. Happy to add one if you would prefer.ape/main.pyreturns 401 (not 403) for a wrong key. I preserved each service's existing status code rather than unifying them here.