fix(stream): scan the whole new chunk, not just the window tail - #4
Merged
Conversation
The streaming guard scanned only the last `window` (8192) chars of the accumulated buffer on every push. When a single push is larger than the window — e.g. a non-streaming completion fed through `guardTextStream` as one chunk, a documented "any async iterable of strings" use — the leading bytes of that chunk were never scanned, so a secret or PII near the start slipped through. Detection depended on chunk size, not content: identical text was blocked when split into small chunks but passed as one big chunk (reproduced). Scan the last `chunk.length + window` chars instead: the full chunk just appended (its leading bytes are always inspected) plus `window` chars of prior context for cross-chunk straddles. Per-push cost stays O(window) for normal token-sized chunks and is O(window + chunk) only for an outsized one. Adds 2 regression tests: PII in a single >window chunk is blocked, and the verdict is independent of chunk size for identical content. Co-authored-by: mattia-mamini-gh <281593356+mattia-mamini-gh@users.noreply.github.com>
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.
Why
createStreamGuard/guardTextStreamscan only the lastwindow(8192) chars of the accumulated buffer on eachpush(). The window is anchored to the tail of the buffer, so when a single push is larger than the window the leading bytes of that chunk are never scanned.This is a realistic path, not a contrived one: the guard is documented to compose with "any async iterable of strings", so adapting a non-streaming completion into it yields the entire output as one chunk. A secret/PII near the start of a >8 KB answer then slips through.
Reproduced — identical content, opposite verdicts:
"Contact alice@example.com " + 8300×"x"as one chunkblocked = false❌ (PII leaks)blocked = true✅Detection depended on chunk size, not content.
What
Scan the last
chunk.length + windowchars: the full chunk just appended (leading bytes always inspected) pluswindowchars of prior context to preserve cross-chunk straddle detection (<scr+ipt>). Per-push cost stays O(window) for normal token-sized chunks and is O(window + chunk) only for an outsized single push — the cost of actually inspecting content you just emitted.Verification
npm run typecheck✅ ·npm run lint✅ ·npm test→ 307 passed / 1 skipped ✅