import { describe, expect, test } from "vitest"; import { isSensitiveLogKey, REDACTED_PLACEHOLDER, redactedJsonStringify, } from "#src/log-redaction"; describe("isSensitiveLogKey", () => { test.each([ "authorization", "Authorization", "apiKey", "api_key", "api-key", "x-api-key", "ANTHROPIC_API_KEY", "secret", "clientSecret", "token", "accessToken", "refresh_token", "password", "passwd", "credential", "credentials", "cookie", "privateKey", "private_key", ])("treats %s as sensitive", (key) => { expect(isSensitiveLogKey(key)).toBe(true); }); test.each([ "toolName", "command", "path", "target", "origin", "matchedPattern", "resolution", "toolInputPreview", "requesterAgentName", "denialReason", "", ])("treats %s as not sensitive", (key) => { expect(isSensitiveLogKey(key)).toBe(false); }); }); describe("redactedJsonStringify", () => { test("masks a top-level sensitive value", () => { expect(redactedJsonStringify({ token: "abc123" })).toBe( `{"token":"${REDACTED_PLACEHOLDER}"}`, ); }); test("masks a nested sensitive value", () => { const details = { toolName: "http", headers: { authorization: "Bearer TEST_VALUE" }, }; expect(redactedJsonStringify(details)).toBe( `{"toolName":"http","headers":{"authorization":"${REDACTED_PLACEHOLDER}"}}`, ); }); test("masks a sensitive value inside an array element", () => { const details = { entries: [{ name: "prod", apiKey: "sk-real-value" }] }; expect(redactedJsonStringify(details)).toBe( `{"entries":[{"name":"prod","apiKey":"${REDACTED_PLACEHOLDER}"}]}`, ); }); test("masks an object-valued sensitive key without descending into it", () => { const details = { credentials: { user: "root", password: "hunter2" } }; expect(redactedJsonStringify(details)).toBe( `{"credentials":"${REDACTED_PLACEHOLDER}"}`, ); }); test("leaves non-sensitive keys untouched", () => { const details = { toolName: "bash", command: "echo hello", matchedPattern: "echo *", }; expect(redactedJsonStringify(details)).toBe( '{"toolName":"bash","command":"echo hello","matchedPattern":"echo *"}', ); }); test("leaves a null or absent sensitive value as-is rather than reading as suppressed", () => { expect(redactedJsonStringify({ token: null })).toBe('{"token":null}'); expect(redactedJsonStringify({ token: undefined })).toBe("{}"); }); test("retains the Error, bigint, and cycle handling of the plain serializer", () => { const error = new Error("boom"); error.stack = "trace"; const node: Record = { size: 10n, error }; node.self = node; expect(redactedJsonStringify(node)).toBe( '{"size":"10","error":{"name":"Error","message":"boom","stack":"trace"},"self":"[Circular]"}', ); }); });