From 396053d0814da0d495ed345d8a03312c735d1f53 Mon Sep 17 00:00:00 2001 From: David699 <33142294+David699@users.noreply.github.com> Date: Thu, 3 Sep 2026 00:00:39 +0800 Subject: [PATCH] fix: redact secrets from command previews Redact common token, password, secret, API key, authorization, and Bearer credential forms before structured command previews are emitted. Keep the surrounding command context useful while preventing credentials from entering logs. --- src/logger.test.ts | 15 +++++++++++++++ src/logger.ts | 6 +++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/logger.test.ts diff --git a/src/logger.test.ts b/src/logger.test.ts new file mode 100644 index 000000000..1d6ff3571 --- /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 c183ff64c..09e408dad 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 {