mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix(compaction): stop retaining credential-like values (#67801)
This commit is contained in:
@@ -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<string, unknown>;
|
||||
const message = ((event.payload as { messages?: unknown[] } | undefined)?.messages ??
|
||||
[]) as Array<Record<string, unknown>>;
|
||||
const sanitizedPayload = (event.payload ?? {}) as Record<string, unknown>;
|
||||
const message = ((sanitizedPayload.messages as unknown[] | undefined) ?? []) as Array<
|
||||
Record<string, unknown>
|
||||
>;
|
||||
const source = (((message[0]?.content as Array<Record<string, unknown>> | undefined) ?? [])[0]
|
||||
?.source ?? {}) as Record<string, unknown>;
|
||||
const metadata = (sanitizedPayload.metadata ?? {}) as Record<string, unknown>;
|
||||
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("<redacted>");
|
||||
expect(source.bytes).toBe(4);
|
||||
expect(source.sha256).toBe(crypto.createHash("sha256").update("QUJDRA==").digest("hex"));
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user