diff --git a/src/logger.test.ts b/src/logger.test.ts new file mode 100644 index 00000000..1d6ff357 --- /dev/null +++ b/src/logger.test.ts @@ -0,0 +1,15 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { commandPreview } from "./logger.js"; + +test("command previews redact common secret forms while preserving useful context", () => { + const preview = commandPreview( + "deploy --token abc123 --password=hidden API_KEY=key123 -authorization BearerToken curl -H 'Authorization: Bearer xyz789'", + ); + + assert.match(preview, /deploy/); + assert.match(preview, /--token \[REDACTED\]/); + assert.match(preview, /--password=\[REDACTED\]/); + assert.match(preview, /API_KEY=\[REDACTED\]/); + assert.doesNotMatch(preview, /abc123|hidden|key123|xyz789/); +}); diff --git a/src/logger.ts b/src/logger.ts index c183ff64..09e408da 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -74,7 +74,11 @@ export function sessionIdPrefix(sessionId: string | undefined): string | undefin export function commandPreview(command: string): string { const normalized = command.replace(/\s+/g, " ").trim(); - return normalized.length > 120 ? `${normalized.slice(0, 117)}...` : normalized; + const redacted = normalized + .replace(/((?:--?|\/)(?:token|password|secret|api[-_]?key|authorization)(?:=|\s+))["']?[^\s"']+["']?/giu, "$1[REDACTED]") + .replace(/\b((?:token|password|secret|api[-_]?key|authorization)\s*=\s*)["']?[^\s"']+["']?/giu, "$1[REDACTED]") + .replace(/\b(Bearer\s+)[A-Za-z0-9._~+\/-]+=*/giu, "$1[REDACTED]"); + return redacted.length > 120 ? `${redacted.slice(0, 117)}...` : redacted; } function firstHeaderValue(value: string | undefined): string | undefined {