diff --git a/packages/angular/ssr/src/utils/validation.ts b/packages/angular/ssr/src/utils/validation.ts index c89cdd6a64ed..a323410a43e7 100644 --- a/packages/angular/ssr/src/utils/validation.ts +++ b/packages/angular/ssr/src/utils/validation.ts @@ -98,8 +98,16 @@ export function cloneRequestAndPatchHeaders( }); const headers = clonedReq.headers; - const originalGet = headers.get; + + for (const name of HOST_HEADERS_TO_VALIDATE) { + const value = originalGet.call(headers, name); + const normalizedValue = getFirstHeaderValue(value); + if (normalizedValue !== undefined && normalizedValue !== value) { + headers.set(name, normalizedValue); + } + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion (headers.get as typeof originalGet) = function (name) { const value = originalGet.call(headers, name); diff --git a/packages/angular/ssr/test/utils/validation_spec.ts b/packages/angular/ssr/test/utils/validation_spec.ts index 10ab896e36f1..cccace5eb4fe 100644 --- a/packages/angular/ssr/test/utils/validation_spec.ts +++ b/packages/angular/ssr/test/utils/validation_spec.ts @@ -240,11 +240,49 @@ describe('Validation Utils', () => { it('should allow accessing other headers without validation', () => { const req = new Request('http://example.com', { - headers: { 'accept': 'application/json' }, + headers: { 'accept': 'text/html, application/json' }, }); const { request: secured } = cloneRequestAndPatchHeaders(req, allowedHosts); - expect(secured.headers.get('accept')).toBe('application/json'); + expect(secured.headers.get('accept')).toBe('text/html, application/json'); + }); + + it('should return the validated host header token when accessed via get()', () => { + const req = new Request('http://example.com', { + headers: { + host: 'example.com,@127.0.0.1:8765', + 'x-forwarded-host': 'sub.valid.com,@127.0.0.1:8765', + }, + }); + const { request: secured } = cloneRequestAndPatchHeaders(req, allowedHosts); + + expect(secured.headers.get('host')).toBe('example.com'); + expect(secured.headers.get('x-forwarded-host')).toBe('sub.valid.com'); + }); + + it('should return validated host header tokens when iterating headers', () => { + const req = new Request('http://example.com', { + headers: { + accept: 'text/html, application/json', + host: 'example.com,@127.0.0.1:8765', + 'x-forwarded-host': 'sub.valid.com,@127.0.0.1:8765', + }, + }); + const { request: secured } = cloneRequestAndPatchHeaders(req, allowedHosts); + + expect([...secured.headers.entries()]).toContain(['host', 'example.com']); + expect([...secured.headers.entries()]).toContain(['x-forwarded-host', 'sub.valid.com']); + expect([...secured.headers.values()]).toContain('example.com'); + expect([...secured.headers.values()]).toContain('sub.valid.com'); + expect([...secured.headers]).toContain(['host', 'example.com']); + expect([...secured.headers]).toContain(['x-forwarded-host', 'sub.valid.com']); + + const forEachValues = new Map(); + secured.headers.forEach((value, key) => forEachValues.set(key, value)); + + expect(forEachValues.get('accept')).toBe('text/html, application/json'); + expect(forEachValues.get('host')).toBe('example.com'); + expect(forEachValues.get('x-forwarded-host')).toBe('sub.valid.com'); }); it('should validate headers when iterating with entries()', async () => {