fix(connection): keep LAN serving working under the /api browser-trust fence

Markerless requests pass on any Host (a non-browser sender is the principal
and forges headers anyway); browser Host matching gains port-less entries and
WHATWG normalization; dsh derives LAN IP-literal authorities for an
all-interfaces bind and web grows --trusted-host for named ones.
This commit is contained in:
creatixchu
2026-07-28 15:40:02 +08:00
parent d1ce22e7ad
commit 01eea07bab
20 changed files with 199 additions and 66 deletions

View File

@@ -6,7 +6,7 @@ Wire consumer layer: the client plugin's apply mounts `ctx.connection` (shared a
## /api browser-trust fence
The node half guards every request under `/api` before bridging (`src/api-request-trust.ts`): the `Host` header must be a loopback authority or an exact `host[:port]` entry from the plugin's `trustedHosts` config (DNS-rebinding defense), an attached `Origin` must equal that authority, and an explicit `sec-fetch-site: cross-site` marker is refused. Requests without browser markers (curl, tests, native clients) pass — without a browser there is no confused deputy. Failures answer plain 403 before any RPC dispatch. A non-loopback (`--host 0.0.0.0`) deployment must therefore list the authorities it is reached by in `trustedHosts`; the fence is deliberately not an authentication layer — reachability policy stays with the webserver binding, and auth remains deferred work. Decision record: [the api browser-trust boundary Agent Note](../../../.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md).
The node half guards every request under `/api` before bridging (`src/api-request-trust.ts`). Requests without browser markers (no `Origin`, no `sec-fetch-site` — curl, tests, native clients) pass on any Host: without a browser there is no confused deputy, and such a sender forges every header anyway. For browser requests, the `Host` header must be a loopback authority or match a `trustedHosts` entry — exact on `host:port` entries, any port on port-less entries, both sides compared through WHATWG normalization (DNS-rebinding defense); an attached `Origin` must equal that authority, and an explicit `sec-fetch-site: cross-site` marker is refused. Failures answer plain 403 before any RPC dispatch. A non-loopback (`--host 0.0.0.0`) deployment therefore needs its serving authorities trusted: the dsh CLI derives the machine's LAN IP literals itself and its `--trusted-host` flag declares named ones, so `trustedHosts` in cordis.yml is for compositions the CLI does not boot. The fence is deliberately not an authentication layer — reachability policy stays with the webserver binding, and auth remains deferred work. Decision record: [the api browser-trust boundary Agent Note](../../../.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md).
## Keyless fixture