diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 1232cf7fb5cf..8f06c0d0d2a0 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,4 +1,12 @@ +## 13.8.2 + +_Released 4/27/2024 (PENDING)_ + +**Bugfixes:** + +- Fixed an issue where receiving HTTP responses with invalid headers raised an error. Now cypress removes the invalid headers and gives a warning in the console with debug mode on. Fixes [#28865](https://github.com/cypress-io/cypress/issues/28865). + ## 13.8.1 _Released 4/23/2024_ diff --git a/packages/proxy/lib/http/response-middleware.ts b/packages/proxy/lib/http/response-middleware.ts index 31a8267a90bb..c650756f8ae8 100644 --- a/packages/proxy/lib/http/response-middleware.ts +++ b/packages/proxy/lib/http/response-middleware.ts @@ -22,6 +22,7 @@ import type { IncomingMessage, IncomingHttpHeaders } from 'http' import { cspHeaderNames, generateCspDirectives, nonceDirectives, parseCspHeaders, problematicCspDirectives, unsupportedCSPDirectives } from './util/csp-header' import { injectIntoServiceWorker } from './util/service-worker-injector' +import { validateHeaderName } from 'http' export interface ResponseMiddlewareProps { /** @@ -306,7 +307,26 @@ const OmitProblematicHeaders: ResponseMiddleware = function () { 'connection', ]) - this.res.set(headers) + this.debug('The headers are %o', headers) + + // Filter for invalid headers + const filteredHeaders = Object.fromEntries( + Object.entries(headers).filter(([key, value]) => { + try { + validateHeaderName(key) + + return true + } catch (err) { + this.debug('Warning: Found header with the invalid name \'%s\', taking it off the response', key) + + return false + } + }), + ) + + this.res.set(filteredHeaders) + + this.debug('the new response headers are %o', this.res.getHeaderNames()) span?.setAttributes({ experimentalCspAllowList: this.config.experimentalCspAllowList, diff --git a/packages/proxy/test/unit/http/response-middleware.spec.ts b/packages/proxy/test/unit/http/response-middleware.spec.ts index a8279fa4d7e7..f8069314aeca 100644 --- a/packages/proxy/test/unit/http/response-middleware.spec.ts +++ b/packages/proxy/test/unit/http/response-middleware.spec.ts @@ -274,6 +274,41 @@ describe('http/response-middleware', function () { }) }) + let badHeaders = { + 'bad-header ': 'value', //(contains trailling space) + 'Content Type': 'value', //(contains a space) + 'User-Agent:': 'value', //(contains a colon) + 'Accept-Encoding;': 'value', //(contains a semicolon) + '@Origin': 'value', //(contains an at symbol) + 'Authorization?': 'value', //(contains a question mark) + 'X-My-Header/Version': 'value', //(contains a slash) + 'Referer[1]': 'value', //(contains square brackets) + 'If-None-Match{1}': 'value', //(contains curly braces) + 'X-Forwarded-For<1>': 'value', //(contains angle brackets) + } + + it('removes invalid headers and leaves valid headers', function () { + prepareContext({ ...badHeaders, 'good-header': 'value' }) + + return testMiddleware([OmitProblematicHeaders], ctx) + .then(() => { + expect(ctx.res.set).to.have.been.calledOnce + expect(ctx.res.set).to.be.calledWith(sinon.match(function (actual) { + // Check if the invalid headers are removed + for (let header in actual) { + if (header in badHeaders) { + throw new Error(`Unexpected header "${header}"`) + } + } + + // Check if the valid header is present + expect(actual['good-header']).to.equal('value') + + return true + })) + }) + }) + const validCspHeaderNames = [ 'content-security-policy', 'Content-Security-Policy', @@ -443,6 +478,7 @@ describe('http/response-middleware', function () { setHeader: sinon.stub(), on: (event, listener) => {}, off: (event, listener) => {}, + getHeaderNames: () => Object.keys(ctx.incomingRes.headers), }, } }