GitHub Security Advisory: GHSA-22cc-p3c6-wpvm
Package: h3 (NPM)
Severity: HIGH (CVSS 7.5)
CWE: CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection')
Vulnerable versions: >= 2.0.0, <= 2.0.1-rc.14
Patched in: 2.0.1-rc.15

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

Details:
## Summary

`createEventStream` in h3 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.

## Details

The vulnerability exists in `src/utils/internal/event-stream.ts`, lines [170](https://github.com/h3js/h3/blob/52c82e18bb643d124b8b9ec3b1f62b081f044611/src/utils/internal/event-stream.ts#L170)-[187](https://github.com/h3js/h3/blob/52c82e18bb643d124b8b9ec3b1f62b081f044611/src/utils/internal/event-stream.ts#L187):

```typescript
export function formatEventStreamComment(comment: string): string {
  return `: ${comment}\n\n`;
}

export function formatEventStreamMessage(message: EventStreamMessage): string {
  let result = "";
  if (message.id) {
    result += `id: ${message.id}\n`;
  }
  if (message.event) {
    result += `event: ${message.event}\n`;
  }
  if (typeof message.retry === "number" && Number.isInteger(message.retry)) {
    result += `retry: ${message.retry}\n`;
  }
  result += `data: ${message.data}\n\n`;
  return result;
}
```

The SSE protocol (defined in the [WHATWG HTML spec](https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation)) uses newline characters (`\n`) as field delimiters and double newlines (`\n\n`) as event separators.

None of the fields (`id`, `event`, `data`, comment) are sanitized for newline characters before being interpolated into the SSE wire format. If any field value contains `\n`, the SSE framing is broken, allowing an attacker to:

1. **Inject arbitrary SSE fields** — break out of one field and add `event:`, `data:`, `id:`, or `retry:` directives
2. **Inject entirely new SSE events** — using `\n\n` to terminate the current event and start a new one
3. **Manipulate reconnection behavior** — inject `retry: 1` to force aggressive reconnection (DoS)
4. **Override Last-Event-ID** — inject `id:` to manipulate which events are replayed on reconnection

### Injection via the `event` field

```
Intended wire format:        Actual wire format (with \n injection):

event: message               event: message
data: attacker: hey          event: admin              ← INJECTED
                             data: ALL_USERS_HACKED    ← INJECTED
                             data: attacker: hey
```

The browser's `EventSource` API parses these as two separate events: one `message` event and one `admin` event.

### Injection via the `data` field

```
Intended:                    Actual (with \n\n injection):

event: message               event: message
data: bob: hi                data: bob: hi
                                                        ← event boundary
                             event: system              ← INJECTED event
                             data: Reset: evil.com      ← INJECTED data
```

Before exploit:
<img width="700" heigh