Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
AC:H reflects the non-default application pattern required (attacker-controlled data must flow into event/id); S:C applies because the injected output affects the browser client; I:L for stream integrity spoofing with no confidentiality or availability impact.
Primary rating from NVD.
CVSS VectorNVD
Lifecycle Timeline
4Blast Radius
ecosystem impact- 10 npm packages depend on eventsource-encoder (2 direct, 8 indirect)
Ecosystem-wide dependent count for version 1.0.2.
DescriptionNVD
Summary
eventsource-encoder does not sanitize the event or id fields of an EventSourceMessage before serializing them. An attacker who controls either field can inject arbitrary Server-Sent Events line terminators (\n, \r, or \r\n) and thereby forge additional SSE fields or entire messages on the stream. This is similar in spirit to GHSA-4hxc-9384-m385 (h3), but the vulnerable fields are event/id rather than data/comment. These are less likely to be user-controllable, but should still be sanitized.
Details
In src/encode.ts, encodeMessage interpolates event and id into the output without inspecting them for line terminators:
if (message.event) {
output += `event: ${message.event}\n`
}
// ...
if (typeof message.id === 'string' || typeof message.id === 'number') {
output += `id: ${message.id}\n`
}The SSE specification treats \r, \n, and \r\n as line terminators. A \n (or \r) embedded in either field is rendered as the end of that field, allowing the rest of the input to be interpreted by the client as new SSE fields.
By contrast, data and comment already normalize all three line-terminator forms via NEWLINES_RE = /(\r\n|\r|\n)/g, so they are not affected.
Proof of concept
import {encode} from 'eventsource-encoder'
// Attacker-controlled value flows into `event`
const userSuppliedTopic = 'message\nevent: admin\ndata: {"role":"admin"}'
console.log(encode({event: userSuppliedTopic, data: 'hello'}))Output:
event: message
event: admin
data: {"role":"admin"}
data: hello
The browser sees two events: a forged admin event with attacker-chosen payload, followed by the legitimate message event. The same primitive works through id for any string id value.
Impact
If untrusted input is passed into the event or id field of a message, an attacker can:
- Spoof events of arbitrary type (rerouting payloads to handlers the attacker chooses)
- Inject additional SSE fields (
data:,id:,retry:) into the stream - Split a single
encode()call into multiple distinct browser events - Override the client's
Last-Event-IDvia injectedid:lines
The vulnerability requires that an application places attacker-controlled data into event or id. Applications that only put trusted, statically-defined values into these fields are not affected.
Patches
Fixed in eventsource-encoder@1.0.2. The event and string id fields are now validated; any value containing \r or \n causes the encoder to throw a TypeError rather than emit a malformed stream.
Workarounds
If users cannot upgrade, validate or strip line terminators from any untrusted value before passing it to encode / encodeMessage:
function safeSingleLine(value) {
if (/[\r\n]/.test(value)) throw new Error('SSE field must be single-line')
return value
}
encode({event: safeSingleLine(topic), id: safeSingleLine(id), data})Resources
- Related advisory (different package, same class): https://github.com/advisories/GHSA-4hxc-9384-m385
- SSE spec, line terminators: https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream
Credit
Discovered while reviewing in light of GHSA-4hxc-9384-m385.
AnalysisAI
SSE field injection in eventsource-encoder below 1.0.2 allows an attacker who controls the event or id field values to inject line terminators into the serialized Server-Sent Events stream, forging additional SSE fields or entirely synthetic browser events. A working proof of concept is publicly documented in the GitHub Security Advisory GHSA-m9g3-3g99-mhpx, demonstrating that a crafted event value can cause the browser to receive a forged admin-typed event with attacker-chosen payload. No public exploit infrastructure has been identified and EPSS sits at the 1st percentile, reflecting the narrow application pattern required for exploitation.
Technical ContextAI
eventsource-encoder (pkg:npm/eventsource-encoder) is a TypeScript/JavaScript library for serializing Server-Sent Events messages on the server side. The SSE wire format, defined in the WHATWG HTML specification, treats \r, \n, and \r\n as line terminators that end a field; a bare newline in a field value therefore opens a new SSE field line. The root cause is CWE-93 (Improper Neutralization of CRLF Sequences): in src/encode.ts, the encodeMessage function performs direct template-literal interpolation of message.event and message.id without stripping or rejecting embedded line terminators. The library's data and comment fields are already protected via a NEWLINES_RE regex that normalises all three terminator forms, but this sanitisation was not applied to event or id. The affected package range is eventsource-encoder <= 1.0.1.
RemediationAI
Vendor-released patch: eventsource-encoder 1.0.2. Upgrade the npm dependency immediately - the fix causes encodeMessage to throw a TypeError when event or id contain \r or \n, converting a silent data-corruption risk into a loud, detectable error. Full advisory at https://github.com/rexxars/eventsource-encoder/security/advisories/GHSA-m9g3-3g99-mhpx. If upgrading is not immediately possible, add an application-layer guard before calling encode or encodeMessage: validate that the event and id values are single-line strings (i.e., reject any value matching /[\r\n]/) and throw before passing them to the library. This workaround mirrors what 1.0.2 does internally. The trade-off is that it moves the validation responsibility to each call site and must be applied consistently across all code paths that construct SSE messages.
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-31968
GHSA-m9g3-3g99-mhpx