From 42cf32c3869c281c6a78fa0773fde8d12cacdcf6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 26 Feb 2026 14:17:31 +0000 Subject: [PATCH] fix(browser): land PR #26015 query-token auth for /json relay routes Align relay HTTP /json auth with websocket auth by accepting query-param tokens, add regression coverage, and update changelog. Landed from contributor @Sid-Qin (PR #26015). Co-authored-by: SidQin-cyber --- CHANGELOG.md | 1 + src/browser/extension-relay.test.ts | 13 +++++++++++++ src/browser/extension-relay.ts | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed4ec4445..02c424634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Browser/Extension relay CORS: handle `/json*` `OPTIONS` preflight before auth checks, allow Chrome extension origins, and return extension-origin CORS headers on relay HTTP responses so extension token validation no longer fails cross-origin. Landed from contributor PR #23962 by @miloudbelarebia. (#23842) +- Browser/Extension relay auth: allow `?token=` query-param auth on relay `/json*` endpoints (consistent with relay WebSocket auth) so curl/devtools-style `/json/version` and `/json/list` probes work without requiring custom headers. Landed from contributor PR #26015 by @Sid-Qin. (#25928) - Auth/Auth profiles: normalize `auth-profiles.json` alias fields (`mode -> type`, `apiKey -> key`) before credential validation so entries copied from `openclaw.json` auth examples are no longer silently dropped. (#26950) thanks @byungsker. - Cron/Hooks isolated routing: preserve canonical `agent:*` session keys in isolated runs so already-qualified keys are not double-prefixed (for example `agent:main:main` no longer becomes `agent:main:agent:main:main`). Landed from contributor PR #27333 by @MaheshBhushan. (#27289, #27282) - iOS/Talk mode: stop injecting the voice directive hint into iOS Talk prompts and remove the Voice Directive Hint setting, reducing model bias toward tool-style TTS directives and keeping relay responses text-first by default. (#27543) thanks @ngutman. diff --git a/src/browser/extension-relay.test.ts b/src/browser/extension-relay.test.ts index 5405dc93e..8c920b351 100644 --- a/src/browser/extension-relay.test.ts +++ b/src/browser/extension-relay.test.ts @@ -332,6 +332,19 @@ describe("chrome extension relay server", () => { ext.close(); }); + it("accepts /json endpoints with relay token query param", async () => { + const port = await getFreePort(); + cdpUrl = `http://127.0.0.1:${port}`; + await ensureChromeExtensionRelayServer({ cdpUrl }); + + const token = relayAuthHeaders(cdpUrl)["x-openclaw-relay-token"]; + expect(token).toBeTruthy(); + const versionRes = await fetch( + `${cdpUrl}/json/version?token=${encodeURIComponent(String(token))}`, + ); + expect(versionRes.status).toBe(200); + }); + it("accepts raw gateway token for relay auth compatibility", async () => { const port = await getFreePort(); cdpUrl = `http://127.0.0.1:${port}`; diff --git a/src/browser/extension-relay.ts b/src/browser/extension-relay.ts index a51b7e7e5..7ad80420b 100644 --- a/src/browser/extension-relay.ts +++ b/src/browser/extension-relay.ts @@ -399,7 +399,7 @@ export async function ensureChromeExtensionRelayServer(opts: { } if (path.startsWith("/json")) { - const token = getHeader(req, RELAY_AUTH_HEADER)?.trim(); + const token = getRelayAuthTokenFromRequest(req, url); if (!token || !relayAuthTokens.has(token)) { res.writeHead(401); res.end("Unauthorized");