From 5fc9e37d224fc7a7e39e01b48c70293563a63e4a Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Thu, 3 Sep 2026 08:26:26 -0300 Subject: [PATCH] feat(video): redact transcript fields in the in-memory pending-request snapshot (#12430 item 6) trackPendingRequest (open-sse/handlers/chatCore.ts) stored the raw client body (with video transcript/audioTranscript cues) under `clientRequest`, live-exposed via /api/usage/call-logs (pendingDetails), /api/logs/[id] and /api/conversations while a request is in-flight. P2a redacted the persisted detailed-log snapshot but not this in-memory copy. Add redactPendingBody() to videoBridgeSnapshotRedaction.ts (sibling to logClientRawRequestRedacted from P2a): when videoBridgeObserved, returns the redacted clone from redactVideoTranscriptFieldsForLog; otherwise returns the exact same reference. Wire it into the trackPendingRequest call site (chatCore.ts:934), keeping the file within its frozen 5976-line budget (5971 -> 5974). --- open-sse/handlers/chatCore.ts | 7 ++- .../videoBridgeSnapshotRedaction.ts | 14 +++++ .../videoBridgeSnapshotRedaction.test.ts | 59 ++++++++++++++++++- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index d28711ce12..a50df1069b 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -360,7 +360,10 @@ import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity"; import { getCacheControlSettings } from "@/lib/cacheControlSettings"; import { guardrailRegistry } from "@/lib/guardrails"; import type { VideoBridgeLogRedactionEntry } from "@/lib/guardrails/videoBridge"; -import { logClientRawRequestRedacted } from "@/lib/guardrails/videoBridgeSnapshotRedaction"; +import { + logClientRawRequestRedacted, + redactPendingBody, +} from "@/lib/guardrails/videoBridgeSnapshotRedaction"; import { shouldPreserveCacheControl, resolveConnectionCacheOverride, @@ -928,7 +931,7 @@ export async function handleChatCore({ const pendingRequestId = trackPendingRequest(model, provider, pendingConnId, true, { clientEndpoint: clientRawRequest?.endpoint || "/v1/chat/completions", - clientRequest: clientRawRequest?.body ?? body, + clientRequest: redactPendingBody(clientRawRequest?.body ?? body, videoBridgeObserved), providerRequest: initialProviderRequest, stage: "registered", correlationId, diff --git a/src/lib/guardrails/videoBridgeSnapshotRedaction.ts b/src/lib/guardrails/videoBridgeSnapshotRedaction.ts index 8738b47cfc..8324b4be90 100644 --- a/src/lib/guardrails/videoBridgeSnapshotRedaction.ts +++ b/src/lib/guardrails/videoBridgeSnapshotRedaction.ts @@ -133,3 +133,17 @@ export function logClientRawRequestRedacted( clientRawRequest.headers ); } + +/** + * Call-site wrapper for the `clientRequest` field stored by `trackPendingRequest` + * (open-sse/handlers/chatCore.ts): the sibling in-memory leak to + * `logClientRawRequestRedacted` above — same raw body, but live-exposed via + * /api/usage/call-logs (pendingDetails), /api/logs/[id] and /api/conversations + * while the request is in-flight, not just in the persisted detailed-log + * snapshot. Identical observed/non-observed branching: a non-observed request + * keeps the exact same reference (no clone); an observed one gets the redacted + * clone. + */ +export function redactPendingBody(clientRequest: unknown, videoBridgeObserved: boolean): unknown { + return videoBridgeObserved ? redactVideoTranscriptFieldsForLog(clientRequest) : clientRequest; +} diff --git a/tests/unit/guardrails/videoBridgeSnapshotRedaction.test.ts b/tests/unit/guardrails/videoBridgeSnapshotRedaction.test.ts index 3f48bee7dc..8ca1dbab9f 100644 --- a/tests/unit/guardrails/videoBridgeSnapshotRedaction.test.ts +++ b/tests/unit/guardrails/videoBridgeSnapshotRedaction.test.ts @@ -7,7 +7,10 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { redactVideoTranscriptFieldsForLog } from "../../../src/lib/guardrails/videoBridgeSnapshotRedaction.ts"; +import { + redactVideoTranscriptFieldsForLog, + redactPendingBody, +} from "../../../src/lib/guardrails/videoBridgeSnapshotRedaction.ts"; // Heavy import is fine here (test only, never in the production module under test) — used // solely to prove the local placeholder literal never drifts from the canonical P1 constant. import { VIDEO_TRANSCRIPT_REDACTION_PLACEHOLDER } from "../../../src/lib/guardrails/videoBridgeHelpers.ts"; @@ -205,3 +208,57 @@ test("the redaction placeholder matches the canonical P1 constant (no drift)", ( const part = contentAt(result, "messages", 0)[0]; assert.equal(part.transcript, VIDEO_TRANSCRIPT_REDACTION_PLACEHOLDER); }); + +// #12430 item 6 (P2c): the sibling in-memory leak. `trackPendingRequest` +// (open-sse/handlers/chatCore.ts) stores the same raw client body under +// `clientRequest`, live-exposed via /api/usage/call-logs (pendingDetails), +// /api/logs/[id] and /api/conversations while the request is in-flight. This +// helper is the guarded call-site wrapper chatCore.ts uses, mirroring +// logClientRawRequestRedacted's observed/non-observed branching. +test("redactPendingBody: observed=true delegates to redactVideoTranscriptFieldsForLog", () => { + const body = { + messages: [ + { + role: "user", + content: [ + { + type: "input_video", + video_url: "https://example.com/clip.mp4", + transcript: { cues: [{ text: "pending secret" }] }, + }, + ], + }, + ], + }; + + const result = redactPendingBody(body, true); + assert.notEqual( + result, + body, + "observed path must return a new structure, not the same reference" + ); + const part = contentAt(result, "messages", 0)[0]; + assert.equal(part.transcript, VIDEO_TRANSCRIPT_REDACTION_PLACEHOLDER); + assert.ok(!JSON.stringify(result).includes("pending secret")); + assert.deepEqual(result, redactVideoTranscriptFieldsForLog(body)); +}); + +test("redactPendingBody: observed=false returns the SAME reference unchanged", () => { + const body = { + messages: [ + { + role: "user", + content: [ + { + type: "input_video", + video_url: "https://example.com/clip.mp4", + transcript: { cues: [{ text: "not observed" }] }, + }, + ], + }, + ], + }; + + const result = redactPendingBody(body, false); + assert.equal(result, body, "non-observed path must return the exact same reference"); +});