mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-01 12:21:25 +00:00
fix: apply SSE auth headers to initial GET, redact URL credentials, warn on malformed headers
This commit is contained in:
committed by
Peter Steinberger
parent
6fda8b4e9a
commit
32b7c00f90
@@ -35,7 +35,10 @@ function toStringRecord(
|
||||
|
||||
export function resolveSseMcpServerLaunchConfig(
|
||||
raw: unknown,
|
||||
options?: { onDroppedHeader?: (key: string, value: unknown) => void },
|
||||
options?: {
|
||||
onDroppedHeader?: (key: string, value: unknown) => void;
|
||||
onMalformedHeaders?: (value: unknown) => void;
|
||||
},
|
||||
): SseMcpServerLaunchResult {
|
||||
if (!isRecord(raw)) {
|
||||
return { ok: false, reason: "server config must be an object" };
|
||||
@@ -56,17 +59,49 @@ export function resolveSseMcpServerLaunchConfig(
|
||||
reason: `only http and https URLs are supported, got ${parsed.protocol}`,
|
||||
};
|
||||
}
|
||||
// Warn if headers is present but not an object (e.g. a string or array).
|
||||
let headers: Record<string, string> | undefined;
|
||||
if (raw.headers !== undefined && raw.headers !== null) {
|
||||
if (!isRecord(raw.headers)) {
|
||||
options?.onMalformedHeaders?.(raw.headers);
|
||||
} else {
|
||||
headers = toStringRecord(raw.headers, options?.onDroppedHeader);
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
config: {
|
||||
url,
|
||||
headers: toStringRecord(raw.headers, options?.onDroppedHeader),
|
||||
headers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function describeSseMcpServerLaunchConfig(config: SseMcpServerLaunchConfig): string {
|
||||
return config.url;
|
||||
try {
|
||||
const parsed = new URL(config.url);
|
||||
// Redact embedded credentials and query-token auth from log/description output.
|
||||
if (parsed.username || parsed.password) {
|
||||
parsed.username = parsed.username ? "***" : "";
|
||||
parsed.password = parsed.password ? "***" : "";
|
||||
}
|
||||
for (const key of parsed.searchParams.keys()) {
|
||||
const lower = key.toLowerCase();
|
||||
if (
|
||||
lower === "token" ||
|
||||
lower === "key" ||
|
||||
lower === "api_key" ||
|
||||
lower === "apikey" ||
|
||||
lower === "secret" ||
|
||||
lower === "access_token"
|
||||
) {
|
||||
parsed.searchParams.set(key, "***");
|
||||
}
|
||||
}
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return config.url;
|
||||
}
|
||||
}
|
||||
|
||||
export type { SseMcpServerLaunchConfig, SseMcpServerLaunchResult };
|
||||
|
||||
@@ -155,13 +155,30 @@ function resolveTransport(
|
||||
`bundle-mcp: server "${serverName}": header "${key}" has an unsupported value type and was ignored.`,
|
||||
);
|
||||
},
|
||||
onMalformedHeaders: () => {
|
||||
logWarn(
|
||||
`bundle-mcp: server "${serverName}": "headers" must be a JSON object; the value was ignored.`,
|
||||
);
|
||||
},
|
||||
});
|
||||
if (sseLaunch.ok) {
|
||||
const headers: Record<string, string> = {
|
||||
...sseLaunch.config.headers,
|
||||
};
|
||||
const hasHeaders = Object.keys(headers).length > 0;
|
||||
const transport = new SSEClientTransport(new URL(sseLaunch.config.url), {
|
||||
requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
|
||||
// Apply headers to POST requests (tool calls, listTools, etc.).
|
||||
requestInit: hasHeaders ? { headers } : undefined,
|
||||
// Apply headers to the initial SSE GET handshake (required for auth).
|
||||
eventSourceInit: hasHeaders
|
||||
? {
|
||||
fetch: (url, init) =>
|
||||
fetch(url, {
|
||||
...init,
|
||||
headers: { ...headers, ...(init?.headers as Record<string, string>) },
|
||||
}),
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
return {
|
||||
transport,
|
||||
|
||||
Reference in New Issue
Block a user