VYPR
High severityNVD Advisory· Published Mar 20, 2026· Updated Mar 20, 2026

h3 has a Server-Sent Events Injection via Unsanitized Newlines in Event Stream Fields

CVE-2026-33128

Description

H3 is a minimal H(TTP) framework. In versions prior to 1.15.6 and between 2.0.0 through 2.0.1-rc.14, createEventStream is vulnerable to Server-Sent Events (SSE) injection due to missing newline sanitization in formatEventStreamMessage() and formatEventStreamComment(). An attacker who controls any part of an SSE message field (id, event, data, or comment) can inject arbitrary SSE events to connected clients. This issue is fixed in versions 1.15.6 and 2.0.1-rc.15.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

In h3 < 1.15.6 and 2.0.0-2.0.1-rc.14, missing newline sanitization enables attacker-controlled SSE fields to inject arbitrary events into connected clients.

Vulnerability

Overview

CVE-2026-33128 is a Server-Sent Events (SSE) injection vulnerability in the h3 minimal HTTP framework. The flaw exists in formatEventStreamMessage() and formatEventStreamComment() functions within src/utils/internal/event-stream.ts. These functions do not sanitize user-controlled fields (id, event, data, comment) for newline characters before inserting them into SSE frames. Because the SSE protocol uses \n as a field delimiter and \n\n as an event separator, an unescaped newline breaks framing and allows injection.[1][2]

Attack

Vector and Exploitation

An attacker who controls any part of an SSE message field can exploit this by introducing newline characters. For example, injecting into the event field can change the intended wire format from event: message to event: message\nevent: admin\ndata: ALL_USERS_HACKED, effectively injecting arbitrary SSE fields or entirely new events. No special authentication is required beyond the ability to influence SSE message content, making this exploitable in any application that passes untrusted input to createEventStream.[2]

Impact

Successful exploitation allows an attacker to inject arbitrary SSE events to connected clients. This can manipulate reconnection behavior via retry directives, override Last-Event-ID via id injection, or force aggressive reconnection attempts leading to a denial-of-service condition. The browser's EventSource API processes the injected events, allowing the attacker to impersonate legitimate server messages or disrupt client-server data flows.[2][3]

Mitigation

The vulnerability is fixed in h3 versions 1.15.6 and 2.0.1-rc.15. Users should upgrade immediately to these patched releases. No known workarounds exist for earlier versions, as the functions directly control SSE wire formatting without input validation. The maintainers addressed the issue by sanitizing newline characters in all event stream fields.[1][2]

AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
h3npm
>= 2.0.0, < 2.0.1-rc.152.0.1-rc.15
h3npm
< 1.15.61.15.6

Affected products

1
  • h3js/h3v5
    Range: >= 2.0.0, < 2.0.1-rc.15

Patches

1
7791538e15ca

fix(sse): sanitize newlines in event stream fields to prevent SSE injection

https://github.com/h3js/h3Pooya ParsaMar 8, 2026via ghsa
2 files changed · +55 4
  • src/utils/internal/event-stream.ts+17 4 modified
    @@ -168,24 +168,37 @@ export function isEventStream(input: unknown): input is EventStream {
     }
     
     export function formatEventStreamComment(comment: string): string {
    -  return `: ${comment}\n\n`;
    +  return (
    +    comment
    +      .split("\n")
    +      .map((l) => `: ${l}\n`)
    +      .join("") + "\n"
    +  );
     }
     
     export function formatEventStreamMessage(message: EventStreamMessage): string {
       let result = "";
       if (message.id) {
    -    result += `id: ${message.id}\n`;
    +    result += `id: ${_sanitizeSingleLine(message.id)}\n`;
       }
       if (message.event) {
    -    result += `event: ${message.event}\n`;
    +    result += `event: ${_sanitizeSingleLine(message.event)}\n`;
       }
       if (typeof message.retry === "number" && Number.isInteger(message.retry)) {
         result += `retry: ${message.retry}\n`;
       }
    -  result += `data: ${message.data}\n\n`;
    +  const data = typeof message.data === "string" ? message.data : "";
    +  for (const line of data.split("\n")) {
    +    result += `data: ${line}\n`;
    +  }
    +  result += "\n";
       return result;
     }
     
    +function _sanitizeSingleLine(value: string): string {
    +  return value.replace(/[\n\r]/g, "");
    +}
    +
     export function formatEventStreamMessages(messages: EventStreamMessage[]): string {
       let result = "";
       for (const msg of messages) {
    
  • test/unit/sse.test.ts+38 0 modified
    @@ -33,4 +33,42 @@ describe("sse (unit)", () => {
         ]);
         expect(result).toEqual(`data: hello world\n\nid: 1\ndata: hello world 2\n\n`);
       });
    +
    +  it("sanitizes newlines in event field to prevent SSE injection", () => {
    +    const result = formatEventStreamMessage({
    +      event: "message\nevent: admin\ndata: INJECTED",
    +      data: "legit",
    +    });
    +    expect(result).toEqual(`event: messageevent: admindata: INJECTED\ndata: legit\n\n`);
    +    // Newlines stripped — no separate "event: admin" line that could be parsed as a new field
    +    expect(result.split("\n").filter((l) => l.startsWith("event:")).length).toBe(1);
    +  });
    +
    +  it("sanitizes newlines in id field to prevent SSE injection", () => {
    +    const result = formatEventStreamMessage({
    +      id: "1\ndata: INJECTED",
    +      data: "legit",
    +    });
    +    expect(result).toEqual(`id: 1data: INJECTED\ndata: legit\n\n`);
    +  });
    +
    +  it("splits multi-line data into separate data fields", () => {
    +    const result = formatEventStreamMessage({
    +      data: "line1\nline2\nline3",
    +    });
    +    expect(result).toEqual(`data: line1\ndata: line2\ndata: line3\n\n`);
    +  });
    +
    +  it("prevents data field injection of new events", () => {
    +    const result = formatEventStreamMessage({
    +      data: "hi\n\nevent: system\ndata: INJECTED",
    +    });
    +    // Each line becomes a separate data: field, no event injection possible
    +    expect(result).toBe(`data: hi\ndata: \ndata: event: system\ndata: data: INJECTED\n\n`);
    +  });
    +
    +  it("sanitizes newlines in comments", () => {
    +    const result = formatEventStreamComment("hello\ndata: INJECTED");
    +    expect(result).toEqual(`: hello\n: data: INJECTED\n\n`);
    +  });
     });
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.