Skip to content

Serve CORS so the cockpit can reach the daemon (D-3) - #68

Merged
adbarc92 merged 1 commit into
mainfrom
fix/d3-fleetd-cors
Sep 8, 2026
Merged

Serve CORS so the cockpit can reach the daemon (D-3)#68
adbarc92 merged 1 commit into
mainfrom
fix/d3-fleetd-cors

Conversation

@adbarc92

@adbarc92 adbarc92 commented Sep 7, 2026

Copy link
Copy Markdown
Owner

fleetd served no CORS headers at allOPTIONS /missions answered 405, and no response carried an Access-Control-Allow-Origin.

This does not merely degrade the FLEET ops grid. store.reconnect() calls listUnits() over HTTP before opening any per-unit stream, so a missing layer blocks discovery entirely and the grid renders empty. Filed as a pre-existing main defect on 2026-08-16 (blocking smoke items 1.2 / 1.4a / 1.7); re-verified still real 2026-09-07.

The allowlist is the point

Deliberately never Any. fleetd binds loopback but takes commands — POST /missions, POST /units/:id/commands — and loopback is reachable from every website the operator's browser visits. The bind address is not a security boundary against a browser; with a permissive origin, any page could drive the daemon.

Defaults cover the Tauri webview on both platforms (the origin differs — tauri://localhost vs http://tauri.localhost) and the Vite dev server. FLEETD_ALLOWED_ORIGINS overrides with a comma-separated list. Credentials stay disabled: the daemon has no cookie or Authorization auth, so allowing them would only widen what a permitted origin can do.

Dependency cost, measured

tower-http is the one crate this addstower 0.5.3 was already resolved via axum, and the lock diff confirms it:

$ git diff Cargo.lock | grep '^+name'
+name = "tower-http"

Hand-rolling the headers was the alternative and was rejected: Vary: Origin is easy to omit and a missing one is a cache-poisoning bug, and preflight has enough corners to be worth a maintained implementation. The tower dev-dependency for ServiceExt::oneshot adds nothing, being already in the lock.

Driven red in both directions

layer removed (the D-3 state)  →  preflight + allowed-origin FAIL
                                  unlisted-origin correctly PASSES
                                  (no headers at all is right there)
allowlist widened to `Any`     →  unlisted-origin FAILS

The second is the one worth keeping — it is what stops a future "just make CORS work" change from handing every website on the internet a command channel.

fleetd 87 → 90 tests. Workspace check clean.

🤖 Generated with Claude Code

fleetd served no CORS headers at all - OPTIONS /missions answered 405 and no
response carried an Access-Control-Allow-Origin. Every browser fetch from the
cockpit failed, which does not merely degrade the FLEET ops grid: store
.reconnect() calls listUnits() over HTTP before opening any per-unit stream, so
a missing layer blocks discovery entirely and the grid renders empty. Filed as
a pre-existing main defect on 2026-08-16, blocking smoke items 1.2, 1.4a and
1.7; re-verified still real on 2026-09-07.

The origin list is an allowlist, deliberately never Any. fleetd binds loopback
but takes commands - POST /missions, POST /units/:id/commands - and loopback is
reachable from every website the operator's browser visits. The bind address is
not a security boundary against a browser, so a permissive origin would let any
page drive the daemon. Defaults cover the Tauri webview on both platforms
(the origin differs) and the Vite dev server; FLEETD_ALLOWED_ORIGINS overrides.
Credentials stay disabled: the daemon has no cookie or Authorization auth, so
allowing them would only widen what a permitted origin can do.

tower-http is the one crate this adds to the graph - tower 0.5.3 was already
resolved via axum, and the lock diff confirms tower-http is the only new entry.
Hand-rolling the headers was the alternative and was rejected: Vary: Origin is
easy to omit and a missing one is a cache-poisoning bug, and preflight has
enough corners to be worth a maintained implementation. The tower dev-dependency
for ServiceExt::oneshot adds nothing, being already in the lock.

Three tests, driven red in both directions rather than one:

  layer removed (the D-3 state)  -> preflight and allowed-origin FAIL, while
                                    the unlisted-origin test correctly passes,
                                    since no headers at all is right there
  allowlist widened to Any       -> the unlisted-origin test FAILS

The second is the one worth keeping: it is what stops a future "just make CORS
work" change from handing every website on the internet a command channel.

fleetd 87 -> 90 tests. Workspace check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@adbarc92
adbarc92 merged commit 0764f5d into main Sep 8, 2026
14 of 16 checks passed
@adbarc92
adbarc92 deleted the fix/d3-fleetd-cors branch September 8, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant