Skip to content

Commit 5d7dfa1

Browse files
committed
fix(@angular/ssr): support all X-Forwarded-* headers when trustProxyHeaders is true
Previously, setting `trustProxyHeaders: true` only allowed a predefined set of common proxy headers (such as `x-forwarded-for` and `x-forwarded-host`). This resulted in warning logs when requests contained other valid proxy headers like `x-forwarded-client-cert` or `x-forwarded-email`. Closes #33169
1 parent dc7d3fc commit 5d7dfa1

2 files changed

Lines changed: 15 additions & 19 deletions

File tree

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,9 @@
77
*/
88

99
/**
10-
* Common X-Forwarded-* headers.
10+
* Internal sentinel string representing a wildcard rule to trust all proxy headers.
1111
*/
12-
const X_FORWARDED_HEADERS: ReadonlySet<string> = new Set([
13-
'x-forwarded-for',
14-
'x-forwarded-host',
15-
'x-forwarded-port',
16-
'x-forwarded-proto',
17-
'x-forwarded-prefix',
18-
]);
12+
const TRUST_ALL_PROXY_HEADERS = 'ɵ*';
1913

2014
/**
2115
* The set of headers that should be validated for host header injection attacks.
@@ -235,7 +229,10 @@ export function isProxyHeaderAllowed(
235229
headerName: string,
236230
trustProxyHeaders: ReadonlySet<string>,
237231
): boolean {
238-
return trustProxyHeaders.has(headerName.toLowerCase());
232+
return (
233+
trustProxyHeaders.has(TRUST_ALL_PROXY_HEADERS) ||
234+
trustProxyHeaders.has(headerName.toLowerCase())
235+
);
239236
}
240237

241238
/**
@@ -251,7 +248,7 @@ export function normalizeTrustProxyHeaders(
251248
}
252249

253250
if (trustProxyHeaders === true) {
254-
return X_FORWARDED_HEADERS;
251+
return new Set([TRUST_ALL_PROXY_HEADERS]);
255252
}
256253

257254
return new Set(trustProxyHeaders.map((h) => h.toLowerCase()));

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {
1010
getFirstHeaderValue,
11+
normalizeTrustProxyHeaders,
1112
sanitizeRequestHeaders,
1213
validateRequest,
1314
validateUrl,
@@ -224,23 +225,22 @@ describe('Validation Utils', () => {
224225
'x-forwarded-proto': 'https',
225226
},
226227
});
227-
const secured = sanitizeRequestHeaders(req, new Set());
228+
const secured = sanitizeRequestHeaders(req, normalizeTrustProxyHeaders(false));
228229

229230
expect(secured.headers.get('host')).toBe('example.com');
230231
expect(secured.headers.has('x-forwarded-host')).toBeFalse();
231232
expect(secured.headers.has('x-forwarded-proto')).toBeFalse();
232233
});
233234

234-
it('should retain allowed proxy headers when explicitly provided', () => {
235-
const trustProxyHeaders = new Set(['x-forwarded-host']);
235+
it('should only retain allowed proxy headers when explicitly provided', () => {
236236
const req = new Request('http://example.com', {
237237
headers: {
238238
'host': 'example.com',
239239
'x-forwarded-host': 'proxy.com',
240240
'x-forwarded-proto': 'https',
241241
},
242242
});
243-
const secured = sanitizeRequestHeaders(req, trustProxyHeaders);
243+
const secured = sanitizeRequestHeaders(req, normalizeTrustProxyHeaders(['x-forwarded-host']));
244244

245245
expect(secured.headers.get('host')).toBe('example.com');
246246
expect(secured.headers.get('x-forwarded-host')).toBe('proxy.com');
@@ -253,23 +253,22 @@ describe('Validation Utils', () => {
253253
'host': 'example.com',
254254
'x-forwarded-host': 'proxy.com',
255255
'x-forwarded-proto': 'https',
256+
'x-forwarded-email': 'user@example.com',
256257
},
257258
});
258-
const secured = sanitizeRequestHeaders(
259-
req,
260-
new Set(['x-forwarded-host', 'x-forwarded-proto']),
261-
);
259+
const secured = sanitizeRequestHeaders(req, normalizeTrustProxyHeaders(true));
262260

263261
expect(secured.headers.get('host')).toBe('example.com');
264262
expect(secured.headers.get('x-forwarded-host')).toBe('proxy.com');
265263
expect(secured.headers.get('x-forwarded-proto')).toBe('https');
264+
expect(secured.headers.get('x-forwarded-email')).toBe('user@example.com');
266265
});
267266

268267
it('should not clone the request if no proxy headers need to be removed', () => {
269268
const req = new Request('http://example.com', {
270269
headers: { 'accept': 'application/json' },
271270
});
272-
const secured = sanitizeRequestHeaders(req, new Set());
271+
const secured = sanitizeRequestHeaders(req, normalizeTrustProxyHeaders(false));
273272

274273
expect(secured).toBe(req);
275274
expect(secured.headers.get('accept')).toBe('application/json');

0 commit comments

Comments
 (0)