Skip to content

Commit b1fe66a

Browse files
committed
fix(@angular/ssr): patch Headers.forEach in cloneRequestAndPatchHeaders
This commit updates the cloneRequestAndPatchHeaders function to patch the Headers.forEach method. This ensures that host headers are validated when the application iterates over request headers using forEach, preventing potential host header injection attacks during header iteration. A unit test has been added to validation_spec.ts to verify that forEach correctly triggers validation and throws an error for disallowed hosts. (cherry picked from commit bcd99f9)
1 parent 2aa93fc commit b1fe66a

File tree

2 files changed

+32
-0
lines changed

2 files changed

+32
-0
lines changed

packages/angular/ssr/src/utils/validation.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,19 @@ export function cloneRequestAndPatchHeaders(
151151
};
152152
};
153153

154+
const originalForEach = headers.forEach;
155+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
156+
(headers.forEach as typeof originalForEach) = function (callback, thisArg) {
157+
originalForEach.call(
158+
headers,
159+
(value, key, parent) => {
160+
validateHeader(key, value, allowedHosts, onError);
161+
callback.call(thisArg, value, key, parent);
162+
},
163+
thisArg,
164+
);
165+
};
166+
154167
// Ensure for...of loops use the new patched entries
155168
(headers[Symbol.iterator] as typeof originalEntries) = headers.entries;
156169

packages/angular/ssr/test/utils/validation_spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,5 +341,24 @@ describe('Validation Utils', () => {
341341
}),
342342
);
343343
});
344+
345+
it('should validate headers when iterating with forEach()', async () => {
346+
const req = new Request('http://example.com', {
347+
headers: { 'host': 'evil.com' },
348+
});
349+
const { request: secured, onError } = cloneRequestAndPatchHeaders(req, allowedHosts);
350+
351+
expect(() => {
352+
secured.headers.forEach(() => {
353+
// access the header to trigger the validation
354+
});
355+
}).toThrowError('Header "host" with value "evil.com" is not allowed.');
356+
357+
await expectAsync(onError).toBeResolvedTo(
358+
jasmine.objectContaining({
359+
message: jasmine.stringMatching('Header "host" with value "evil.com" is not allowed.'),
360+
}),
361+
);
362+
});
344363
});
345364
});

0 commit comments

Comments
 (0)