Skip to main content
← Back to list
01Issue
BugOpenSwamp CLIPublic
AssigneesNone

Relationships

#3892 WS handshake drops the port from the Origin, so a --trusted-hosts entry with a port never matches (follow-up to #1840)

Opened by thomas · 10/24/2026

Situation

I publish swamp serve --dashboard on my tailnet with tailscale serve, which terminates TLS and forwards to serve on loopback. Tailscale gives each machine a single HTTPS hostname, and port 443 on that name already serves something else, so the dashboard has to be published on another port. The public address is https://swamp.example.ts.net:9443, and the browser sends Origin: https://swamp.example.ts.net:9443.

No single --trusted-hosts entry lets the dashboard both sign in and open its WebSocket:

  • --trusted-hosts swamp.example.ts.net:9443: sign-in succeeds, then every WebSocket upgrade is rejected: WebSocket upgrade rejected: "untrusted origin: https://swamp.example.ts.net:9443"
  • --trusted-hosts swamp.example.ts.net: the WebSocket would pass, but sign-in is rejected: Dashboard session request rejected: "origin does not match request authority: https://swamp.example.ts.net:9443"
  • Workaround: list both, --trusted-hosts swamp.example.ts.net,swamp.example.ts.net:9443.

Setup

tailscale serve --bg --https=9443 http://127.0.0.1:9090
swamp serve --host 127.0.0.1 --port 9090 --auth-mode oauth --admins <me> --allowed-users <me> \
  --dashboard --trusted-hosts <one of the values above>

Reproduction without a tailnet or OAuth

swamp serve --port 19090 --auth-mode token --admins user:repro --dashboard --no-schedule \
  --trusted-hosts swamp.example.ts.net:9443

O=https://swamp.example.ts.net:9443; H=swamp.example.ts.net:9443
# WebSocket upgrade
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:19090/ -H "Host: $H" -H "Origin: $O" \
  -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' \
  -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ=='
# Dashboard session
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:19090/auth/dashboard/session \
  -H "Host: $H" -H "Origin: $O" -H 'content-type: application/json' -d '{}'
--trusted-hosts WebSocket upgrade Dashboard session
swamp.example.ts.net:9443 403, untrusted origin passes the origin check (400, empty body)
swamp.example.ts.net passes the origin check (401, no token) 403, origin does not match request authority
both passes (401) passes (400)

Cause

The two checks treat the port differently:

  • The WebSocket check (validateOrigin / trustedOrigins, from #1840) cuts the Origin down to protocol//hostname before comparing it with https://<entry>. An entry with a port can never match, and an Origin with a port matches only an entry without one.
  • The dashboard check (validateDashboardSessionOrigin) compares the full origin, port included, with https://<entry>. A bare-hostname entry matches only port 443.

Version

20261024.204655.0

Suggested fixes

  1. Make both checks treat the port the same way. For example, keep the port in the WebSocket check's originBase so that a host:port entry works for both checks. A bare-hostname entry would then still mean port 443, as it already does in the dashboard check.
  2. Or document how to write --trusted-hosts entries for a port other than 443.
  3. Alternatively, bring back the --trusted-origins setting proposed in #1840, so the exact browser origin (https://host:9443) can be given.
02Bog Flow
◉OPEN○TRIAGED○IN PROGRESS○SHIPPED

Open

10/24/2026, 10:11:45 PM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.