@@ -74,6 +74,34 @@ describe("sse (unit)", () => {
7474 expect ( result ) . toEqual ( `: hello\n: data: INJECTED\n\n` ) ;
7575 } ) ;
7676
77+ it ( "sanitizes carriage returns in data to prevent SSE injection" , ( ) => {
78+ const result = formatEventStreamMessage ( {
79+ data : "legit\revent: evil" ,
80+ } ) ;
81+ // \r must be treated as a line break, so "event: evil" becomes a data: line
82+ expect ( result ) . toBe ( `data: legit\ndata: event: evil\n\n` ) ;
83+ } ) ;
84+
85+ it ( "sanitizes \\r\\n in data field" , ( ) => {
86+ const result = formatEventStreamMessage ( {
87+ data : "line1\r\nline2\rline3\nline4" ,
88+ } ) ;
89+ expect ( result ) . toBe ( `data: line1\ndata: line2\ndata: line3\ndata: line4\n\n` ) ;
90+ } ) ;
91+
92+ it ( "prevents event splitting via \\r\\r in data" , ( ) => {
93+ const result = formatEventStreamMessage ( {
94+ data : "first\r\rdata: injected" ,
95+ } ) ;
96+ // \r\r should produce an empty line between, not a message boundary
97+ expect ( result ) . toBe ( `data: first\ndata: \ndata: data: injected\n\n` ) ;
98+ } ) ;
99+
100+ it ( "sanitizes carriage returns in comments to prevent injection" , ( ) => {
101+ const result = formatEventStreamComment ( "x\rdata: injected" ) ;
102+ expect ( result ) . toBe ( `: x\n: data: injected\n\n` ) ;
103+ } ) ;
104+
77105 describe ( "EventStream" , ( ) => {
78106 it ( "onClosed does not cause unhandled rejection when callback throws" , async ( ) => {
79107 const event = mockEvent ( "/" ) ;
0 commit comments