Cloud VM: a chat move in and folder trust need the caller's vmKind owner, never the VM's record
Built on #26323 (PR 3, merged as a6c49dbd358), which adds the app's per-link vmKind answer (CloudAttachEndpoint.callerOwnsVM, CloudMachineLink.callerOwnsVM) and vm_kind on agent-session-start. This PR needs that field. It is now rebased onto feat-cmux-next after #26323, with patches identical to the reviewed stack (range-diff =).
The rule (claude-sr2, relayed by the area owner): "the kind comes from outside the guest. The client side (the app or the daemon that starts the remote session) asks the Cloud API which kind the VM is, for the signed-in account, and passes that to the remote start; the guest never decides." And: "Agents keep sudo in Cloud VMs". #26323 applied that rule to agent-session-start only. A chat move in and folder trust on a Cloud VM still trusted the guest's own /etc/cmux/vm-kind owner record, which any agent there can write.
What changes
- Daemon (cmux-tui-core):
agent-transfer-receive,agent-transfer-finishandagent-session-trusttake avm_kindparam and use the same gate asagent-session-start:- On a Cloud VM they go on only on the exact
vm_kind: "owner". - The VM's own record never counts.
- Team evidence refuses even with the caller's word.
- With no word they fail closed (
agent_session.host_unverified). - Macs and SSH hosts need no word.
- The old
Nonepath that took the record is gone:host_gatenow takes a plain bool.
- On a Cloud VM they go on only on the exact
- Page path: a page's routed
agent_session.trust.get/setcannot vouch for the Cloud API's answer. It takes novm_kind(bad_request), so it reaches no Cloud VM's trust. - Capabilities: new
agent-transfer-vm-kind-v1andagent-session-trust-vm-kind-v1. - Pump (
cmux link agent move --vm-kind KIND):- Sends the kind on the receive and the finish to a target that serves the capability.
- The flag is also the caller's word that the target is a Cloud machine. An older target would still trust its own record, so the pump refuses any kind but
ownerthere (host_unverified,side: target) before the chat freezes. What the target advertises (fs-v1or not) never changes that, because a guest can hide any sign of being a Cloud VM. - Without the flag the caller says the target is not a Cloud machine (Bring Back Local's Mac). A current Cloud daemon still refuses a chat with no word.
- App:
- Move to Cloud passes
--vm-kind owner|nonefrom the running link's answer, read at the move and never kept. The owner word names the link process it came from:--to-pidisCloudMachineLink.ownerPID. The pump sends the receive and the finish only over a connection whose peer is that process (SO_PEERCRED/LOCAL_PEERPID, viacmux_link::caller::peer_pid, nowpub). So a link that restarts while the source's begin is pending never receives the old link's answer. Because the socket path stays the same, a rerun still resolves an unresolved move. - Remote folder trust takes the start's rule from #26323's review fix (
8fa90e61b79). The app reads the link's owner answer (CloudMachineLink.ownerLink, a per-link id) before the connection and again as it sends. A Cloud machine's trust is refused without that answer, through any daemon.vm_kind: "owner"goes only to a daemon whose ownidentifyon that connection listsagent-session-trust-vm-kind-v1.
- Move to Cloud passes
- Daemon gate: a
vm_kindother thanowneris refused (host_unverified) on any host for start, move-in and trust, because a guest's root can remove every Cloud sign the daemon reads. - Not changed: MDM's refusals. Nothing here loosens any check; on a Cloud VM every path only gets stricter.
Risk
- Medium. Move to Cloud and remote folder trust on an updated Cloud VM now need the app's link to carry the Cloud API's
owneranswer. - The legacy
/api/vmlink carries it (#26323). The app-server link carries none yet, so a move or a trust read through it is refused, and Move to Cloud already refuses that link (linkUnsupported). - Bring Back Local is not affected, because its target is the Mac.
Tests (cmux-ci, Linux)
- Head
b45560da004(on #26323's8ad048a1e9c), run https://github.com/manaflow-ai/cmux/actions/runs/40590890295: green. An explicit"vm_kind": nullrefuses on trust, receive and finish, as on start (present_nullable). Tested on a host with no Cloud sign:a_word_that_is_not_owner_refuses_on_any_hostand the trust test. Clippy ran without thecollapsible_ifallowance, because the tip has the fix. Earlier heads, all green:73a5f87134a(run 40589090968),fb6883a2fd1(run 40586211848),d5506bb1052(run 40583784546),40d8fa0661c(run 40570069438),7af51f7ed48(run 40563544239),c0b87266ae7(run 40558028188).- fmt check and clippy
-D warnings. (The earlier runs allowedclippy::collapsible_ifbecause #26323's old base had that red.) cmux-tui-coreserver::: 1069 passed.cli agent_session: 36 passed.link_agent_move,cloud_move_journey,machine_update_journey,bring_back_journey,access_request_journey: all passed.link::bin tests: passed.
- fmt check and clippy
- Base (#26323's head plus only the three new tests), run https://github.com/manaflow-ai/cmux/actions/runs/40558019316: each new test fails.
a_cloud_vm_takes_a_chat_only_on_the_callers_owner_word: a receive with no word succeeded on the owner record.agent_session_trust_on_a_cloud_vm_needs_the_callers_owner_word: a trust read with no word succeeded.the_cloud_apis_kind_reaches_the_target_or_an_older_one_is_refused: the pump has no--vm-kind. At the head this test also covers an older target that advertises nothing Cloud-like, given--vm-kind teamornone: it is refused beforeagent-transfer-begin.
- New at the head:
link_agent_move::the_owner_word_reaches_only_the_link_process_it_came_from: with another process at the socket, the pump sends no receive and novm_kind, and the chat is given back. With the same process, the chat moves withowner.CloudLinkReconnectingTests.theOwnerWordNamesTheLinkProcessItCameFrom(Swift):ownerPIDis the link's pid, nil once the link ends, and the replacement's own pid after that.
- Updated:
cloud_move_journeyandmachine_update_journeynow pass--vm-kind owner/vm_kind. The journey also checks that the bare andteamforms are refused. - Swift:
AgentSessionStartOwnerLinkTests.aCloudTrustCallGoesOutOnlyOnTheOwnerAnswerOfTheSameLink(real line socket);CloudMoveTestsnow expects--vm-kind nonewith no owner link. Compiled by PR CI only.
No capture: nothing visible changes. The Swift edits only decide whether --vm-kind owner / vm_kind is sent for Move to Cloud and remote folder trust. A refusal shows through the existing agent_session.host_unverified strings, and a fleet capture mini has no live owned Cloud link to show either path.
Agent session: agent session
š¤ Generated with Claude Code