Security Vulnerability Report
Severity: Medium (CVSS 5.4)
CWE: CWE-93 — Improper Neutralization of CRLF Sequences ('CRLF Injection')
Affected Version: sse-channel v4.0.0
Reporter: Kai Aizen (kai.aizen.dev@gmail.com)
Summary
The parseMessage() function in sse-channel concatenates user-controlled SSE fields (event, id, retry) directly into the SSE wire format without sanitizing newline characters. An attacker who controls these fields can inject additional SSE fields or entirely new SSE events into the stream, enabling event spoofing and client-side injection.
Vulnerable Code
In the message formatting logic, event, id, and retry values are concatenated raw:
// When constructing the SSE message string:
// event, id, and retry fields are interpolated directly
// without newline sanitization
if (msg.event) {
output += 'event:' + msg.event + '\n';
}
if (msg.id) {
output += 'id:' + msg.id + '\n';
}
if (msg.retry) {
output += 'retry:' + msg.retry + '\n';
}
The event, id, and retry fields are concatenated directly into the output without any check for \n or \r characters.
Proof of Concept
const SseChannel = require('sse-channel');
const http = require('http');
const channel = new SseChannel();
// Attacker injects a newline + fake event via the 'event' field
channel.send({
event: 'legit\ndata:injected-event\n',
data: 'real-data'
});
Expected output (safe):
event:legit
data:real-data
Actual output (vulnerable):
event:legit
data:injected-event
data:real-data
The injected data:injected-event line followed by a blank line creates a completely new, attacker-controlled SSE event that the client parses as legitimate.
Impact
- Event Spoofing: Attacker can inject arbitrary SSE events into the stream
-
- Client-side Manipulation: Injected events can trigger unintended behavior in frontend JavaScript EventSource listeners
-
-
- Data Integrity: Consumers of the SSE stream cannot distinguish injected events from legitimate ones
Suggested Fix
Sanitize newline characters (\n, \r) from the event, id, and retry fields before formatting:
function sanitize(value) {
if (value === null || value === undefined) return value;
return String(value).replace(/[\r\n]/g, '');
}
// Then use sanitize() when building the output:
if (msg.event) {
output += 'event:' + sanitize(msg.event) + '\n';
}
if (msg.id) {
output += 'id:' + sanitize(msg.id) + '\n';
}
if (msg.retry) {
output += 'retry:' + sanitize(msg.retry) + '\n';
}
Related Advisory
This is part of a class of SSE injection vulnerabilities across multiple SSE libraries. See: GHSA-4hxc-9384-m385
Note: I attempted to report this via GitHub PVRT, but private vulnerability reporting is not enabled on this repository. This issue is filed for visibility. Please consider enabling PVRT for future security reports.
Security Vulnerability Report
Severity: Medium (CVSS 5.4)
CWE: CWE-93 — Improper Neutralization of CRLF Sequences ('CRLF Injection')
Affected Version: sse-channel v4.0.0
Reporter: Kai Aizen (kai.aizen.dev@gmail.com)
Summary
The
parseMessage()function in sse-channel concatenates user-controlled SSE fields (event,id,retry) directly into the SSE wire format without sanitizing newline characters. An attacker who controls these fields can inject additional SSE fields or entirely new SSE events into the stream, enabling event spoofing and client-side injection.Vulnerable Code
In the message formatting logic,
event,id, andretryvalues are concatenated raw:The
event,id, andretryfields are concatenated directly into the output without any check for\nor\rcharacters.Proof of Concept
Expected output (safe):
Actual output (vulnerable):
The injected
data:injected-eventline followed by a blank line creates a completely new, attacker-controlled SSE event that the client parses as legitimate.Impact
Suggested Fix
Sanitize newline characters (
\n,\r) from theevent,id, andretryfields before formatting:Related Advisory
This is part of a class of SSE injection vulnerabilities across multiple SSE libraries. See: GHSA-4hxc-9384-m385