fix(audit): redact camelCase toolResult and toolArguments in audit trail - #329
fix(audit): redact camelCase toolResult and toolArguments in audit trail#329Ayush7614 wants to merge 1 commit into
Conversation
Tool results and arguments were only redacted under snake_case tool_result/tool_arguments, but audit payloads also carry them as toolResult/toolArguments (direct MCP and computer tool calls). The normalized check fell through because the set lacked the stripped forms toolresult/toolarguments, so plaintext secrets leaked into audit_events.payload. Add the two missing normalized keys so both spellings are covered. Verified with redactAuditPayload unit probes: snake, camel, nested, and upper-case variants now all return [REDACTED].
kevin9327
left a comment
There was a problem hiding this comment.
The gap is real — I reproduced it on main: redactAuditPayload({ toolResult: "x" }) comes back unredacted, because normalizedKey (server/src/audit.ts:445-447) yields toolresult, and the set holds only tool_result. Two questions on the shape of the fix:
-
isSensitiveKey(audit.ts:449-454) already strips every non-alphanumeric and lowercases before the second lookup, so the snake_case spellings in the set are only ever matched by the first,key.toLowerCase(), and every key added since has had to be typed twice. Would normalising the set once at construction —new Set([...].map(normalizedKey))and a singlesensitiveKeys.has(normalizedKey(key))— close this class rather than this pair? The next key to land with one spelling misses the same way. -
The description says callers write
toolResult/toolArguments"(MCP and computer tool calls)". I could not find a writer:grep -rn "toolResult\|toolArguments" server/src app/src shared agent-computer/src agent-bot/srcmatches onlyaudit.tsitself onmain. Which payload did you see the camelCase key in? If it is a nested key inside a vendor's own result rather than one this codebase writes, that is worth saying, because it changes what the row was leaking.
Either way, a case in server/tests/audit.test.ts (which already exercises redactAuditPayload at ~line 54) pinning { toolResult: "x" } and { nested: { toolArguments: "x" } } would keep this from regressing when the set is next edited.
What this changes
Audit payloads were redacted only for snake_case
tool_result/tool_arguments, but callers also write them as camelCasetoolResult/toolArguments(MCP and computer tool calls). The sensitive-key set atserver/src/audit.ts:5-33contained both spellings for every other key (access_token+accesstoken,document_content+documentcontent, …) but missed the stripped formstoolresult/toolarguments.redactAuditPayload({toolResult: "secret"})fell through bothtoLowerCase()(toolresult) andnormalizedKey()(toolresult) — neither in the set — and was stored verbatim inaudit_events.payload. Nested payloads leaked the same way.This adds the two missing normalized keys so both spellings are covered:
Why it matters
audit_events.payloadis queryable by administrators and retained perAUDIT_RETENTION_DAYS. A vendor token, prompt, or document content that reaches the trail astoolResultwas stored in plaintext, violating the gateway's invariant that secrets never enter the transcript trail. The same gap lettoolArgumentscontaining credentials slip through wheretool_argumentswould have been redacted.Not urgent per row, but silent and wide: every MCP/computer tool call carries one of the two keys, and the leak is invisible until somebody queries the trail.
Proof
Inline probe against
redactAuditPayloadbefore/after:bun run typecheck·bun run lint·bun run format:checkall pass. No existing tests touchredactAuditPayloadcamelCase variants — this fills that gap without touching the read path.Checklist
Closes nothing yet — standalone hardening.