Skip to content

feat(ai): guard streamed output in the Vercel AI SDK middleware (wrapStream) - #5

Merged
aymandakirgh merged 1 commit into
mainfrom
feat/ai-middleware-wrapstream
Jun 18, 2026
Merged

feat(ai): guard streamed output in the Vercel AI SDK middleware (wrapStream)#5
aymandakirgh merged 1 commit into
mainfrom
feat/ai-middleware-wrapstream

Conversation

@aymandakirgh

Copy link
Copy Markdown
Owner

Why

aegisMiddleware() implemented transformParams (input scan) and wrapGenerate (non-streaming output scan) but not wrapStream. In the Vercel AI SDK, streamText() — the most common production path — routes through the middleware's wrapStream hook, not wrapGenerate. So when a model streams, every output guard (LLM02 PII, LLM05 improper-output, LLM06 disclosure, LLM08 agency) was silently bypassed even though the middleware was attached, and the file's docstring's claim that it scans "the generated text after" was false for streaming.

For a security middleware, silently not guarding the most-used path is a serious gap.

What

  • Implement wrapStream: pipe the provider's stream through a TransformStream that feeds text-delta chunks into the existing sliding-window stream guard (createStreamGuard), and controller.error(AegisBlockedError) the moment a detector trips — before the offending delta is emitted. Non-text parts pass through untouched.
  • This reuses the same guard as guardTextStream, so it catches violations that straddle chunk boundaries (e.g. admin@ + example.com), and mirrors wrapGenerate's fail-closed throw-on-violation semantics.
  • Docstring updated to state both generateText and streamText are covered.

Verification

Two new tests (benign streamed output passes through unchanged; PII completing across deltas aborts the stream). Full suite green: typecheck · lint · test (310 pass) · build · bench (thresholds met, 0% FP) · perf (p95 within budget).

🤖 Generated with Claude Code

aegisMiddleware() implemented transformParams (input) and wrapGenerate
(non-streaming output) but not wrapStream. streamText() — the most common
production path — routes through wrapStream, so every output guard
(LLM02 PII, LLM05 improper-output, LLM06 disclosure, LLM08 agency) was
silently bypassed for streamed responses, even though the middleware was
attached and its docstring claimed it scanned generated text.

Add wrapStream: it pipes the provider's stream through a TransformStream
that feeds text deltas into the existing sliding-window stream guard
(createStreamGuard), erroring the stream with AegisBlockedError the moment
a detector trips — before the offending delta reaches the consumer. This
mirrors the fail-closed semantics of wrapGenerate and catches violations
that straddle chunk boundaries.

Adds tests: benign streamed output passes through unchanged; PII that
completes across deltas aborts the stream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aymandakirgh
aymandakirgh merged commit b855290 into main Jun 18, 2026
3 checks passed
@aymandakirgh
aymandakirgh deleted the feat/ai-middleware-wrapstream branch June 18, 2026 08:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant