diff --git a/lib/fetch/headers.js b/lib/fetch/headers.js index 634b81fc74a..ea3b9a14a43 100644 --- a/lib/fetch/headers.js +++ b/lib/fetch/headers.js @@ -24,10 +24,12 @@ function headerValueNormalize (potentialValue) { // To normalize a byte sequence potentialValue, remove // any leading and trailing HTTP whitespace bytes from // potentialValue. - return potentialValue.replace( - /^[\r\n\t ]+|[\r\n\t ]+$/g, - '' - ) + + // Trimming the end with `.replace()` and a RegExp is typically subject to + // ReDoS. This is safer and faster. + let i = potentialValue.length + while (/[\r\n\t ]/.test(potentialValue.charAt(--i))); + return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, '') } function fill (headers, object) { diff --git a/test/fetch/headers.js b/test/fetch/headers.js index 7ae85b815b5..1f9196c4697 100644 --- a/test/fetch/headers.js +++ b/test/fetch/headers.js @@ -666,6 +666,18 @@ tap.test('invalid headers', (t) => { t.end() }) +tap.test('headers that might cause a ReDoS', (t) => { + t.doesNotThrow(() => { + // This test will time out if the ReDoS attack is successful. + const headers = new Headers() + const attack = 'a' + '\t'.repeat(500_000) + '\ta' + headers.append('fhqwhgads', attack) + }) + + t.end() +}) + + tap.test('Headers.prototype.getSetCookie', (t) => { t.test('Mutating the returned list does not affect the set-cookie list', (t) => { const h = new Headers([ @@ -682,4 +694,4 @@ tap.test('Headers.prototype.getSetCookie', (t) => { }) t.end() -}) +}) \ No newline at end of file