Repository navigation
Conversation
… cannot raise secrets.compare_digest accepts only ASCII str values and raises TypeError when either side holds a non-ASCII character. Uncaught, a merely wrong token answers 500 instead of 401, and a non-ASCII DEVUI_AUTH_TOKEN never matches even when the client presents it correctly. Compare bytes instead: headers travel as bytes and Starlette hands them back decoded as latin-1, while the configured token is text, so UTF-8 is its wire form. ASCII tokens compare exactly as before.
There was a problem hiding this comment.
🟡 Changes recommended
Unicode whitespace trimming still corrupts some valid UTF-8 token endings, causing correct credentials to receive HTTP 401.
1 open issue
What changed in this PR
This PR addresses #9713 by updating Python DevUI bearer-token authentication to compare bytes, avoiding non-ASCII string comparison errors.
Changes:
- Adds a token-comparison helper that recovers header bytes using Latin-1 and encodes configured tokens as UTF-8.
- Adds four regression cases covering matching and mismatched ASCII and non-ASCII tokens.
| File | Description |
|---|---|
python/packages/devui/tests/devui/test_server.py |
Adds authentication regression tests using raw-byte headers. |
python/packages/devui/agent_framework_devui/_server.py |
Introduces byte-based token comparison and integrates it into authentication middleware. |
Review effort: Balanced
|
|
||
| token = auth_header.replace("Bearer ", "", 1).strip() | ||
| if not secrets.compare_digest(token, expected_token): | ||
| if not self._token_matches(token, expected_token): |
Eduard van Valkenburg (eavanvalkenburg)
left a comment
There was a problem hiding this comment.
lisry-1 Please take a look at the inline test-typing finding in addition to the existing authentication feedback.
| # Sent as raw bytes: httpx refuses to encode a non-ASCII header value itself. | ||
| response = client.get( | ||
| "/v1/entities", | ||
| headers={"Authorization": b"Bearer " + presented_token.encode("utf-8")}, |
There was a problem hiding this comment.
lisry-1 This header mapping mixes a string key with a bytes value. HTTPX accepts string/string or bytes/bytes mappings, so the request works at runtime but the current test fails Pyright and Pyrefly and blocks the merge gatekeeper. Please use a bytes key—{b"Authorization": b"Bearer " + presented_token.encode("utf-8")}—to preserve the raw token bytes without violating the declared header type.

Motivation & Context
DevUI's bearer-token middleware compares the presented token with the configured one using
secrets.compare_digest(token, expected_token).compare_digestaccepts only ASCIIstrvalues — anything else raisesTypeError("comparing strings with non-ASCII characters is not supported"). Nothing catches it, so:Authorization: Bearer …value holds a non-ASCII byte answers HTTP 500 instead of HTTP 401.DEVUI_AUTH_TOKENitself contains non-ASCII characters, the request raises the same way, so that configured token can never authenticate — the second point in the issue.The failure is fail-closed (no bypass), but it turns an authentication failure into an internal error, and it makes a non-ASCII
DEVUI_AUTH_TOKENunusable.Reported in #9713.
Description & Review Guide
What are the major changes?
python/packages/devui/agent_framework_devui/_server.py:DevServer._token_matches(presented, expected)static helper replaces the directsecrets.compare_digestcall inauth_middleware. It compares bytes, whichcompare_digestaccepts without restriction.DEVUI_AUTH_TOKENfrom the environment) is text, so UTF-8 is its wire form.Falserather than raising, so no input can turn into a 500.python/packages/devui/tests/devui/test_server.py:raise_server_exceptions=Falseturns the old crash into an observable 500.ASCII tokens are unaffected: latin-1 and UTF-8 agree on ASCII, so the comparison is bit-for-bit what it was.
What is the impact of these changes?
A wrong or malformed bearer token is now consistently
401 invalid_tokeninstead of500. ADEVUI_AUTH_TOKENcontaining non-ASCII characters becomes usable when the client sends the same characters as UTF-8. No behavior change for ASCII tokens, which is every token DevUI generates itself (secrets.token_urlsafe).What do you want reviewers to focus on?
The encoding choice. There is no standard for a non-ASCII bearer token (RFC 6750 restricts
b64tokento ASCII), so I had to pick one: wire bytes for the presented value, UTF-8 for the configured one, which is what makes case 2 of the issue work. If you would rather treat any non-ASCII token as simply invalid, the alternative is a one-linetry/except TypeError: return Falsearound the existing call — that fixes the 500 but leaves a non-ASCIIDEVUI_AUTH_TOKENunusable. Happy to switch.Related Issue
Fixes #9713
This is submitted before maintainer triage (the issue carries
needs-maintainer-triage). To be explicit about why I consider it under the trivial-change bar: the issue states the desired outcome ("invalid or malformed bearer tokens should be rejected with HTTP 401 rather than causing an internal server error"), the fix is contained to the comparison, and no ASCII behavior changes. If you would rather this waited for triage, say so and I will hold.Contribution Checklist
ruff checkandruff format --checkpass)assert 500 == 401/assert 500 == 200)AI Assistance
Sending
Authorizationas raw bytes (httpx refuses to encode a non-ASCII header value itself), before the fix:After the fix:
Test runs:
python/packages/devui/tests/devui/test_server.py58 passed; the widerpython/packages/devui/tests/devui/suite 219 passed / 4 skipped (test_ui_memory_regression.pyneedswebsockets, which is not installed here).