Skip to main content
If your app streams, you need one straight answer before you trust a content policy: what actually gets enforced on an SSE response. A guardrail that inspects a whole reply is easy to reason about; a guardrail that has to act on deltas as they flush to the browser is not. This page is the streaming guardrail coverage matrix — every action, across the input and output stages, on streaming and non-streaming traffic — written to be precise about how each cell behaves on a live stream. For the full engine — every rule type, field, and route — see Guardrails. For the mechanics of how a stream gets cut, see stream-safe rules.

1. The streaming guardrail coverage question

A guardrail rule carries a stage (input, output, or both) and an action — one of five: block, mask, flag, annotate, or spotlight. The stage decides when the gateway runs it; the action decides what it does. The only place streaming changes the shape of the answer is the output stage — because that’s the only stage where the gateway is forwarding bytes to your client as they arrive, with no chance to hold the whole payload first. So the matrix has two cells where streaming matters, and they behave differently: output block is fully enforced on a stream (the scanner cuts it), but output mask is enforced on non-streaming replies only. On a streaming reply the scanner still detects the match and can act on a block decision, but it does not rewrite the masked text into the stream today — in-band streaming output masking is on the roadmap.
Input is never the problem. Input-stage rules run before the model — the gateway screens (and, for mask, rewrites) your request, then forwards the sanitized version upstream. Whether the response will stream is irrelevant; the request is a complete payload the gateway holds in full. Input scanning is fully live, masking included, on every request.

2. The coverage matrix

Read this top to bottom. Every block cell is live, streaming included — but output + mask + streaming is the one cell that is not yet enforced in the stream: a mask rule redacts a non-streaming reply, and on a streaming reply it detects the match without rewriting the delta (in-stream output masking is on the roadmap).
Stage · actionNon-streamingStreaming
input · blockrejects requestrejects request
input · maskrewrites requestrewrites request
output · blockrejects replycuts the stream
output · maskredacts replydetects match; does not redact in-stream (roadmap)
any · flagrecords onlyrecords only
annotate and spotlight attach a note (or wrap matched text) without rejecting traffic and are input-stage actions in practice, so they don’t change the output/streaming cells above; they record a match like any other rule.
Input-stage rules screen the request before the upstream model runs. A block short-circuits the call (HTTP 400 guardrail_blocked, before metering, so it costs no quota). A mask rewrites the matched fields in the prompt in place — the sanitized text is what goes upstream, and the model never sees the original. None of this depends on whether the response streams.
On a non-streaming reply the completion is screened in full before it returns — a block surfaces as HTTP 400 guardrail_blocked. On a streaming reply a stream scanner watches the deltas as they flow; when a block rule fires it cuts the stream — seals the scanner, emits a short replacement notice in place of the rest, and closes the SSE channel before further blocked content reaches the client. Because the 200 SSE headers have already gone out by then, a streaming block can’t return a 400: it delivers the block as a final in-band delta rather than an HTTP error.
On a non-streaming reply a mask rule rewrites the completion — e.g. an email becomes [EMAIL] — and the sanitized text is what your client gets. On a streaming reply the stream scanner still detects the match and computes the mask, but it does not forward the masked text into the delta — the masked output is discarded and only a block decision is acted on. So a mask rule does not redact a streaming reply today; in-band streaming output masking is on the roadmap. If you need PII kept out of a streamed reply right now, author the rule as block (the scanner cuts the stream on match) or screen non-streaming.
A flag rule never alters traffic — it records a match and lets the bytes through. Stage and stream don’t change its behavior. Use it to measure a rule’s hit rate before you promote it to block.
The one line to remember: output block is enforced live on both transports — streaming included — and input masking is always live. Output mask redacts non-streaming replies only; on a stream it detects the match but does not yet rewrite the delta (in-stream output masking is on the roadmap). To keep PII out of a streamed reply today, author the rule as block or screen non-streaming.

3. One concrete example — keep PII out of a streamed reply

