Skip to content

Fix WebSocket auth rejecting API keys with URL-special characters#964

Open
chrisyoung2005 wants to merge 1 commit intoRightNow-AI:mainfrom
chrisyoung2005:fix/ws-token-url-decode
Open

Fix WebSocket auth rejecting API keys with URL-special characters#964
chrisyoung2005 wants to merge 1 commit intoRightNow-AI:mainfrom
chrisyoung2005:fix/ws-token-url-decode

Conversation

@chrisyoung2005
Copy link
Copy Markdown

What

Fixes #962.

The WebSocket upgrade handler authenticates browser clients via a ?token= query parameter (browsers cannot set custom headers on WebSocket connections). The handler was extracting the token with split('&') / strip_prefix("token=") and comparing it directly against the configured API key using constant-time comparison.

The problem: browsers call encodeURIComponent() on the token before appending it to the WebSocket URL (api.js:226). Characters common in base64-derived keys — +, /, = — are percent-encoded to %2B, %2F, %3D. The server received the encoded string and compared it character-for-character against the raw key. They never match, so every WebSocket upgrade returned 401.

The result: the fallback in chat.js silently switched to HTTP polling mode. Users with base64-derived API keys (e.g. generated with openssl rand -base64 32) could never get streaming — without any clear error pointing to the root cause.

How

Switch to url::form_urlencoded::parse() to decode the query string before comparison. This is an existing workspace dependency (url = "2" in root Cargo.toml) — no new transitive dependencies are added.

Before (ws.rs):

let query_auth = uri
    .query()
    .and_then(|q| q.split('&').find_map(|pair| pair.strip_prefix("token=")))
    .map(|token| ct_eq(token, api_key))
    .unwrap_or(false);

After:

let query_auth = uri
    .query()
    .and_then(|q| {
        url::form_urlencoded::parse(q.as_bytes()).find_map(|(k, v)| {
            if k == "token" {
                Some(v.into_owned())
            } else {
                None
            }
        })
    })
    .map(|token| ct_eq(&token, api_key))
    .unwrap_or(false);

Verification

Workaround (until merged)

Generate an API key using only URL-safe characters:

openssl rand -hex 32

The ?token= query parameter was extracted with split/strip_prefix and
compared raw against the configured API key. Browsers encode the token
with encodeURIComponent before appending it to the WebSocket URL, so
characters common in base64-derived keys (+, /, =) arrive
percent-encoded (%2B, %2F, %3D) and the comparison always fails.

Switch to url::form_urlencoded::parse (an existing workspace dep) to
decode the query string before comparison. No new transitive
dependencies are added.

Fixes RightNow-AI#962
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WebSocket auth rejects valid API keys containing URL-special characters — chat silently falls back to HTTP (no streaming)

1 participant