From 8ac764e3b888fbd225b359fd757ca525f5f54588 Mon Sep 17 00:00:00 2001 From: Arnas Donauskas Date: Fri, 22 May 2026 15:35:42 +0300 Subject: [PATCH] fix(flows): redact customer text from reply_received event payload The `reply_received` event was persisting `text: message.text` verbatim into flow_run_events.payload. A customer prompted "what's your card number?" by a collect_input node would leave the PAN sitting in the events table forever, surfaced via the runs viewer to anyone with access. Switch to `text_length` only. The `node_entered` event already records `captured_length` for the same reason after a successful capture; this makes the asymmetry symmetric. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/flows/engine.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/flows/engine.ts b/src/lib/flows/engine.ts index 069c98a..612709d 100644 --- a/src/lib/flows/engine.ts +++ b/src/lib/flows/engine.ts @@ -822,11 +822,18 @@ async function handleReplyForActiveRun( run: FlowRunRow, message: ParsedInbound, ): Promise { + // Note: we intentionally do NOT persist the raw customer text. A + // `collect_input` prompt that asks "what's your card number?" would + // otherwise leave the PAN sitting in flow_run_events.payload forever, + // visible to anyone with access to the runs viewer or the events + // table. Length is enough for "did they actually reply?" debugging; + // for the captured value itself, the `node_entered` event already + // records `captured_key` + `captured_length` after the var is stored. await logEvent(db, run.id, "reply_received", run.current_node_key, { meta_message_id: message.meta_message_id, reply_kind: message.kind, reply_id: message.kind === "interactive_reply" ? message.reply_id : null, - text: message.kind === "text" ? message.text : null, + text_length: message.kind === "text" ? message.text.length : null, }); if (!run.current_node_key) {