From 90e61844af3094ebf73d875fec95d6f5d6c8b00c Mon Sep 17 00:00:00 2001 From: chris-young Date: Thu, 2 Apr 2026 10:43:06 -0700 Subject: [PATCH] Fix WebSocket auth rejecting API keys with URL-special characters 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 #962 --- Cargo.lock | 1 + crates/openfang-api/Cargo.toml | 1 + crates/openfang-api/src/ws.rs | 12 ++++++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d84ca7e84..b2c1ee64d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4014,6 +4014,7 @@ dependencies = [ "tower", "tower-http", "tracing", + "url", "uuid", ] diff --git a/crates/openfang-api/Cargo.toml b/crates/openfang-api/Cargo.toml index fcc3e0088c..92ba12fcf4 100644 --- a/crates/openfang-api/Cargo.toml +++ b/crates/openfang-api/Cargo.toml @@ -32,6 +32,7 @@ futures = { workspace = true } governor = { workspace = true } tokio-stream = { workspace = true } subtle = { workspace = true } +url = { workspace = true } base64 = { workspace = true } sha2 = { workspace = true } hmac = { workspace = true } diff --git a/crates/openfang-api/src/ws.rs b/crates/openfang-api/src/ws.rs index 353ffd770e..fb969044fe 100644 --- a/crates/openfang-api/src/ws.rs +++ b/crates/openfang-api/src/ws.rs @@ -168,8 +168,16 @@ pub async fn agent_ws( let query_auth = uri .query() - .and_then(|q| q.split('&').find_map(|pair| pair.strip_prefix("token="))) - .map(|token| ct_eq(token, api_key)) + .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); if !header_auth && !query_auth {