fix(diagnostics): harden capture redaction and discord metadata fetch (#71303)

This commit is contained in:
Vincent Koc
2026-04-24 17:51:12 -07:00
committed by GitHub
parent 25a02825a5
commit 718dffd2f2
6 changed files with 111 additions and 63 deletions

View File

@@ -814,7 +814,7 @@ describe("diagnostics-otel service", () => {
toolCallId: "tool-1",
durationMs: 20,
toolInput: "tool input",
toolOutput: "x".repeat(6000),
toolOutput: `${"x".repeat(4077)} Bearer ${"a".repeat(80)}`, // pragma: allowlist secret
} as Parameters<typeof emitDiagnosticEvent>[0]);
await flushDiagnosticEvents();
@@ -842,6 +842,7 @@ describe("diagnostics-otel service", () => {
expect(String(toolAttrs?.["openclaw.content.tool_output"]).length).toBeLessThanOrEqual(
MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS + OTEL_TRUNCATED_SUFFIX_MAX_CHARS,
);
expect(String(toolAttrs?.["openclaw.content.tool_output"])).not.toContain("a".repeat(11));
await service.stop?.(ctx);
});

View File

@@ -134,7 +134,7 @@ function clampOtelLogText(value: string, maxChars: number): string {
}
function normalizeOtelLogString(value: string, maxChars: number): string {
return redactSensitiveText(clampOtelLogText(value, maxChars));
return clampOtelLogText(redactSensitiveText(value), maxChars);
}
function resolveContentCapturePolicy(value: unknown): OtelContentCapturePolicy {

View File

@@ -14,7 +14,6 @@ import { danger } from "openclaw/plugin-sdk/runtime-env";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import * as undici from "undici";
import * as ws from "ws";
import { validateDiscordProxyUrl } from "../proxy-fetch.js";
import { DISCORD_GATEWAY_TRANSPORT_ACTIVITY_EVENT } from "./gateway-handle.js";
@@ -473,8 +472,6 @@ export function createDiscordGatewayPlugin(params: {
runtime: RuntimeEnv;
__testing?: {
HttpsProxyAgentCtor?: typeof httpsProxyAgent.HttpsProxyAgent;
ProxyAgentCtor?: typeof undici.ProxyAgent;
undiciFetch?: typeof undici.fetch;
webSocketCtor?: DiscordGatewayWebSocketCtor;
registerClient?: (
plugin: carbonGateway.GatewayPlugin,
@@ -520,31 +517,24 @@ export function createDiscordGatewayPlugin(params: {
validateDiscordProxyUrl(proxy);
const HttpsProxyAgentCtor =
params.__testing?.HttpsProxyAgentCtor ?? httpsProxyAgent.HttpsProxyAgent;
const ProxyAgentCtor = params.__testing?.ProxyAgentCtor ?? undici.ProxyAgent;
const wsAgent = new HttpsProxyAgentCtor<string>(proxy);
const fetchAgent = new ProxyAgentCtor(proxy);
params.runtime.log?.("discord: gateway proxy enabled");
return createGatewayPlugin({
options,
fetchImpl: async (input, init) => {
const response = (await (params.__testing?.undiciFetch ?? undici.fetch)(
return await fetchDiscordGatewayMetadataDirect(
input,
init,
)) as unknown as Response;
captureHttpExchange({
url: input,
method: (init?.method as string | undefined) ?? "GET",
requestHeaders: init?.headers as Headers | Record<string, string> | undefined,
requestBody: (init as RequestInit & { body?: BodyInit | null })?.body ?? null,
response,
flowId: randomUUID(),
meta: { subsystem: "discord-gateway-metadata" },
});
return response;
debugProxySettings.enabled
? false
: {
flowId: randomUUID(),
meta: { subsystem: "discord-gateway-metadata" },
},
);
},
fetchInit: { dispatcher: fetchAgent },
wsAgent,
runtime: params.runtime,
testing: params.__testing

View File

@@ -18,18 +18,12 @@ const {
globalFetchMock,
HttpsProxyAgent,
getLastAgent,
restProxyAgentSpy,
resolveDebugProxySettingsMock,
undiciFetchMock,
undiciProxyAgentSpy,
resetLastAgent,
webSocketSpy,
wsProxyAgentSpy,
} = vi.hoisted(() => {
const wsProxyAgentSpy = vi.fn();
const undiciProxyAgentSpy = vi.fn();
const restProxyAgentSpy = vi.fn();
const undiciFetchMock = vi.fn();
const globalFetchMock = vi.fn();
const baseRegisterClientSpy = vi.fn();
const webSocketSpy = vi.fn();
@@ -86,12 +80,9 @@ const {
globalFetchMock,
HttpsProxyAgent,
getLastAgent: () => HttpsProxyAgent.lastCreated,
restProxyAgentSpy,
captureHttpExchangeSpy,
captureWsEventSpy,
resolveDebugProxySettingsMock,
undiciFetchMock,
undiciProxyAgentSpy,
resetLastAgent: () => {
HttpsProxyAgent.lastCreated = undefined;
},
@@ -115,15 +106,6 @@ vi.mock("https-proxy-agent", () => ({
HttpsProxyAgent,
}));
vi.mock("undici", () => ({
ProxyAgent: function ProxyAgent(this: { proxyUrl: string }, proxyUrl: string) {
this.proxyUrl = proxyUrl;
undiciProxyAgentSpy(proxyUrl);
restProxyAgentSpy(proxyUrl);
},
fetch: undiciFetchMock,
}));
vi.mock("ws", () => ({
default: function MockWebSocket(url: string, options?: { agent?: unknown }) {
webSocketSpy(url, options);
@@ -176,12 +158,6 @@ describe("createDiscordGatewayPlugin", () => {
return {
HttpsProxyAgentCtor:
HttpsProxyAgent as unknown as typeof import("https-proxy-agent").HttpsProxyAgent,
ProxyAgentCtor: function ProxyAgentCtor(this: { proxyUrl: string }, proxyUrl: string) {
this.proxyUrl = proxyUrl;
undiciProxyAgentSpy(proxyUrl);
restProxyAgentSpy(proxyUrl);
} as unknown as typeof import("undici").ProxyAgent,
undiciFetch: undiciFetchMock,
webSocketCtor: function WebSocketCtor(url: string, options?: { agent?: unknown }) {
webSocketSpy(url, options);
} as unknown as new (url: string, options?: { agent?: unknown }) => import("ws").WebSocket,
@@ -276,9 +252,6 @@ describe("createDiscordGatewayPlugin", () => {
vi.useRealTimers();
baseRegisterClientSpy.mockClear();
globalFetchMock.mockClear();
restProxyAgentSpy.mockClear();
undiciFetchMock.mockClear();
undiciProxyAgentSpy.mockClear();
wsProxyAgentSpy.mockClear();
webSocketSpy.mockClear();
captureHttpExchangeSpy.mockClear();
@@ -454,7 +427,7 @@ describe("createDiscordGatewayPlugin", () => {
expect(runtime.log).not.toHaveBeenCalled();
});
it("uses proxy fetch for gateway metadata lookup before registering", async () => {
it("keeps gateway metadata lookup on the guarded direct fetch when proxy is configured", async () => {
const runtime = createRuntime();
const plugin = createDiscordGatewayPlugin({
discordConfig: { proxy: "http://127.0.0.1:8080" },
@@ -462,14 +435,12 @@ describe("createDiscordGatewayPlugin", () => {
__testing: createProxyTestingOverrides(),
});
await registerGatewayClientWithMetadata({ plugin, fetchMock: undiciFetchMock });
await registerGatewayClientWithMetadata({ plugin, fetchMock: globalFetchMock });
expect(restProxyAgentSpy).toHaveBeenCalledWith("http://127.0.0.1:8080");
expect(undiciFetchMock).toHaveBeenCalledWith(
expect(globalFetchMock).toHaveBeenCalledWith(
"https://discord.com/api/v10/gateway/bot",
expect.objectContaining({
headers: { Authorization: "Bot token-123" },
dispatcher: expect.objectContaining({ proxyUrl: "http://127.0.0.1:8080" }),
}),
);
expect(baseRegisterClientSpy).toHaveBeenCalledTimes(1);
@@ -488,7 +459,7 @@ describe("createDiscordGatewayPlugin", () => {
expect(captureHttpExchangeSpy).not.toHaveBeenCalled();
});
it("accepts IPv6 loopback proxy URLs for gateway metadata and websocket setup", async () => {
it("accepts IPv6 loopback proxy URLs for websocket setup", async () => {
const runtime = createRuntime();
const plugin = createDiscordGatewayPlugin({
discordConfig: { proxy: "http://[::1]:8080" },
@@ -499,10 +470,9 @@ describe("createDiscordGatewayPlugin", () => {
const createWebSocket = (plugin as unknown as { createWebSocket: (url: string) => unknown })
.createWebSocket;
createWebSocket("wss://gateway.discord.gg");
await registerGatewayClientWithMetadata({ plugin, fetchMock: undiciFetchMock });
await registerGatewayClientWithMetadata({ plugin, fetchMock: globalFetchMock });
expect(wsProxyAgentSpy).toHaveBeenCalledWith("http://[::1]:8080");
expect(restProxyAgentSpy).toHaveBeenCalledWith("http://[::1]:8080");
expect(runtime.error).not.toHaveBeenCalled();
});