diff --git a/packages/angular/ssr/src/utils/validation.ts b/packages/angular/ssr/src/utils/validation.ts index cb1bf6ecb56b..f398a233116b 100644 --- a/packages/angular/ssr/src/utils/validation.ts +++ b/packages/angular/ssr/src/utils/validation.ts @@ -151,6 +151,19 @@ export function cloneRequestAndPatchHeaders( }; }; + const originalForEach = headers.forEach; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + (headers.forEach as typeof originalForEach) = function (callback, thisArg) { + originalForEach.call( + headers, + (value, key, parent) => { + validateHeader(key, value, allowedHosts, onError); + callback.call(thisArg, value, key, parent); + }, + thisArg, + ); + }; + // Ensure for...of loops use the new patched entries (headers[Symbol.iterator] as typeof originalEntries) = headers.entries; diff --git a/packages/angular/ssr/test/utils/validation_spec.ts b/packages/angular/ssr/test/utils/validation_spec.ts index d8c3eaeebdb3..fa1a2faf818c 100644 --- a/packages/angular/ssr/test/utils/validation_spec.ts +++ b/packages/angular/ssr/test/utils/validation_spec.ts @@ -325,5 +325,24 @@ describe('Validation Utils', () => { }), ); }); + + it('should validate headers when iterating with forEach()', async () => { + const req = new Request('http://example.com', { + headers: { 'host': 'evil.com' }, + }); + const { request: secured, onError } = cloneRequestAndPatchHeaders(req, allowedHosts); + + expect(() => { + secured.headers.forEach(() => { + // access the header to trigger the validation + }); + }).toThrowError('Header "host" with value "evil.com" is not allowed.'); + + await expectAsync(onError).toBeResolvedTo( + jasmine.objectContaining({ + message: jasmine.stringMatching('Header "host" with value "evil.com" is not allowed.'), + }), + ); + }); }); });