SSE (Server-Sent Events)¶
yorishiro-proxy detects and handles Server-Sent Events (SSE) responses at the event level. Each SSE event is parsed, recorded as a separate flow message, and forwarded to the client with output filter support.
How detection works¶
SSE detection happens at the HTTP response level. When the upstream server returns a response with Content-Type: text/event-stream, the proxy switches from normal HTTP response handling to SSE streaming mode. The MIME type check ignores parameters like charset, so text/event-stream; charset=utf-8 is also detected.
SSE is not a separate protocol -- it rides on top of HTTP/1.x (or HTTPS via MITM). The request phase is recorded normally as an HTTP flow, and the response phase switches to event-level streaming.
Client ──HTTP Request──> yorishiro-proxy ──HTTP Request──> Upstream Server
<──SSE Events─── <──SSE Events───
Event parsing¶
The proxy parses SSE events according to the HTML Living Standard. Each event is delimited by a blank line and can contain the following fields:
| Field | Description |
|---|---|
event: |
Event type name (default is "message" if omitted) |
data: |
Event payload (multiple data: lines are joined with newlines) |
id: |
Last event ID for reconnection |
retry: |
Reconnection interval hint (milliseconds) |
Lines starting with : are treated as comments and silently consumed. Unknown fields are ignored per the specification.
Size limits¶
- Single event: capped at 1 MB of raw bytes to prevent memory exhaustion
- Events per stream: up to 10,000 events are recorded per stream; beyond this limit, events are still forwarded to the client but no longer saved to the flow store
- Event body recording: event data is truncated at 254 MB per event in the flow store
- Stream duration: a safety cap of 24 hours prevents indefinite resource consumption
Event-level recording¶
Each SSE event is recorded as a separate flow.Message with direction="receive". This follows the same progressive recording pattern used by WebSocket frame recording.
Flow structure¶
An SSE flow is recorded with:
- Protocol:
HTTP/1.x(orHTTPSfor TLS connections) - Flow type:
stream(updated fromunarywhen SSE is detected) - State:
activewhile the stream is open,completewhen it ends - Tags:
streaming_type=sseandsse_events_recorded=<count>
The flow starts with the HTTP request (recorded as a send message) and the response headers (recorded as the first receive message with metadata sse_type=headers). Each subsequent SSE event is appended as a receive message with metadata sse_type=event.
Event message metadata¶
Each event message includes the following metadata fields:
| Key | Value |
|---|---|
sse_type |
"event" for SSE events, "headers" for the initial response headers |
sse_event |
Event type from the event: field (omitted if empty) |
sse_id |
Event ID from the id: field (omitted if empty) |
sse_retry |
Retry value from the retry: field (omitted if empty) |
The event Data is stored in the message Body field, and the original wire-format bytes are preserved in RawBytes.
Intercept support¶
SSE supports intercept at two levels:
Response-level intercept¶
When the SSE response matches intercept rules, the entire response is held for review before any events are streamed. At this level:
- Release: the stream proceeds normally
- Drop: the stream is terminated and the client receives a 502 error
- Modify: not supported at the response level (the body is a stream); treated as release with a warning
Event-level intercept¶
Individual SSE events can be intercepted and held for review. All three actions are supported:
- Release: the event is forwarded unchanged
- Drop: the event is silently skipped (not forwarded to the client)
- Modify: the event data and metadata can be modified before forwarding
Event metadata is exposed as pseudo-headers for intercept rule matching:
| Header | Maps to |
|---|---|
X-SSE-Event |
Event type (event: field) |
X-SSE-Id |
Event ID (id: field) |
X-SSE-Retry |
Retry value (retry: field) |
Variant tracking¶
When an event is modified by intercept or a plugin, both the original and modified versions are recorded:
- Sequence N: original event (metadata
variant=original) - Sequence N+1: modified event (metadata
variant=modified)
Unmodified events are recorded as a single message without variant metadata.
Plugin hooks¶
The SSE handler dispatches the same plugin hooks as standard HTTP responses, adapted for event-level processing:
| Hook | When it fires | Scope |
|---|---|---|
on_receive_from_server |
After receiving the response headers | Header-level (once) |
on_receive_from_server |
After parsing each SSE event | Event-level (per event) |
on_before_send_to_client |
Before writing the response headers | Header-level (once) |
on_before_send_to_client |
Before forwarding each SSE event | Event-level (per event) |
For event-level hooks, the SSE event is mapped to a synthetic HTTP response:
- Response body: the event
Datafield X-SSE-Eventheader: the event typeX-SSE-Idheader: the event IDX-SSE-Retryheader: the retry value
Plugins can modify these fields; changes are applied back to the SSE event before forwarding.
Note
Plugin hooks are dispatched in the plaintext HTTP path. In the HTTPS MITM path, event-level intercept and variant tracking work, but per-event plugin hooks are not dispatched. This is a known limitation.
Safety filter¶
The output filter (PII masking) is applied per-event to the SSE Data field before forwarding to the client:
- Mask rules: matching data is replaced with masked values in the forwarded event; the flow store always records the original (unfiltered) data
- Block rules: if a block-action rule matches an event's data, the entire SSE stream is terminated immediately
When events are displayed in the intercept queue, the output filter is also applied so that the reviewing agent sees masked data.
Auto-transform¶
Response auto-transform rules are not applied to SSE streams. Auto-transform requires buffering the full response body in memory, which is not possible for a streaming response.
Query examples¶
Find SSE flows¶
There is no dedicated protocol filter for SSE since it uses HTTP/HTTPS as the transport. Use the url_pattern filter or query flow details to identify SSE streams by their tags:
Get SSE flow details¶
The response includes flow_type: "stream", message_count with the total number of recorded events, and message_preview with the first 10 messages.
Page through SSE events¶
Filter receive-only messages¶
// query
{
"resource": "messages",
"id": "flow-abc-123",
"filter": {"direction": "receive"},
"limit": 50
}
Processing pipeline¶
Each SSE event passes through the following steps in order:
- Parse --
SSEParser.Next()reads and parses the next event from the stream - Plugin hook --
on_receive_from_server(event-level) - Snapshot -- original event data is captured for variant tracking
- Intercept -- event is checked against intercept rules; if matched, held for review (release / drop / modify)
- Plugin hook --
on_before_send_to_client(event-level) - Record -- event is saved to the flow store (with variant if modified)
- Output filter -- PII masking / block rules applied to the data
- Forward -- the (possibly filtered) event bytes are written to the client
Limitations¶
- No auto-transform -- response auto-transform rules require full body buffering and are skipped for SSE
- HTTPS plugin hooks -- per-event plugin hooks are not dispatched in the HTTPS MITM path (header-level intercept and event-level intercept still work)
- Response-level modify --
modify_and_forwardis not supported at the response header level for SSE (treated as release) - Recording cap -- only the first 10,000 events per stream are recorded; subsequent events are forwarded but not saved
Related pages¶
- HTTP/1.x -- the HTTP transport that SSE rides on
- HTTPS MITM -- SSE over HTTPS connections
- Intercept -- intercept rules and the review queue
- SafetyFilter -- output filter rules for PII masking
- Plugin hook reference -- detailed hook documentation