From 8b7d76bfbb0b510f04d5287b3de2039eb2eb61de Mon Sep 17 00:00:00 2001 From: Devin Robison Date: Thu, 16 Apr 2026 14:04:45 -0600 Subject: [PATCH] fix(compaction): stop retaining credential-like values (#67801) --- src/agents/anthropic-payload-log.test.ts | 19 ++++++++++++++++--- src/agents/anthropic-payload-log.ts | 4 ++-- .../compaction.identifier-policy.test.ts | 2 ++ ...compaction.identifier-preservation.test.ts | 4 ++++ src/agents/compaction.ts | 2 +- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/agents/anthropic-payload-log.test.ts b/src/agents/anthropic-payload-log.test.ts index fb3cf18e47d..e4eb40a3c0e 100644 --- a/src/agents/anthropic-payload-log.test.ts +++ b/src/agents/anthropic-payload-log.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; import { createAnthropicPayloadLogger } from "./anthropic-payload-log.js"; describe("createAnthropicPayloadLogger", () => { - it("redacts image base64 payload data before writing logs", async () => { + it("sanitizes credential fields and image base64 payload data before writing logs", async () => { const lines: string[] = []; const logger = createAnthropicPayloadLogger({ env: { OPENCLAW_ANTHROPIC_PAYLOAD_LOG: "1" }, @@ -19,6 +19,7 @@ describe("createAnthropicPayloadLogger", () => { messages: [ { role: "user", + authorization: "Bearer sk-secret", // pragma: allowlist secret content: [ { type: "image", @@ -27,6 +28,11 @@ describe("createAnthropicPayloadLogger", () => { ], }, ], + metadata: { + api_key: "sk-test", // pragma: allowlist secret + nestedToken: "shh", // pragma: allowlist secret + tokenBudget: 1024, + }, }; const streamFn: StreamFn = ((model, __, options) => { options?.onPayload?.(payload, model); @@ -37,10 +43,17 @@ describe("createAnthropicPayloadLogger", () => { await wrapped?.({ api: "anthropic-messages" } as never, { messages: [] } as never, {}); const event = JSON.parse(lines[0]?.trim() ?? "{}") as Record; - const message = ((event.payload as { messages?: unknown[] } | undefined)?.messages ?? - []) as Array>; + const sanitizedPayload = (event.payload ?? {}) as Record; + const message = ((sanitizedPayload.messages as unknown[] | undefined) ?? []) as Array< + Record + >; const source = (((message[0]?.content as Array> | undefined) ?? [])[0] ?.source ?? {}) as Record; + const metadata = (sanitizedPayload.metadata ?? {}) as Record; + expect(message[0]).not.toHaveProperty("authorization"); + expect(metadata).not.toHaveProperty("api_key"); + expect(metadata).not.toHaveProperty("nestedToken"); + expect(metadata.tokenBudget).toBe(1024); expect(source.data).toBe(""); expect(source.bytes).toBe(4); expect(source.sha256).toBe(crypto.createHash("sha256").update("QUJDRA==").digest("hex")); diff --git a/src/agents/anthropic-payload-log.ts b/src/agents/anthropic-payload-log.ts index 2eb5d62e770..ed060e4804a 100644 --- a/src/agents/anthropic-payload-log.ts +++ b/src/agents/anthropic-payload-log.ts @@ -7,7 +7,7 @@ import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveUserPath } from "../utils.js"; import { parseBooleanValue } from "../utils/boolean.js"; import { safeJsonStringify } from "../utils/safe-json.js"; -import { redactImageDataForDiagnostics } from "./payload-redaction.js"; +import { sanitizeDiagnosticPayload } from "./payload-redaction.js"; import { getQueuedFileWriter, type QueuedFileWriter } from "./queued-file-writer.js"; type PayloadLogStage = "request" | "usage"; @@ -137,7 +137,7 @@ export function createAnthropicPayloadLogger(params: { return streamFn(model, context, options); } const nextOnPayload = (payload: unknown) => { - const redactedPayload = redactImageDataForDiagnostics(payload); + const redactedPayload = sanitizeDiagnosticPayload(payload); record({ ...base, ts: new Date().toISOString(), diff --git a/src/agents/compaction.identifier-policy.test.ts b/src/agents/compaction.identifier-policy.test.ts index 23c199236af..b949e7dfd14 100644 --- a/src/agents/compaction.identifier-policy.test.ts +++ b/src/agents/compaction.identifier-policy.test.ts @@ -6,6 +6,8 @@ describe("compaction identifier policy", () => { const built = buildCompactionSummarizationInstructions(); expect(built).toContain("Preserve all opaque identifiers exactly as written"); expect(built).toContain("UUIDs"); + expect(built).not.toContain("tokens"); + expect(built).not.toContain("API keys"); }); it("can disable identifier preservation with off policy", () => { diff --git a/src/agents/compaction.identifier-preservation.test.ts b/src/agents/compaction.identifier-preservation.test.ts index 77868bdaffc..957180c72a9 100644 --- a/src/agents/compaction.identifier-preservation.test.ts +++ b/src/agents/compaction.identifier-preservation.test.ts @@ -71,6 +71,8 @@ describe("compaction identifier-preservation instructions", () => { expect(firstSummaryInstructions()).toContain("UUIDs"); expect(firstSummaryInstructions()).toContain("IPs"); expect(firstSummaryInstructions()).toContain("ports"); + expect(firstSummaryInstructions()).not.toContain("tokens"); + expect(firstSummaryInstructions()).not.toContain("API keys"); }); it("keeps identifier-preservation guidance when custom instructions are provided", async () => { @@ -139,6 +141,8 @@ describe("buildCompactionSummarizationInstructions", () => { const result = buildCompactionSummarizationInstructions(); expect(result).toContain("Preserve all opaque identifiers exactly as written"); expect(result).not.toContain("Additional focus:"); + expect(result).not.toContain("tokens"); + expect(result).not.toContain("API keys"); }); it("appends custom instructions in a stable format", () => { diff --git a/src/agents/compaction.ts b/src/agents/compaction.ts index 3704c8f481e..00b97d8fbac 100644 --- a/src/agents/compaction.ts +++ b/src/agents/compaction.ts @@ -37,7 +37,7 @@ const MERGE_SUMMARIES_INSTRUCTIONS = [ ].join("\n"); const IDENTIFIER_PRESERVATION_INSTRUCTIONS = "Preserve all opaque identifiers exactly as written (no shortening or reconstruction), " + - "including UUIDs, hashes, IDs, tokens, API keys, hostnames, IPs, ports, URLs, and file names."; + "including UUIDs, hashes, IDs, hostnames, IPs, ports, URLs, and file names."; export type CompactionSummarizationInstructions = { identifierPolicy?: AgentCompactionIdentifierPolicy;