Say the model can surface a customer email from RAG context, and your app streams. Output mask does not redact in the stream today (in-stream output masking is on the roadmap) — so to keep PII out of a streamed reply, author the output rule as block: the scanner kills the stream the moment a match appears. (Output mask does redact on a non-streaming reply.) Policy editing is a management action on your console session (gated to Developer+); the sk-orca-... relay key only sends /v1/* traffic and never edits policy.
  • Open /console/guardrails, New guardrail, name it stream-pii-out.
  • Add one rule:
    • Type: PII detection
    • Stage: output
    • Action: block ← cuts the stream on match; on a stream mask only detects (it redacts non-streaming replies)
  • Save, then attach it on /console/token via the key’s Guardrail dropdown.
Now call the gateway with stream: true, exactly as before:
curl https://api.orcarouter.ai/v1/chat/completions \
  -H "Authorization: Bearer sk-orca-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "stream": true,
    "messages": [
      {"role": "user", "content": "Email the customer from the record above"}
    ]
  }'
If a delta matches, the scanner cuts the stream, emits a replacement notice, and closes the channel — the client never receives the rest. If the reply is clean, every delta streams through untouched.
A streaming block stops everything after the match, but it cannot un-send bytes already flushed before the match landed. If your policy demands that not one offending byte ever reaches the client, screen the request non-streaming, where the whole completion is held until the policy clears it.

4. PII Shield across the matrix

The PII Shield preset is a single pii rule, action mask, stage both. Map it onto the matrix and the coverage is exactly what you’d expect from §2:
  • Input stage — fully live, streaming or not. The request is masked before the model sees it (the headline value of input masking).
  • Output stage, non-streaming — the completion is masked before it returns.
  • Output stage, streaming — the stream scanner detects the match but does not rewrite the delta today, so the masked form does not reach a streamed client (in-stream output masking is on the roadmap).
So the mask preset does not cover PII out of a streamed reply on its own. To keep PII out of a streamed reply, author that rule as block (or call non-streaming) so the stream is cut on match. See PII Shield and masking formats.

5. What a streaming block costs

A streaming block carries the same accounting as any output block — the model has already run, so the gateway handles the refund for you:
  • On a non-streaming reply the call returns HTTP 400 guardrail_blocked naming the guardrail and the rule that fired. On a streaming reply the 200 SSE headers are already on the wire, so the block arrives as a final in-band replacement delta and a clean channel close instead of a 400.
  • No quota is charged. An input block fires before metering; an output block (streaming or not) refunds the pre-consumed quota once the reply is rejected. Either way the caller pays nothing.
  • The request is marked skip-retry — re-running the same prompt would just block again, so the gateway won’t burn a retry on another channel.
Every output rule that fires also records a match in the workspace Matches feed (GET /api/guardrail/match, open to any Member); the matched substring is captured only when the guardrail’s Log raw content toggle is on (off by default). Full detail lives in the guardrail_blocked error and the matches feed.

6. Prove your stage/action combo before you ship

Don’t guess which cell of the matrix applies to your policy — verify it. Both tools run on your console session via the management API:

Test tab

Each guardrail editor has a Test tab: paste a sample, pick the stage, and run the current policy with no upstream call and no quota. See the verdict and, for mask rules, the rendered text. The Test sandbox is gated to Developer+ (it can fire paid judge/grounding calls and outbound integration requests).

Eval tab

The Eval tab scores a guardrail against bundled or custom JSONL corpora — useful to confirm a block rule catches a known leak before you attach a key. Running an eval needs only read access (viewer+).
See testing & eval and tune false positives for depth.

7. Where to go next

Stream-safe rules

How the scanner cuts an SSE stream mid-flight, and how to author a policy that holds on streaming traffic.

Output stage

Screening the model’s reply — block vs. mask, the quota refund, and grounding.

Input stage

Screening the request before the model — masking included, streaming or not.

Actions

block, mask, flag, annotate, and spotlight in depth — when each one is the right call.
Guardrails — every rule type, field, and route, including grounding and the LLM judge.