fix(realtime): strip originator header from browser WebRTC SDP offer (#76435)

Remove server-side-only OpenAI attribution headers from browser WebRTC SDP offer headers while preserving Gateway-side attribution.

Closes #76435.
Thanks @hclsys.
This commit is contained in:
hcl
2026-05-03 17:24:03 +08:00
committed by GitHub
parent 058b625154
commit bdf91fab9c
3 changed files with 11 additions and 8 deletions

View File

@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Control UI/Talk: fix Talk (OpenAI Realtime WebRTC) CORS failure by stripping server-side-only attribution headers (`originator`, `version`, `User-Agent`) from browser offer headers; `api.openai.com/v1/realtime/calls` only allows `authorization` and `content-type` in its CORS preflight, so forwarding these headers caused the browser SDP exchange to fail. Fixes #76435. Thanks @hclsys.
- Plugins/onboarding: trust optional official plugin and web-search installs selected from the official catalog so npm security scanning treats them like other source-linked official install paths. Thanks @vincentkoc.
- Microsoft Teams: persist sent-message markers across Gateway restarts so follow-up replies to recent bot messages keep resolving the original conversation instead of dropping out after restart, with marker TTLs preserved on best-effort recovery. (#75585) Thanks @amknight.
- Matrix: persist pending approval reaction targets across Gateway restarts so room approvers can still approve or deny outstanding prompts after OpenClaw comes back online. (#75586) Thanks @amknight.

View File

@@ -192,14 +192,12 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
transport: "webrtc-sdp",
clientSecret: "client-secret-123",
offerUrl: "https://api.openai.com/v1/realtime/calls",
offerHeaders: {
originator: "openclaw",
version: "2026.3.22",
},
});
expect((session as { offerHeaders?: Record<string, string> }).offerHeaders).not.toHaveProperty(
"User-Agent",
);
// originator, version, and User-Agent are server-side attribution headers; they
// must not be forwarded to the browser so that the browser's direct SDP POST to
// api.openai.com passes the CORS preflight (only authorization,content-type
// allowed — #76435). All three are filtered, leaving no browser offer headers.
expect((session as { offerHeaders?: Record<string, string> }).offerHeaders).toBeUndefined();
});
it("resolves keychain OPENAI_API_KEY refs before creating browser sessions", async () => {

View File

@@ -731,8 +731,12 @@ function resolveOpenAIRealtimeBrowserOfferHeaders(): Record<string, string> | un
transport: "http",
defaultHeaders: {},
});
// Strip server-side-only attribution headers: browser direct fetches to
// api.openai.com fail CORS preflight when these are present (only
// authorization,content-type are allowed by the endpoint's CORS policy).
const SERVER_ONLY_HEADERS = new Set(["user-agent", "originator", "version"]);
const browserHeaders = Object.fromEntries(
Object.entries(headers ?? {}).filter(([key]) => key.toLowerCase() !== "user-agent"),
Object.entries(headers ?? {}).filter(([key]) => !SERVER_ONLY_HEADERS.has(key.toLowerCase())),
);
return Object.keys(browserHeaders).length > 0 ? browserHeaders : undefined;
}