fix: only trust X-Forwarded-* headers from local/LAN proxies#1987
Conversation
Forwarded headers (X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-Port) can be injected by remote attackers to poison the application base URL, enabling password reset token hijacking, open redirects, cache poisoning, and JavaScript asset replacement. Introduce is_trusted_proxy() which validates REMOTE_ADDR against loopback and RFC 1918 private ranges before forwarded headers are acted upon. Public IPs are untrusted and fall back to the real Host header. Local tunnel services (Dataplicity, ngrok) and reverse proxies (nginx, HA ingress) continue to work automatically as their agents connect via 127.0.0.1 or a LAN address.
|
@alexandrecuer interested in your feedback on this one if you have a moment. |
|
Good idea, but what does Claude think this is going to do for IPv6? Edit: Oh I see it's accepting ::1. That's fine. |
|
@TrystanLea : I've seen that fix/trusted-proxy-host-header-injection is on version 11.11.3 Last time I've built for 11.9.11
I have built with that fix, using last versions of modules and symodules : https://hub.docker.com/layers/alexjunk/emoncms/alpine3.20_emoncms11.11.3 I have to test if ingress is allright |
|
Just tested and it seems to work
For those of yours using home-assistant, you can also test, I've migrated the next branch to the last container 11.11.3 Add in the next branch to the store : https://github.com/Open-Building-Management/emoncms#next
|
| // FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE causes | ||
| // filter_var to return false for private/reserved ranges, true for public. | ||
| // So a private/loopback address returns false here, meaning is_trusted_proxy() = true. | ||
| return filter_var( |
There was a problem hiding this comment.
Public IP → filter_var() returns IP → === false → false
Private or reserved IP → filter_var() returns false → === false → true
There was a problem hiding this comment.
| REMOTE_ADDR | trusted ? |
|---|---|
| 127.0.0.1 | yes |
| ::1 | yes |
| 172.18.0.2 | yes |
| 192.168.1.10 | yes |
| 10.0.0.5 | yes |
| IP publique Internet | no |
|
Thanks much appreciated, glad that's all working fine. Will merge and do a forum post alongside all the other already merged hardening changes 👍 I think the worst issue was a potential secondary SQL injection in the dashboard clone feature, that's patched now in latest release. |
|
@TrystanLea : once you merge, i will rebuild the container with all the versions of components fixed...just used master for the tests, but use specific versions is more clear for tracability. |
|
Thanks @alexandrecuer v11.12.1 is now released and available on stable |


Important for anyone self-hosting emoncms in public web accessible context.
LLM (Claude) security audit suggestion:
Forwarded headers (X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-Port) can be injected by remote attackers to poison the application base URL, enabling password reset token hijacking, open redirects, cache poisoning, and JavaScript asset replacement.
Introduce is_trusted_proxy() which validates REMOTE_ADDR against loopback and RFC 1918 private ranges before forwarded headers are acted upon. Public IPs are untrusted and fall back to the real Host header.
Local tunnel services (Dataplicity, ngrok) and reverse proxies (nginx, HA ingress) continue to work automatically as their agents connect via 127.0.0.1 or a LAN address.
Immediate solution: Use manual domain setting
There is a manual domain setting that can be set in settings.ini that avoids the use of X-Forwarded-... The manual setting has been in place for a long time and is the approach used by emoncms.org.