diff --git a/packages/agent/src/session-log-writer.test.ts b/packages/agent/src/session-log-writer.test.ts index 2a492b58ac..f693140025 100644 --- a/packages/agent/src/session-log-writer.test.ts +++ b/packages/agent/src/session-log-writer.test.ts @@ -47,6 +47,45 @@ describe("SessionLogWriter", () => { expect(entries).toHaveLength(2); }); + it("redacts MCP authorization headers before persistence", async () => { + const sessionId = "s1"; + logWriter.register(sessionId, { taskId: "t1", runId: sessionId }); + + logWriter.appendRawLine( + sessionId, + JSON.stringify({ + jsonrpc: "2.0", + method: "session/new", + params: { + mcpServers: [ + { + name: "posthog", + headers: [ + { name: "Authorization", value: "Bearer protocol-secret" }, + { name: "x-posthog-project-id", value: "123" }, + ], + }, + ], + }, + }), + ); + await logWriter.flush(sessionId); + + const entries: StoredNotification[] = mockAppendLog.mock.calls[0][2]; + expect(JSON.stringify(entries)).not.toContain("protocol-secret"); + expect(entries[0].notification.params).toEqual({ + mcpServers: [ + { + name: "posthog", + headers: [ + { name: "Authorization", value: "[REDACTED]" }, + { name: "x-posthog-project-id", value: "123" }, + ], + }, + ], + }); + }); + it("ignores unregistered sessions", async () => { logWriter.appendRawLine("unknown", JSON.stringify({ method: "test" })); await logWriter.flush("unknown"); diff --git a/packages/agent/src/session-log-writer.ts b/packages/agent/src/session-log-writer.ts index a11566902c..56bf584401 100644 --- a/packages/agent/src/session-log-writer.ts +++ b/packages/agent/src/session-log-writer.ts @@ -73,6 +73,31 @@ interface SessionState { pendingRawInputSnapshots: Map; } +function redactAuthorizationHeaders(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map(redactAuthorizationHeaders); + } + if (value === null || typeof value !== "object") { + return value; + } + + const record = value as Record; + if ( + typeof record.name === "string" && + record.name.toLowerCase() === "authorization" && + "value" in record + ) { + return { ...record, value: "[REDACTED]" }; + } + + return Object.fromEntries( + Object.entries(record).map(([key, nestedValue]) => [ + key, + redactAuthorizationHeaders(nestedValue), + ]), + ); +} + export class SessionLogWriter { /** * When consecutive in-progress tool updates for one call span more than this @@ -212,7 +237,9 @@ export class SessionLogWriter { const entry: StoredNotification = { type: "notification", timestamp, - notification: message, + notification: redactAuthorizationHeaders( + message, + ) as StoredNotification["notification"], }; this.emitToSinks(sessionId, entry);