feat(ai): extend the prompt injection defense to streamed suggestions - #668
Open
Reversean wants to merge 1 commit into
Open
feat(ai): extend the prompt injection defense to streamed suggestions#668Reversean wants to merge 1 commit into
Reversean wants to merge 1 commit into
Conversation
Reversean
force-pushed
the
fix/ai-stream-prompt-injection
branch
2 times, most recently
from
July 29, 2026 16:21
a08550b to
18f7aa1
Compare
Reversean
force-pushed
the
fix/ai-stream-prompt-injection
branch
from
July 29, 2026 17:22
18f7aa1 to
571263c
Compare
The one-shot path scans the finished answer before returning it. A streamed answer leaves the server while it is still being written, so the same scan cannot be applied: a nonce split across two deltas passes a per-delta check untouched and the marker reaches the client. The stream now runs through a guard holding back the last nonce.length - 1 characters and scanning them together with each new delta, releasing only text that can no longer begin the nonce. That length is the exact minimum for an occurrence to always fall inside a single scanned window. The guard is applied to the model's typed stream parts rather than the encoded SSE bytes, where JSON envelopes and escaping would split the nonce beyond the reach of a substring scan. On detection the rest of the answer is replaced by the fallback message and the event ids are logged. The stream is not aborted: aborting obliges the caller to synthesize finish chunks whose shape follows the SDK version, and suppressing text keeps the stream well formed instead. What counts as a leak stays in the domain layer behind a port, so the provider adapter still knows nothing about it.
Reversean
force-pushed
the
fix/ai-stream-prompt-injection
branch
from
July 29, 2026 17:50
571263c to
e64c1a7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The one-shot path scans the finished answer before returning it. A stream cannot be checked
that way: it leaves the server while still being written, and a nonce split across two deltas
passes a per-chunk scan untouched.
Add a holdback guard. It withholds the last
nonce.length − 1characters and scanswithheld + new chunkon every delta, releasing only text that cannot be the start of thenonce. The bound is exact — for the nonce to straddle the send boundary, more than
nonce.length − 1characters after its first one must already be sent — so every occurrencefalls inside one scanned window and the nonce never reaches the client. Detection itself is
unchanged and shared with the one-shot path.
The guard runs in
streamText'sexperimental_transform, on typedTextStreamPartchunks:in the encoded SSE bytes the nonce is split by JSON envelopes and escaping, where no substring
search can find it. The withheld tail is released on
text-endrather than in theTransformStream's
flush, so deltas stay inside the block they belong to.stopStreamis not used: it requires synthesisingfinish-stepandfinishchunks whoseshape is tied to the SDK version. Suppressing text instead keeps the stream well-formed, at
the cost of tokens the model goes on producing unheard — paid only when the tripwire fires.
On detection the guard emits the fallback message once and stays silent, logging
eventIdand
originalEventId. The prefix already sent cannot be retracted, which is acceptable: thenonce is what triggered detection and is still withheld when it fires.
The transport takes a
StreamGuardport declared in the domain layer and does not know whata leak is.