From f79f18acbf5adfcb780c12c41b4eccd6600d85d5 Mon Sep 17 00:00:00 2001 From: Amp Date: Fri, 21 Aug 2026 05:08:00 +0000 Subject: [PATCH] fix(health): redact failure diagnostics --- src/commands/health-format.ts | 5 +++-- src/commands/health.test.ts | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/commands/health-format.ts b/src/commands/health-format.ts index 0105dd2defbe..b31aa4e753e8 100644 --- a/src/commands/health-format.ts +++ b/src/commands/health-format.ts @@ -6,6 +6,7 @@ import { colorize, isRich, theme } from "../../packages/terminal-core/src/theme. import { formatChannelStatusState } from "../channels/plugins/status-state.js"; import { isGatewayTransportError } from "../gateway/call.js"; import type { ChannelAccountHealthSummary, HealthSummary } from "../gateway/health/types.js"; +import { redactSensitiveText } from "../logging/redact.js"; export function formatGatewayClosedDiagnostic(err: unknown): string | undefined { if (!isGatewayTransportError(err) || err.kind !== "closed" || err.code === undefined) { @@ -35,8 +36,8 @@ const formatKv = (line: string, rich: boolean) => { /** Formats thrown health errors with rich detail lines when terminal color is enabled. */ export function formatHealthCheckFailure(err: unknown, opts: { rich?: boolean } = {}): string { const rich = opts.rich ?? isRich(); - const raw = String(err); - const message = err instanceof Error ? err.message : raw; + const raw = redactSensitiveText(String(err)); + const message = err instanceof Error ? redactSensitiveText(err.message) : raw; if (!rich) { return `Health check failed: ${raw}`; diff --git a/src/commands/health.test.ts b/src/commands/health.test.ts index ec4b83f42395..608f5c2755b4 100644 --- a/src/commands/health.test.ts +++ b/src/commands/health.test.ts @@ -1,7 +1,9 @@ // Health command tests cover gateway health probes, JSON output, and status formatting. -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { GatewayClientRequestError } from "../../packages/gateway-client/src/index.js"; import { stripAnsi } from "../../packages/terminal-core/src/ansi.js"; +import { registerSecretValueForRedaction } from "../logging/secret-redaction-registry.js"; +import { resetSecretRedactionRegistryForTest } from "../logging/secret-redaction-registry.test-support.js"; import { ExitError } from "../runtime.js"; import { buildCredentialsRequiredHealthDiagnostic, @@ -26,6 +28,10 @@ const runtime = { exit: vi.fn(), }; +afterEach(() => { + resetSecretRedactionRegistryForTest(); +}); + const defaultSessions: HealthSummary["sessions"] = { path: "/tmp/sessions.json", count: 0, @@ -908,6 +914,18 @@ describe("formatConfigReloadHealthLine", () => { }); describe("formatHealthCheckFailure", () => { + it.each([false, true])("redacts registered secrets from rich=%s output", (rich) => { + const registeredSecret = "qa-health-check-secret"; + registerSecretValueForRedaction(registeredSecret); + + const output = formatHealthCheckFailure(new Error(`gateway failed with ${registeredSecret}`), { + rich, + }); + + expect(output).toContain("gateway failed with"); + expect(output).not.toContain(registeredSecret); + }); + it("keeps non-rich output stable", () => { const err = new Error("gateway closed (1006 abnormal closure): no close reason"); expect(formatHealthCheckFailure(err, { rich: false })).toBe(