fix(dashboard): validate WebSocket proxy peers and origins#20136
fix(dashboard): validate WebSocket proxy peers and origins#20136MrJPlayGround wants to merge 2 commits into
Conversation
|
Hey @MrJPlayGround — the duplicate-detector bot grouped several dashboard-host PRs together; wanted to acknowledge this one specifically because it's the most ambitious scope of the bunch and IMO mostly orthogonal to the others. The Host-header allowlist piece overlaps with the narrower env-only PRs (#29195, #25173, #27113, #28578, #28954) — but the WebSocket peer allowlist ( If you wanted to split:
(I opened #29195 for the env-only narrow case with loopback-only placement + tests. Not trying to displace this PR's scope — just sharing in case splitting helps land at least the WebSocket-peer piece.) |
|
Thanks, this is helpful context. I agree the Host-header allowlist overlaps with the narrower env-only PRs, while the WebSocket peer allowlist / CIDR support and Origin validation are a separate concern. I’ll take a look at splitting this so the maintainer can evaluate:
Appreciate you calling out the overlap without treating the whole PR as duplicate. |
551a5d7 to
25cb2cc
Compare
|
Thanks, this was good guidance. I split the scope and force-pushed this PR down to the WebSocket peer / Origin validation piece only:
I intentionally left the broader HTTP Host-header allowlist out of this PR now, since that overlaps with the narrower Host-header PRs you called out. Tested with: scripts/run_tests.sh tests/hermes_cli/test_web_server.py -qResult: |
|
Adding a real-world deployment datapoint here in case it helps evaluation. We hit this class of failure on a Hermes Dashboard deployment that is intentionally tailnet-only and served through Tailscale Serve. Hermes was bound to an explicit tailnet hostname/IP path rather than exposed publicly. Observed failure mode before the fix:
The important part was that the breakage showed up in the actual browser/Tailscale Serve path, not just in a synthetic localhost-only setup. After applying the narrower bound-host peer acceptance fix locally, we verified successful WebSocket upgrades end-to-end from the tailnet path:
So from an operator standpoint, there is definitely a real deployment need here for reverse-proxy / tailnet WebSocket handling beyond the current loopback-only peer assumption. Not trying to bikeshed the exact implementation shape — just confirming that this problem is reproducible in practice and worth landing in some upstream form. |
…ed-proxy-hosts # Conflicts: # hermes_cli/web_server.py # tests/hermes_cli/test_web_server.py
Cross-linking from related PRsI have a tested My patch (local, not pushed):
Why I didn't open a competing PR:
What I'd offer:
Let me know which (or none). |
e9668c9 to
cd27694
Compare
|
@thunderchick3n I agree with your read: based on the earlier closed CLI-flag PRs, I think the safer shape here is config-based rather than another dashboard CLI flag. This PR is intentionally scoped to the WebSocket peer/origin validation piece, but the HTTP Host-header allowlist is clearly adjacent and probably wants to share the same helper semantics. If you’re willing, the most useful next step would be the A unified diff is totally fine. I can either fold it into this PR if it stays small and keeps the current DNS-rebinding behavior intact, or we can split it into a follow-up if maintainers want this PR to remain WebSocket-only. The constraints I’d want to preserve are exactly the ones you called out:
So: yes, please send the config-setting variant/diff if you have it handy. |
|
Adding another real-world Tailscale Serve datapoint. Setup:
Observed:
Working workaround:
This proves the desired deployment works if Hermes can accept a configured public/reverse-proxy Host while preserving the dashboard security boundary. I think the durable product shape should be config-based rather than another CLI-only flag, e.g.: dashboard:
allowed_hosts:
- my-box.mytailnet.ts.net
trusted_proxy_hosts:
- 127.0.0.1The important security properties:
|
Summary
Narrows this PR to the dashboard WebSocket hardening portion of the original change, leaving the broader HTTP Host-header allowlist overlap for separate review / the narrower competing PRs.
dashboard.trusted_proxy_hosts.HostandOriginheaders for/api/pty,/api/ws,/api/pub, and/api/events.Motivation
The dashboard can be safely exposed inside a private reverse-proxy/tailnet setup by keeping Hermes bound to loopback and explicitly trusting the reverse proxy for dashboard WebSocket upgrades.
The previous version bundled this with a broader HTTP Host-header allowlist. That overlapped with narrower Host-header PRs, so this version focuses on the WebSocket peer / Origin validation security boundary.
Test plan
Result: