Stop Connect daemons before revocation - #859
Conversation
Co-authored-by: Möbius Agent <mobius-agent@users.noreply.github.com>
miljanm
left a comment
There was a problem hiding this comment.
Reviewer: QA second look
I found the following concrete risks. I’ve kept this focused on issues with a supported failure mode rather than style preferences.
HIGH · force=true cannot revoke an online runner that refuses to acknowledge disconnect
backend/app/routes/connect.py:372 · security
Reviewer rule: security.authority_boundary
A compromised or merely malfunctioning runner that keeps its SSE stream open and either nacks the disconnect (exit_code!=0) or never closes gates the owner's revocation on its own cooperation. Every DELETE — including ?force=true — then fails and preserves the still-valid machine token, inverting the purpose of revocation for exactly the untrusted-machine case it exists to handle. The only owner-side recovery is restarting Möbius to clear in-memory _channels, since /disconnect requires the host's own bearer.
Evidence: In delete_host the new force flag is only consulted inside the if ch is None (offline) branch. When a channel exists the code always takes else: daemon = await _ask_runner_to_disconnect(ch), and that helper raises HTTPException 502 on a nonzero uninstall result or 504 when ch.closed never fires — both before _forget_host(host_id) runs. Tests test_failed_daemon_cleanup_keeps_the_connection (502) and test_unconfirmed_legacy_shutdown_keeps_the_connection (504) assert the host and channel are retained after failure.
Suggested direction: Have force=true bypass the online ack requirement: perform a best-effort disconnect on the online branch but still call _forget_host unconditionally when force is set, so the owner can always revoke a token even when a runner refuses to confirm shutdown.
Reviewed revision 599c33a1275b.
Summary
Follow-up to #854, which introduced secure external machine pairing.
Testing
14 passed— focused Connect route and runner tests83 passed— backend host contract suite