diff --git a/src/commands/channels.status.command-flow.test.ts b/src/commands/channels.status.command-flow.test.ts index 0fa8ae85665..e9d44f3e0a3 100644 --- a/src/commands/channels.status.command-flow.test.ts +++ b/src/commands/channels.status.command-flow.test.ts @@ -256,6 +256,7 @@ describe("channelsStatusCommand SecretRef fallback flow", () => { [ "gateway timeout after 3000ms", "Gateway target: wss://user:pass@gateway.example.com/socket?token=secret-token&keep=visible", + "Gateway fallback: (wss://fallback-user:fallback-pass@[bad-host/socket?token=fallback-secret&keep=visible)", "Source: env OPENCLAW_GATEWAY_URL", ].join("\n"), ), @@ -280,9 +281,13 @@ describe("channelsStatusCommand SecretRef fallback flow", () => { const payload = JSON.parse(logs.at(-1) ?? "{}"); expect(errors.join("\n")).not.toContain("user:pass"); expect(errors.join("\n")).not.toContain("secret-token"); + expect(errors.join("\n")).not.toContain("fallback-user:fallback-pass"); + expect(errors.join("\n")).not.toContain("fallback-secret"); expect(payload.error).toContain("Gateway target:"); expect(payload.error).not.toContain("user:pass"); expect(payload.error).not.toContain("secret-token"); + expect(payload.error).not.toContain("fallback-user:fallback-pass"); + expect(payload.error).not.toContain("fallback-secret"); expect(payload).toEqual( expect.objectContaining({ gatewayReachable: false, diff --git a/src/commands/channels/status.ts b/src/commands/channels/status.ts index 7f2ce3060fc..c774b86e5c3 100644 --- a/src/commands/channels/status.ts +++ b/src/commands/channels/status.ts @@ -9,6 +9,7 @@ import { formatErrorMessage } from "../../infra/errors.js"; import { formatTimeAgo } from "../../infra/format-time/format-relative.ts"; import { listConfiguredChannelIdsForReadOnlyScope } from "../../plugins/channel-plugin-ids.js"; import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js"; +import { redactSensitiveUrlLikeString } from "../../shared/net/redact-sensitive-url.js"; import { formatDocsLink } from "../../terminal/links.js"; import { theme } from "../../terminal/theme.js"; import { @@ -30,36 +31,9 @@ export type ChannelsStatusOptions = { timeout?: string; }; -const SENSITIVE_GATEWAY_URL_QUERY_KEYS = new Set([ - "access_token", - "api_key", - "auth", - "key", - "password", - "secret", - "signature", - "token", -]); - function redactGatewayUrlSecretsInText(text: string): string { return text.replace(/\b(?:wss?|https?):\/\/[^\s"'<>]+/gi, (rawUrl) => { - try { - const url = new URL(rawUrl); - if (url.username) { - url.username = "redacted"; - } - if (url.password) { - url.password = "redacted"; - } - for (const key of url.searchParams.keys()) { - if (SENSITIVE_GATEWAY_URL_QUERY_KEYS.has(key.toLowerCase())) { - url.searchParams.set(key, "redacted"); - } - } - return url.toString(); - } catch { - return rawUrl; - } + return redactSensitiveUrlLikeString(rawUrl); }); } diff --git a/src/shared/net/redact-sensitive-url.test.ts b/src/shared/net/redact-sensitive-url.test.ts index b72b90655cb..3a092fab090 100644 --- a/src/shared/net/redact-sensitive-url.test.ts +++ b/src/shared/net/redact-sensitive-url.test.ts @@ -34,12 +34,21 @@ describe("redactSensitiveUrlLikeString", () => { "//***:***@example.com/mcp?client_secret=***", ); }); + + it("redacts protocol URLs that are too malformed to parse", () => { + expect( + redactSensitiveUrlLikeString( + "wss://fallback-user:fallback-pass@[bad-host/socket?token=fallback-secret&keep=visible)", + ), + ).toBe("wss://***:***@[bad-host/socket?token=***&keep=visible)"); + }); }); describe("isSensitiveUrlQueryParamName", () => { it("matches the auth-oriented query params used by MCP SSE config redaction", () => { expect(isSensitiveUrlQueryParamName("token")).toBe(true); expect(isSensitiveUrlQueryParamName("refresh_token")).toBe(true); + expect(isSensitiveUrlQueryParamName("signature")).toBe(true); expect(isSensitiveUrlQueryParamName("safe")).toBe(false); }); }); diff --git a/src/shared/net/redact-sensitive-url.ts b/src/shared/net/redact-sensitive-url.ts index c2a3f379f27..090a4ee3200 100644 --- a/src/shared/net/redact-sensitive-url.ts +++ b/src/shared/net/redact-sensitive-url.ts @@ -15,6 +15,7 @@ const SENSITIVE_URL_QUERY_PARAM_NAMES = new Set([ "auth", "client_secret", "refresh_token", + "signature", ]); export function isSensitiveUrlQueryParamName(name: string): boolean {