-
Notifications
You must be signed in to change notification settings - Fork 11.9k
Description
Command
other
Is this a regression?
- Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was
21.1.4
Description
My SSR application runs in a Kubernetes cluster, and behind a load balancer. After updating @angular/ssr to version 21.1.5, which contains the SSRF fix #32516, I am getting the following error logged server-side for every request:
URL with hostname "10.x.x.x" is not allowed.Please provide a list of allowed hosts in the "allowedHosts" option in the "CommonEngine" constructor.
Falling back to client side rendering. This will become a 400 Bad Request in a future major version.
The address 10.x.x.x varies from one request to the next, because Kubernetes spins up multiple containers, each with a different private address.
I assume that either the load balancer or Kubernetes itself (not sure which) is passing the real hostname as x-forwarded-host: my.real.hostname, while setting host: 10.x.x.x on the internal request, where 10.x.x.x is the dynamic internal IP request of each container instance.
I cannot resolve this with the allowedHosts option (or the NG_ALLOWED_HOSTS environment variable), because this option only supports either exact hostnames or a wildcard prefix such as *.domain. It does not support a wildcard postfix such as 10.*. I can't even hack a fix by using just *, as this also fails to match.
I can think of two possible solutions, each very different:
- Extend the
allowedHostsoption to support glob-style wildcards. Then I could setallowedHoststo['10.*', 'my.real.hostname'](allowing for both headers). - If the later code that this validation is protecting always derives the real hostname as
x-forwarded-host || host(pseudocode), then this SSRF protection should only need to validate thehostheader ifx-forwarded-hostis not present. Then I could setallowedHoststo just['my.real.hostname']and the private IP address inhostwouldn't matter.
Minimal Reproduction
- Create a new application with the latest versions, with
@angular/ssrat version 21.1.5 or 21.2.0, and SSR enabled. - Add at least one
RenderMode.Serverpage in the server routes. - Running the compiled module locally with Node.js, verify that the
URL with hostname "localhost" is not allowederror occurs, but can be resolved by settingNG_ALLOWED_HOSTS=localhost, and that the SSR route is now server-side rendered. - Build the compiled app into a Node.js container image.
- Deploy the container to a cloud service and give it a public hostname, but in such a way that traffic will be routed internally using a private IP address as the
hostheader. (It may also be possible to reproduce running a Kubernetes cluster locally without a load balancer. I haven't verified this.) - Access the SSR route, and observe that the page is not server-side rendered, and the
URL with hostname "10.x.x.x" is not allowederror (or equivalent IP address) is logged out by the container. - Try setting the
NG_ALLOWED_HOSTSenvironment variable on the container, and observe that it is not possible to find a value that successfully resolves the error, except supplying the public hostname and the exact private IP address – until it changes. Any other value you try using a wildcard results in a 400 error.
I can try to create a simple example repo if necessary, but I hope this explanation is enough, together with inspecting the code change introduced in #32516.
Exception or Error
URL with hostname "10.x.x.x" is not allowed.Please provide a list of allowed hosts in the "allowedHosts" option in the "CommonEngine" constructor.
Falling back to client side rendering. This will become a 400 Bad Request in a future major version.
Your Environment
┌───────────────────────────────────┬───────────────────┬───────────────────┐
│ Package │ Installed Version │ Requested Version │
├───────────────────────────────────┼───────────────────┼───────────────────┤
│ @angular-devkit/build-angular │ 21.1.5 │ 21.1.5 │
│ @angular-devkit/core │ 21.1.5 │ 21.1.5 │
│ @angular-devkit/schematics │ 21.1.5 │ 21.1.5 │
│ @angular/animations │ 21.1.6 │ 21.1.6 │
│ @angular/build │ 21.1.5 │ 21.1.5 │
│ @angular/cdk │ 21.1.6 │ 21.1.6 │
│ @angular/cli │ 21.1.5 │ 21.1.5 │
│ @angular/common │ 21.1.6 │ 21.1.6 │
│ @angular/compiler │ 21.1.6 │ 21.1.6 │
│ @angular/compiler-cli │ 21.1.6 │ 21.1.6 │
│ @angular/core │ 21.1.6 │ 21.1.6 │
│ @angular/forms │ 21.1.6 │ 21.1.6 │
│ @angular/language-service │ 21.1.6 │ 21.1.6 │
│ @angular/localize │ 21.1.6 │ 21.1.6 │
│ @angular/material │ 21.1.6 │ 21.1.6 │
│ @angular/platform-browser │ 21.1.6 │ 21.1.6 │
│ @angular/platform-browser-dynamic │ 21.1.6 │ 21.1.6 │
│ @angular/platform-server │ 21.1.6 │ 21.1.6 │
│ @angular/router │ 21.1.6 │ 21.1.6 │
│ @angular/ssr │ 21.1.5 │ 21.1.5 │
│ @schematics/angular │ 21.1.5 │ 21.1.5 │
│ rxjs │ 7.8.2 │ 7.8.2 │
│ typescript │ 5.9.3 │ 5.9.3 │
│ zone.js │ 0.16.0 │ 0.16.0 │
└───────────────────────────────────┴───────────────────┴───────────────────┘
Anything else relevant?
I am using Google Kubernetes Engine behind Google Load Balancer, but other platforms may also show the issue. (I'm not certain whether it's the Load Balancer or Kubernetes that is using the IP address as the host header; possibly the latter, in which case it might be possible to reproduce the issue running Kubernetes locally.)