File tree Expand file tree Collapse file tree 3 files changed +47
-3
lines changed
Expand file tree Collapse file tree 3 files changed +47
-3
lines changed Original file line number Diff line number Diff line change @@ -29,12 +29,26 @@ module.exports = (opts) => {
2929 return typeof opts [ k ] !== 'undefined' && opts [ k ] !== null ;
3030 }
3131
32+ function validateNoCRLF ( str ) {
33+ if ( typeof str === 'string' && ( str . includes ( '\r' ) || str . includes ( '\n' ) ) ) {
34+ throw new Error ( 'Header is not a string or contains CRLF' ) ;
35+ }
36+ }
37+
38+ function addHeader ( key , value ) {
39+ validateNoCRLF ( key ) ;
40+ validateNoCRLF ( value ) ;
41+ headers [ key ] = value ;
42+ }
43+
3244 function setHeader ( str ) {
45+ validateNoCRLF ( str ) ;
46+
3347 const m = / ^ ( .+ ?) \s * : \s * ( .* ) $ / . exec ( str ) ;
3448 if ( ! m ) {
35- headers [ str ] = true ;
49+ addHeader ( str , true ) ; // Use addHeader instead of direct assignment
3650 } else {
37- headers [ m [ 1 ] ] = m [ 2 ] ;
51+ addHeader ( m [ 1 ] , m [ 2 ] ) ; // Use addHeader instead of direct assignment
3852 }
3953 }
4054
@@ -135,7 +149,7 @@ module.exports = (opts) => {
135149 opts [ k ] . forEach ( setHeader ) ;
136150 } else if ( opts [ k ] && typeof opts [ k ] === 'object' ) {
137151 Object . keys ( opts [ k ] ) . forEach ( ( key ) => {
138- headers [ key ] = opts [ k ] [ key ] ;
152+ addHeader ( key , opts [ k ] [ key ] ) ; // Uses same validation path
139153 } ) ;
140154 } else {
141155 setHeader ( opts [ k ] ) ;
Original file line number Diff line number Diff line change @@ -42,6 +42,16 @@ function HttpServer(options) {
4242 }
4343 }
4444
45+ // CRLF injection prevention
46+ for ( const [ key , value ] of Object . entries ( options . headers || { } ) ) {
47+ if ( typeof key !== 'string' || typeof value !== 'string' ) {
48+ throw new Error ( 'Header is not a string or contains CRLF' ) ;
49+ }
50+ if ( key . includes ( '\r' ) || key . includes ( '\n' ) || value . includes ( '\r' ) || value . includes ( '\n' ) ) {
51+ throw new Error ( 'Header is not a string or contains CRLF' ) ;
52+ }
53+ }
54+
4555 this . headers = options . headers || { } ;
4656 this . headers [ 'Accept-Ranges' ] = 'bytes' ;
4757
Original file line number Diff line number Diff line change @@ -84,3 +84,23 @@ test('H array', (t) => {
8484 t . equal ( headers . beep , 'boop' ) ;
8585 } ) ;
8686} ) ;
87+
88+ // CRLF injection prevention
89+ test ( 'CRLF injection prevention' , ( t ) => {
90+ t . plan ( 1 ) ;
91+
92+ t . throws ( ( ) => {
93+ const server = http . createServer (
94+ ecstatic ( {
95+ root,
96+ H : [
97+ 'X-CRLF-Injection: X\r\nContent-Type: text/html' ,
98+ ] ,
99+ autoIndex : true ,
100+ defaultExt : 'html' ,
101+ } )
102+ ) ;
103+
104+ server . close ( ) ;
105+ } , / H e a d e r i s n o t a s t r i n g o r c o n t a i n s C R L F / ) ;
106+ } ) ;
You can’t perform that action at this time.
0 commit comments