fix(bluebubbles): revert undici import, restore dispatcher-strip approach

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.
This commit is contained in:
Omar Shahine
2026-04-16 14:55:52 +00:00
parent 0d7d1c4208
commit 279dba17d2

View File

@@ -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<typeof undiciFetch>[1])) as unknown as Response;
}
return await fetch(url, { ...initWithDispatcher, signal: controller.signal });
return await fetch(url, { ...safeInit, signal: controller.signal });
} finally {
clearTimeout(timer);
}