## Summary
Radar's host local terminal launches a shell as the Radar process's
operating-system user, so it is intended for direct local use rather
than shared or authenticated deployments. This change makes the server
enforce that boundary and reports the same result to the UI.
## What changed
- Make `/api/local-terminal` unavailable when disabled, authenticated,
in-cluster, reached through the authenticated Hub tunnel, bound to a
shared listener, or requested with a non-loopback `Host`.
- Reject non-loopback `Host` values across an unauthenticated loopback
instance, preventing an external hostname from being treated as a local
client.
- Apply one WebSocket Origin policy to the host terminal and pod exec:
normalized authority matching, TLS downgrade rejection when transport
evidence is available, authenticated-tunnel handling, and the existing
exact Vite development path. Host-rewriting intermediaries are supported
when a browser supplies same-origin Fetch Metadata; otherwise the
stricter authority match applies.
- Compute request-specific capabilities on a copy so one request cannot
change cached capability state for another. The terminal capability
remains available during a disconnected direct-local session, which
preserves its recovery use case.
- Hide terminal actions when the capability is unavailable while
retaining copyable recovery and Diagnose commands. Standalone library
surfaces fail closed when no capabilities provider is mounted.
- Document the supported local-listener and reverse-proxy
configurations.
No new configuration flag is introduced. Pod file transfer, port
forwarding, and exec-session lifecycle are intentionally outside this
focused change.
## Testing
- `make tsc`
- `make test`
- `make build`
- Focused Origin, `Host`, local-terminal availability, disconnected
capability, tunnel, capability-cache, and recovery-UI regression tests
- Real local binary/browser smoke tests against a connected cluster and
an isolated unreachable cluster: opened the UI, ran harmless commands in
the host terminal, confirmed disconnected recovery remains available,
and verified hostile `Host` rejection
- Deployment smoke checks for `--disable-local-terminal`, proxy-auth
denial/401 behavior, and cross-origin WebSocket rejection
Visual testing was skipped because the UI delta conditionally removes
unavailable actions and is covered by component tests plus live browser
checks. Shared-listener and authenticated-tunnel cases are covered by
integration tests; this run did not expose a workstation listener on
`0.0.0.0` or use a live Hub tunnel.