From 279dba17d2162def5299b8c3ea8f844032dbfa03 Mon Sep 17 00:00:00 2001 From: Omar Shahine Date: Thu, 16 Apr 2026 14:55:52 +0000 Subject: [PATCH] fix(bluebubbles): revert undici import, restore dispatcher-strip approach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert the @claude bot's undici import in types.ts — it introduced a direct 'undici' dependency that is not declared in the BB extension's package.json and would break isolated plugin installs. Restore the original dispatcher-strip approach which is correct: the SSRF guard already completed validation upstream before calling this function as fetchImpl, so stripping the dispatcher does not weaken security. --- extensions/bluebubbles/src/types.ts | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/extensions/bluebubbles/src/types.ts b/extensions/bluebubbles/src/types.ts index b4d738e69b2..307fe2b1809 100644 --- a/extensions/bluebubbles/src/types.ts +++ b/extensions/bluebubbles/src/types.ts @@ -1,6 +1,5 @@ import type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/setup"; import { fetchWithSsrFGuard, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime"; -import { fetch as undiciFetch } from "undici"; export type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime"; export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/setup"; @@ -176,26 +175,18 @@ export async function blueBubblesFetchWithTimeout( await release(); } } - // The SSRF guard (and other guarded callers using this function as their - // `fetchImpl`) may attach a bundled-undici `dispatcher` to `init` to enforce DNS - // pinning per request. That dispatcher is incompatible with Node 22+'s built-in - // undici backing globalThis.fetch and causes a silent TypeError (invalid - // onRequestStart method) when forwarded — but it works correctly with the - // bundled-undici `fetch`. When a dispatcher is present, route through bundled - // undici so the DNS-pinning contract is preserved; otherwise stay on - // globalThis.fetch. (#64105, #67510) - const initWithDispatcher = (init ?? {}) as RequestInit & { dispatcher?: unknown }; - const hasDispatcher = initWithDispatcher.dispatcher !== undefined; + // Strip `dispatcher` from init — the SSRF guard may have attached a bundled-undici + // dispatcher that is incompatible with Node 22+'s built-in undici backing globalThis.fetch(). + // Passing it through causes a silent TypeError (invalid onRequestStart method). + // The SSRF validation already completed upstream in fetchWithSsrFGuard before calling + // this function as fetchImpl, so stripping the dispatcher does not weaken security. (#64105) + const { dispatcher: _dispatcher, ...safeInit } = (init ?? {}) as RequestInit & { + dispatcher?: unknown; + }; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { - if (hasDispatcher) { - return (await undiciFetch(url, { - ...initWithDispatcher, - signal: controller.signal, - } as Parameters[1])) as unknown as Response; - } - return await fetch(url, { ...initWithDispatcher, signal: controller.signal }); + return await fetch(url, { ...safeInit, signal: controller.signal }); } finally { clearTimeout(timer); }