From c93787fbce3b7f4e27c74193890eaa28d28235b5 Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Fri, 4 Sep 2026 14:14:33 +0530 Subject: [PATCH] fix(audit): redact payloads in store insert as last defence --- server/src/audit.ts | 5 ++++- server/tests/audit.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/server/src/audit.ts b/server/src/audit.ts index 22c75acb0..3e42b2b30 100644 --- a/server/src/audit.ts +++ b/server/src/audit.ts @@ -485,7 +485,10 @@ export async function recordAuditEvent( export function createAuditStore(database: Database): AuditStore { return { insert: async (event) => { - await database.insert(auditEvents).values(event); + await database.insert(auditEvents).values({ + ...event, + payload: redactAuditPayload(event.payload) as Record, + }); }, }; } diff --git a/server/tests/audit.test.ts b/server/tests/audit.test.ts index 22a7dedf5..d55c01ad9 100644 --- a/server/tests/audit.test.ts +++ b/server/tests/audit.test.ts @@ -3,6 +3,7 @@ import { readdir, readFile } from "node:fs/promises"; import { createApp } from "../src/app"; import { auditEventTypes, + createAuditStore, recordAuditEvent, redactAuditPayload, } from "../src/audit"; @@ -95,6 +96,30 @@ describe("audit payload redaction", () => { }, ]); }); + + test("a direct store insert is redacted too", async () => { + // Redaction used to live only in recordAuditEvent, so a direct insert() + // stored secrets in cleartext. The store is the last line of defence. + let stored: unknown; + const store = createAuditStore({ + insert: () => ({ + values: async (event: unknown) => { + stored = event; + }, + }), + } as never); + + await store.insert({ + eventType: "credential.created", + targetType: "credential", + targetId: "credential-1", + payload: { apiKey: "plaintext-key", provider: "openai" }, + } as never); + + expect(stored).toMatchObject({ + payload: { apiKey: "[REDACTED]", provider: "openai" }, + }); + }); }); describe("audit event immutability", () => {