Files
airship/apps
Nayan 6c3d84f1d3 feat(server): loopback bind, host allowlist, and an origin gate
The proxy bound every interface — server.listen with no host — and the
control WebSocket completed any upgrade with no Origin or Host check. That
socket drives a coding agent with write access to the project, so anyone
routable to the machine, or any page open in the developer's browser
(WebSocket handshakes are not subject to CORS), could edit files, commit,
and open PRs under the user's identity. Reported in #15 by yarikbright,
with the repro this change's tests replay.

Three checks, each stopping an attack the other two do not:

- bind 127.0.0.1 by default (the posture opencode-server.ts always had) —
  stops the LAN attacker; --host / AIRSHIP_HOST opts out, with a loud
  launch warning that a wide bind is an unauthenticated agent
- an exact-match Host allowlist (localhost and IP literals always pass;
  --allowed-hosts adds names) — stops DNS rebinding, where the attacker's
  Origin and Host match and an Origin check alone waves them through
- Origin-matches-Host on every upgrade, control socket and HMR tunnel
  alike, refused with a real 403 before the upgrade completes. An absent
  Origin (curl, CLI) is allowed; Origin: null is not.

The Host gate sits ahead of even the editor's own assets, so a blocked
page cannot fetch overlay.js. requireHost accepts IPv6 literals bare or
bracketed, EADDRNOTAVAIL now names the interface it could not find, and
the printed URL follows the bind (wildcards present as localhost so it
stays clickable). SECURITY.md states the model and its deliberate limits.
The README's "runs entirely on localhost" is now an enforced default
rather than an unchecked claim.

Reported-by: yarikbright
Closes #15
2026-08-16 13:05:37 +05:30
..