{"record":{"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":"exception","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/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/fastapi/sse.py#L30-L66","documentation":"Raised by `_check_id_valid` (fastapi/sse.py:48) as a ValueError when the `id` field of a `ServerSentEvent` contains a null character ('\\0'). The SSE spec forbids NUL in event IDs because they break `Last-Event-ID` handling on reconnect; the browser sends the id back as a header where NUL is illegal. This is checked in addition to the single-line rule.","triggerScenarios":"Constructing `ServerSentEvent(id='abc\\x00def', data=...)`. Building the id from binary/serialized data that contains NUL bytes. Using a database/protobuf value that includes embedded nulls as the SSE id.","commonSituations":"Feeding binary identifiers (e.g. packed structs, UUIDs from binary sources) into the `id` field. Logging/telemetry payloads mistakenly placed in `id`. Strings from C-interop or NUL-padded buffers.","solutions":["Strip NUL bytes: `id = raw_id.replace('\\0', '')` before constructing the event.","Encode binary ids as hex/base64 so they contain no NUL: `id = raw_bytes.hex()`.","Validate upstream that ids are printable ASCII/UTF-8 without NUL."],"exampleFix":"// before\nyield ServerSentEvent(id=raw_bytes.decode('latin-1'), data=payload)\n// after\nyield ServerSentEvent(id=raw_bytes.hex(), data=payload)","handlingStrategy":"validation","validationCode":"from fastapi.sse import ServerSentEvent\n\ndef safe_sse_id(raw) -> str | None:\n    if raw is None:\n        return None\n    s = str(raw).replace('\\0', '')\n    s = s.replace('\\r', '').replace('\\n', ' ')\n    return s\n\nyield ServerSentEvent(id=safe_sse_id(raw_id), data=payload)","typeGuard":"def id_has_no_null(value: object) -> bool:\n    return isinstance(value, str) and '\\0' not in value","tryCatchPattern":null,"preventionTips":["Encode binary identifiers as hex/base64 before using as SSE id.","Strip NUL from any string sourced from binary/interop.","Reject ids containing control characters at the input boundary."],"tags":["fastapi","sse","validation","streaming","security"],"backgroundTag":null,"analyzedSha":"3e8d1526d83a90aaf7d6eb6dc682bf150f180b25","analyzedAt":"2026-08-11T02:34:52.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}