From acd3d46ab4e2ec136760b69351757200fc5e0018 Mon Sep 17 00:00:00 2001 From: Austin Liu <193228693+Dingding-leo@users.noreply.github.com> Date: Thu, 23 Jul 2026 23:36:51 +0930 Subject: [PATCH] fix(sanitizer): strip zero-width chars from Anthropic-native streaming text_delta (#8271) (#8287) sanitizeStreamingChunk() only stripped zero-width characters (U+200B, U+200C, U+200D, U+FEFF) from OpenAI-format choices[].delta.content. Anthropic-native content_block_delta events with text_delta or thinking_delta payloads bypassed that path entirely, leaking U+200D to clients on the Messages API streaming route. Add a content_block_delta branch that strips zero-width characters from delta.text and delta.thinking before returning the event, matching the existing OpenAI path behavior from #5857. Co-authored-by: Austin Liu --- open-sse/handlers/responseSanitizer.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/open-sse/handlers/responseSanitizer.ts b/open-sse/handlers/responseSanitizer.ts index 0e2ebd880c..140011f64e 100644 --- a/open-sse/handlers/responseSanitizer.ts +++ b/open-sse/handlers/responseSanitizer.ts @@ -1011,6 +1011,23 @@ export function sanitizeStreamingChunk(parsed: unknown): unknown { return sanitizeResponsesStreamingEvent(parsedRecord); } + // #8271: Anthropic-native streaming events (content_block_delta with + // text_delta / thinking_delta) bypass the OpenAI choices[].delta.content + // path below. Strip zero-width characters from their text payloads so + // U+200D and friends don't leak to the client on the Messages API. + if (eventType === "content_block_delta") { + const deltaRecord = toRecord(parsedRecord.delta); + if (deltaRecord) { + if (typeof deltaRecord.text === "string") { + deltaRecord.text = stripZeroWidthText(deltaRecord.text); + } + if (typeof deltaRecord.thinking === "string") { + deltaRecord.thinking = stripZeroWidthText(deltaRecord.thinking); + } + } + return parsedRecord; + } + // Build sanitized chunk const sanitized: JsonRecord = {};