mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:00:44 +00:00
fix: redact channel status json errors
This commit is contained in:
@@ -251,7 +251,15 @@ describe("channelsStatusCommand SecretRef fallback flow", () => {
|
||||
});
|
||||
|
||||
it("keeps JSON fallback structured without rendering config-only text", async () => {
|
||||
mocks.callGateway.mockRejectedValue(new Error("gateway closed"));
|
||||
mocks.callGateway.mockRejectedValue(
|
||||
new Error(
|
||||
[
|
||||
"gateway timeout after 3000ms",
|
||||
"Gateway target: wss://user:pass@gateway.example.com/socket?token=secret-token&keep=visible",
|
||||
"Source: env OPENCLAW_GATEWAY_URL",
|
||||
].join("\n"),
|
||||
),
|
||||
);
|
||||
mocks.requireValidConfigSnapshot.mockResolvedValue({ secretResolved: false, channels: {} });
|
||||
mocks.resolveCommandConfigWithSecrets.mockResolvedValue({
|
||||
resolvedConfig: { secretResolved: true, channels: {} },
|
||||
@@ -270,6 +278,9 @@ describe("channelsStatusCommand SecretRef fallback flow", () => {
|
||||
}),
|
||||
);
|
||||
const payload = JSON.parse(logs.at(-1) ?? "{}");
|
||||
expect(payload.error).toContain("Gateway target:");
|
||||
expect(payload.error).not.toContain("user:pass");
|
||||
expect(payload.error).not.toContain("secret-token");
|
||||
expect(payload).toEqual(
|
||||
expect.objectContaining({
|
||||
gatewayReachable: false,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { withProgress } from "../../cli/progress.js";
|
||||
import { readConfigFileSnapshot } from "../../config/config.js";
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import { collectChannelStatusIssues } from "../../infra/channels-status-issues.js";
|
||||
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";
|
||||
@@ -29,6 +30,43 @@ 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formatChannelsStatusJsonError(err: unknown): string {
|
||||
return redactGatewayUrlSecretsInText(formatErrorMessage(err));
|
||||
}
|
||||
|
||||
export function formatGatewayChannelsStatusLines(payload: Record<string, unknown>): string[] {
|
||||
const lines: string[] = [];
|
||||
lines.push(theme.success("Gateway reachable."));
|
||||
@@ -190,7 +228,7 @@ export async function channelsStatusCommand(
|
||||
if (opts.json) {
|
||||
writeRuntimeJson(runtime, {
|
||||
gatewayReachable: false,
|
||||
error: String(err),
|
||||
error: formatChannelsStatusJsonError(err),
|
||||
configOnly: true,
|
||||
config: {
|
||||
path: snapshot.path,
|
||||
|
||||
Reference in New Issue
Block a user