{"id":"ace7ef806e04f036","repo":"tiangolo/fastapi","slug":"sse-id-must-not-contain-null-characters","errorCode":null,"errorMessage":"SSE 'id' must not contain null characters","messagePattern":"SSE 'id' must not contain null characters","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastapi/sse.py","lineNumber":48,"sourceCode":"    serves mainly as a marker and sets the correct `Content-Type`.\n    \"\"\"\n\n    media_type = \"text/event-stream\"\n\n\ndef _check_single_line(v: str | None, field_name: str) -> str | None:\n    if v is not None and (\"\\r\" in v or \"\\n\" in v):\n        raise ValueError(f\"SSE '{field_name}' must be a single line\")\n    return v\n\n\ndef _check_event_single_line(v: str | None) -> str | None:\n    return _check_single_line(v, \"event\")\n\n\ndef _check_id_valid(v: str | None) -> str | None:\n    if v is not None and \"\\0\" in v:\n        raise ValueError(\"SSE 'id' must not contain null characters\")\n    return _check_single_line(v, \"id\")\n\n\nclass ServerSentEvent(BaseModel):\n    \"\"\"Represents a single Server-Sent Event.\n\n    When `yield`ed from a *path operation function* that uses\n    `response_class=EventSourceResponse`, each `ServerSentEvent` is encoded\n    into the [SSE wire format](https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream)\n    (`text/event-stream`).\n\n    If you yield a plain object (dict, Pydantic model, etc.) instead, it is\n    automatically JSON-encoded and sent as the `data:` field.\n\n    All `data` values **including plain strings** are JSON-serialized.\n\n    For example, `data=\"hello\"` produces `data: \"hello\"` on the wire (with\n    quotes).","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/tiangolo/fastapi/blob/42a41db11f6882807ac3c057b942178d53b97438/fastapi/sse.py#L30-L66","documentation":"`ServerSentEvent.id` uses `AfterValidator(_check_id_valid)` (sse.py:46-49) which raises `ValueError` if the id contains a null byte (`\\0`). The SSE spec forbids U+0000 in the event id; it also still applies the single-line check. The browser sends `id` back as `Last-Event-ID` on reconnect, and a null byte would break that round-trip.","triggerScenarios":"Yielding `ServerSentEvent(id=some_id, data=...)` where `some_id` contains `\\0` — e.g. a binary/C-string buffer, a DB value with an embedded null, or untrusted input not sanitized.","commonSituations":"Using a row id or token that came from a binary source; reading headers/bytes and passing them through as an event id; corrupted data containing control characters.","solutions":["Strip null bytes: `id = raw_id.replace(\"\\0\", \"\")` before constructing the event.","Reject/sanitize upstream inputs that may contain control characters.","Use a generated, known-safe id (uuid, integer) instead of passing raw external values."],"exampleFix":"# before\nyield ServerSentEvent(id=row_id, data=payload)  # row_id may contain \\0\n\n# after\nclean_id = (row_id or \"\").replace(\"\\0\", \"\")\nyield ServerSentEvent(id=clean_id, data=payload)","handlingStrategy":"validation","validationCode":"def sanitize_sse_id(value: str | None) -> str | None:\n    if value is None:\n        return None\n    if \"\\0\" in value:\n        raise ValueError(f\"SSE id contains null byte: {value!r}\")\n    if \"\\r\" in value or \"\\n\" in value:\n        raise ValueError(f\"SSE id must be single line: {value!r}\")\n    return value\n\n# usage (strip-and-accept variant):\nyield ServerSentEvent(id=(raw or \"\").replace(\"\\0\", \"\"), data=payload)","typeGuard":"def is_valid_sse_id(value: str | None) -> bool:\n    return value is None or (\"\\0\" not in value and \"\\r\" not in value and \"\\n\" not in value)","tryCatchPattern":"from fastapi.sse import ServerSentEvent\n\ntry:\n    yield ServerSentEvent(id=raw_id, data=payload)\nexcept ValueError as exc:\n    logging.warning(\"dropping SSE event with bad id: %s\", exc)\n    yield ServerSentEvent(id=None, data=payload)  # omit id on bad input","preventionTips":["Never pass binary/C-string buffers or raw external tokens as an SSE id.","Prefer generated ids (uuid/int) for events.","Sanitize control characters at the trust boundary, not at SSE construction."],"tags":["sse","server-sent-events","validation","input-sanitization"],"analyzedSha":"42a41db11f6882807ac3c057b942178d53b97438","analyzedAt":"2026-08-04T19:23:32.007Z","schemaVersion":2}