fix(mcp): reject non-ASCII OAuth callback state instead of crashing with 500 - #66614
Open
Drexuxux wants to merge 1 commit into
Open
fix(mcp): reject non-ASCII OAuth callback state instead of crashing with 500#66614Drexuxux wants to merge 1 commit into
Drexuxux wants to merge 1 commit into
Conversation
…ith 500
The dashboard MCP OAuth callback compared the returned `state` against the
expected value with `secrets.compare_digest(expected_state, state)` on raw
str operands. `state` is an attacker-controllable callback query parameter,
and `secrets.compare_digest` raises `TypeError: comparing strings with
non-ASCII characters is not supported` when either str operand holds a
non-ASCII character. A crafted callback such as `?state=café&code=x` then
surfaced as an unhandled 500 on the public callback endpoint instead of the
intended clean rejection ("OAuth flow expired" / "state mismatch").
Compare the UTF-8 bytes of both operands at the two callback sites
(`tools/mcp_dashboard_oauth.DashboardOAuthFlow.deliver_callback` and the
`mcp_oauth_callback` route in `hermes_cli/web_server.py`), so a non-ASCII
state misses like any other wrong state. Same non-ASCII `compare_digest`
class as the api-server bearer and webhook-signature hardenings; the OAuth
dashboard callback state was the remaining unencoded comparison.
Test: a callback with a non-ASCII state is now rejected with the clean
`state mismatch` ValueError; before the fix it raised TypeError.
tonydwb
reviewed
Jul 18, 2026
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved with Comment
PR #66614 — fix(mcp): reject non-ASCII OAuth callback state instead of crashing with 500
- Validates non-ASCII OAuth callback state and returns proper error instead of 500.
- 51 additions, 2 deletions — security-relevant fix.
- Properly handles error case that could be exploited for DoS.
Suggestions
- Confirm the error response uses an appropriate HTTP status code (4xx, not 500).
Reviewed by Hermes Agent
Contributor
|
Thanks for addressing both raw-string comparisons; the premise is confirmed on current main at Problems
Suggested changes
Automated hermes-sweeper review. |
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.
What
The dashboard MCP OAuth callback compared the returned
stateagainst the expected value viasecrets.compare_digest(expected_state, state)on raw str operands.stateis an attacker-controllable callback query parameter, andsecrets.compare_digestraisesTypeError: comparing strings with non-ASCII characters is not supportedwhen either str operand holds a non-ASCII char. A crafted callback like?state=café&code=xthen surfaced as an unhandled 500 on the public callback endpoint instead of the clean "OAuth flow expired" / "state mismatch" rejection.Same non-ASCII
compare_digestclass as the api-server bearer (#65305) and webhook-signature (#65307) hardenings — the OAuth dashboard callback state was the remaining unencoded comparison.Fix
Compare the UTF-8 bytes of both operands at the two callback sites:
tools/mcp_dashboard_oauth.py—DashboardOAuthFlow.deliver_callbackhermes_cli/web_server.py— themcp_oauth_callbackrouteA non-ASCII
statenow misses like any other wrong state.Tests
tests/tools/test_mcp_dashboard_oauth.py— newtest_dashboard_flow_rejects_non_ascii_state_without_crashing: a callback with a non-ASCII state is rejected with the cleanstate mismatchValueError; before the fix it raised TypeError.