fix: reject newline characters in SSE event and id fields by subhashdasyam · Pull Request #15187 · fastapi/fastapi

AI review requested due to automatic review settings

March 21, 2026 14:57
`format_sse_event()` interpolated the `event` and `id` fields into SSE
wire-format bytes without stripping or rejecting newline characters. A
`\n` or `\r\n` in either field injects an additional SSE field-line into
the stream — enabling event-type spoofing and fabricated data payloads
in browser EventSource clients.

The `data` and `comment` fields were already protected via `splitlines()`;
the omission for `event` and `id` was an inconsistency, not a design
decision. The identical bug class was fixed in Hono (GHSA-p6xx-57qc-3wxr).

Changes:
- Add `_check_event_no_newline()` validator and apply it to
  `ServerSentEvent.event` via `AfterValidator`
- Extend `_check_id_no_null()` to also reject `\n` and `\r`
- Strip `\r`/`\n` in `format_sse_event()` for `event` and `id` as
  defense-in-depth (low-level function, bypasses model validators)
- Add 7 regression tests covering LF/CR rejection in both the model
  and the low-level formatting function

Severity: Medium (client-side injection; server unaffected)
CWE: CWE-116 — Improper Encoding for Output in a Different Plaintext Context
OWASP: A03:2021 — Injection
Prior art: Hono GHSA-p6xx-57qc-3wxr (same class, same fix pattern)

YuriiMotov

…'id'

`_check_event_no_newline` lacked a `\0` check that `_check_id_no_null_or_newline`
has had since the original implementation. Similarly, `format_sse_event()` stripped
`\0` from `id` but not from `event`.

Changes:
- Extend `_check_event_no_newline` to also reject `\0` characters
- Strip `\0` in `format_sse_event()` for `event` (mirrors existing `id` behavior)
- Add `test_server_sent_event_null_event_rejected` regression test

`event` and `id` are now fully symmetric in both the model validator and the
low-level wire formatter.

SadManFahIm

…d chars

Replace silent .replace() sanitization in format_sse_event() with explicit
ValueError raises for \n, \r, and \0 in the event and id fields.

Previously, format_sse_event() would strip those characters, creating a
behavioral inconsistency with ServerSentEvent's AfterValidator which raises
on the same input. As YuriiMotov pointed out: data allows newlines by SSE
spec design (each line becomes a separate data: frame), but event and id are
single-token fields where a newline is always a protocol violation. Silent
stripping masks bugs at the call site; raising surfaces them where they belong.

Both code paths now fail identically for the same invalid input. Tests updated
from strip-behavior assertions to ValueError assertions, plus two new null-byte
tests covering format_sse_event() directly for event and id.