Gateway: suppress NO_REPLY lead-fragment chat leaks#32116
Conversation
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🔵 Overbroad NO_REPLY lead-fragment suppression hides legitimate assistant streaming deltas (e.g., "No")
Description
This creates unintended content filtering / output integrity issues for legitimate short replies because:
Concrete examples of legitimate assistant outputs whose streaming deltas will be suppressed:
While the final message may still be delivered on lifecycle end, any client behavior that relies on deltas for timely display/logging/audit (or where final events are delayed/lost) will experience a denial-of-service style degradation and unexpected filtering triggered purely by prompting the assistant to begin with these strings. Vulnerable code: function isSilentReplyLeadFragment(text: string): boolean {
const normalized = text.trim().toUpperCase();
...
return SILENT_REPLY_TOKEN.startsWith(normalized);
}And the suppression is applied here: if (isSilentReplyLeadFragment(cleaned)) {
return;
}RecommendationNarrow the matching so only actual silent-token lead fragments produced by the system are suppressed, without catching common legitimate text. Options (choose one):
function isSilentReplyLeadFragment(text: string): boolean {
const normalized = text.trim().toUpperCase();
if (!normalized) return false;
// Only suppress fragments that look like the control token (contain '_')
if (!normalized.includes("_")) return false;
if (!/^[A-Z_]+$/.test(normalized)) return false;
if (normalized === SILENT_REPLY_TOKEN) return false;
return SILENT_REPLY_TOKEN.startsWith(normalized);
}
Also add tests asserting that delta events for common replies like "No"/"NO" are (or are not) emitted according to the desired behavior. Analyzed PR: #32116 at commit Last updated on: 2026-03-02T20:12:39Z |
Greptile SummaryThis PR fixes a bug where streaming Key changes:
Technical approach: The solution buffers all text regardless of whether it's a lead fragment, then suppresses only the delta emission. This ensures the final message at lifecycle end has the complete text to determine if it's truly Confidence Score: 5/5
Last reviewed commit: b9c7e65 |
|
Landed via temp rebase onto main.
Thanks @CONTRIB_PLACEHOLDER! |
|
Correction: landed details.
Thanks @liuxiaopai-ai! |
Summary
Describe the problem and fix in 2–5 bullets:
NObefore later chunks complete toNO_REPLY, and the cached final buffer can then surfaceNOas a visible assistant message on lifecycle end/refresh (NO_REPLY control token partially stripped — renders as "NO" in webchat after v2026.3.1 upgrade #32073).NOreplies from control-token flows.NO_REPLYlead-fragment detection in gateway chat event handling; lead fragments are still buffered but no delta is emitted until content disambiguates.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
NOfromNO_REPLYlead fragments in silent-response runs.Noare still preserved in final messages.Security Impact (required)
Yes/No) NoYes/No) NoYes/No) NoYes/No) NoYes/No) NoYes, explain risk + mitigation:Repro + Verification
Environment
Steps
NO,NO_,NO_RE,NO_REPLY.chatfinal payload.Expected
Actual
chatpayload hasmessage: undefined; no strayNOis emitted.Evidence
Attach at least one:
Human Verification (required)
What you personally verified (not just CI), and how:
pnpm exec vitest run src/gateway/server-chat.agent-events.test.tspnpm lint src/gateway/server-chat.ts src/gateway/server-chat.agent-events.test.ts CHANGELOG.mdpnpm exec oxfmt --check src/gateway/server-chat.ts src/gateway/server-chat.agent-events.test.ts CHANGELOG.mdNO/NO_/NO_RE/NO_REPLYprogression suppresses leakNoremains visibleCompatibility / Migration
Yes/No) YesYes/No) NoYes/No) NoFailure Recovery (if this breaks)
src/gateway/server-chat.ts,src/gateway/server-chat.agent-events.test.ts,CHANGELOG.mdNO_REPLY.Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write
None.N/NOmay defer delta emission until disambiguated.Nostill renders in final).