Skip to content

Commit 3b99ee1

Browse files
authored
fix(@angular/ssr): support '*' in allowedHosts and warn about security risks
This commit adds support for '*' in allowedHosts for SSR, allowing any host to be validated. It also adds a security warning when '*' is used to inform users of the potential risks of allowing all host headers.
1 parent f98cc82 commit 3b99ee1

File tree

4 files changed

+36
-3
lines changed

4 files changed

+36
-3
lines changed

packages/angular/build/src/builders/dev-server/vite/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ export async function* serveWithVite(
101101
// Angular SSR supports `*.`.
102102
const allowedHosts = Array.isArray(serverOptions.allowedHosts)
103103
? serverOptions.allowedHosts.map((host) => (host[0] === '.' ? '*' + host : host))
104-
: [];
104+
: serverOptions.allowedHosts === true
105+
? ['*']
106+
: [];
105107

106108
// Always allow the dev server host
107109
allowedHosts.push(serverOptions.host);

packages/angular/ssr/src/app-engine.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,22 @@ export class AngularAppEngine {
8888
* @param options Options for the Angular server application engine.
8989
*/
9090
constructor(options?: AngularAppEngineOptions) {
91-
this.allowedHosts = new Set([...(options?.allowedHosts ?? []), ...this.manifest.allowedHosts]);
91+
this.allowedHosts = this.getAllowedHosts(options);
92+
}
93+
94+
private getAllowedHosts(options: AngularAppEngineOptions | undefined): ReadonlySet<string> {
95+
const allowedHosts = new Set([...(options?.allowedHosts ?? []), ...this.manifest.allowedHosts]);
96+
97+
if (allowedHosts.has('*')) {
98+
// eslint-disable-next-line no-console
99+
console.warn(
100+
'Allowing all hosts via "*" is a security risk. This configuration should only be used when ' +
101+
'validation for "Host" and "X-Forwarded-Host" headers is performed in another layer, such as a load balancer or reverse proxy. ' +
102+
'For more information see: https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf',
103+
);
104+
}
105+
106+
return allowedHosts;
92107
}
93108

94109
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ function verifyHostAllowed(
224224
* @returns `true` if the hostname is allowed, `false` otherwise.
225225
*/
226226
function isHostAllowed(hostname: string, allowedHosts: ReadonlySet<string>): boolean {
227-
if (allowedHosts.has(hostname)) {
227+
if (allowedHosts.has('*') || allowedHosts.has(hostname)) {
228228
return true;
229229
}
230230

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ describe('Validation Utils', () => {
6464
/URL with hostname "google.com" is not allowed/,
6565
);
6666
});
67+
68+
it('should pass for all hostnames when "*" is used', () => {
69+
const allowedHosts = new Set(['*']);
70+
expect(() => validateUrl(new URL('http://example.com'), allowedHosts)).not.toThrow();
71+
expect(() => validateUrl(new URL('http://google.com'), allowedHosts)).not.toThrow();
72+
expect(() => validateUrl(new URL('http://evil.com'), allowedHosts)).not.toThrow();
73+
});
6774
});
6875

6976
describe('validateRequest', () => {
@@ -242,6 +249,15 @@ describe('Validation Utils', () => {
242249
expect(secured.headers.get('host')).toBe('example.com');
243250
});
244251

252+
it('should allow any host header when "*" is used', () => {
253+
const allowedHosts = new Set(['*']);
254+
const req = new Request('http://example.com', {
255+
headers: { 'host': 'evil.com' },
256+
});
257+
const { request: secured } = cloneRequestAndPatchHeaders(req, allowedHosts);
258+
expect(secured.headers.get('host')).toBe('evil.com');
259+
});
260+
245261
it('should validate x-forwarded-host header', async () => {
246262
const req = new Request('http://example.com', {
247263
headers: { 'x-forwarded-host': 'evil.com' },

0 commit comments

Comments
 (0)