#7852: fix(gateway): use port from Host header for canvasHostUrl (reverse proxy support)
stale
Cluster:
Gateway Resilience and Configuration
## Summary
Fixes a bug where mobile apps fail to load A2UI when connecting through a reverse proxy (e.g., Tailscale Serve 443 → 18789).
**Root cause:** The gateway always used the internal `canvasPort` when constructing `canvasHostUrl`, ignoring the port from the HTTP Host header.
**Fix:** Parse the port from the Host header and prefer it over the internal `canvasPort`.
---
## Problem
When connecting via reverse proxy:
| Host Header | canvasPort | Before (broken) | After (fixed) |
|-------------|------------|-----------------|---------------|
| `node.tailnet:443` | 18789 | `https://node.tailnet:18789` ❌ | `https://node.tailnet:443` ✅ |
Mobile apps tried to load A2UI from port 18789, which isn't exposed through the reverse proxy → connection failed.
---
## Changes
### `src/infra/canvas-host-url.ts`
1. **Added `parseHostPort()`** - Extracts port from Host header (e.g., `"node.tailnet:443"` → `443`)
2. **Fixed `parseHostHeader()`** - Strip IPv6 brackets (Node 25+ returns them in `hostname`)
3. **Updated `resolveCanvasHostUrl()`** - Prefer Host header port, fall back to `canvasPort`
### `src/infra/canvas-host-url.test.ts` (new)
11 test cases covering:
- Tailscale Serve regression case
- Fallback to canvasPort when no port in Host header
- Direct connections, IPv6, default ports (80/443)
- Edge cases (invalid headers, missing values)
---
## Backwards Compatibility
✅ **No breaking changes** - If Host header has no port, falls back to existing `canvasPort` behavior.
---
## Testing
### Automated
```
✓ src/infra/canvas-host-url.test.ts (11 tests) 3ms
```
### Manual
- **Environment:** Tailscale Serve (443 → 18789)
- **Result:** A2UI loads successfully with correct port
```
[GatewayNode] handlePush(.snapshot) raw canvasHostUrl='https://ubuntu-4gb-hel1-1.tailed3476.ts.net:443'
[ScreenController] didFinish url='https://ubuntu-4gb-hel1-1.tailed3476.ts.net/__openclaw__/a2ui/?platform=ios'
```
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR updates `resolveCanvasHostUrl()` to prefer the explicit port from the incoming HTTP `Host` header (via a new `parseHostPort()` helper), falling back to the configured internal `canvasPort` when the header has no port. It also adjusts `parseHostHeader()` to strip IPv6 brackets and adds a new Vitest suite covering reverse-proxy, IPv6, default-port, and fallback scenarios. Overall this aligns the constructed A2UI base URL with what reverse proxies actually expose (e.g., Tailscale Serve 443 → internal port).
<h3>Confidence Score: 4/5</h3>
- This PR is largely safe to merge and fixes a real reverse-proxy correctness issue, with minor edge-case concerns around port selection/validation.
- Core change is small, covered by targeted tests, and uses the URL parser to handle IPv4/IPv6/host:port forms. The main risks are behavioral surprises when `hostOverride` is set (hostname overridden but port still taken from `Host`) and lack of numeric range validation for parsed ports, which could yield invalid URLs in misconfigured environments.
- src/infra/canvas-host-url.ts
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#19437: Gateway: respect custom bind host for local health/RPC target resol...
by frudas24 · 2026-02-17
76.3%
#11205: Android: fix gateway connection and canvas URL for Tailscale serve
by emonty · 2026-02-07
75.9%
#16300: fix(tui): respect gateway bind mode in TUI connection
by cortexuvula · 2026-02-14
75.8%
#11740: fix(gateway): remove IP-based canvas auth fallback
by coygeek · 2026-02-08
75.3%
#8745: fix(gateway): respect gateway.port config and --port CLI flag
by revenuestack · 2026-02-04
75.2%
#14564: fix(gateway): crashes on startup when tailscale meets non-loopback ...
by yinghaosang · 2026-02-12
74.9%
#13321: android/gateway: harden manual connect identity and A2UI UX
by m888m · 2026-02-10
74.8%
#22453: fix(tui): resolve and pass tlsFingerprint for secure connections [A...
by captmoss · 2026-02-21
74.4%
#21772: [Bug]: Allow ws:// to Tailscale CGNAT addresses
by AIflow-Labs · 2026-02-20
73.7%
#22056: fix(gateway): use loopback for self-connections regardless of bind ...
by usedhonda · 2026-02-20
73.6%