security: scope Move to Cloud Always consent to account and team (SW-16)
SW-16: make Move to Cloud consent accountable to the signed-in destination
An agent can request Always when moving a chat's folder to Cloud. Previously the app stored approved folder paths in cmux.cloud.moveAlwaysFolders and silently reused them after the person switched Cloud accounts, teams or backend origins. That allowed a previous approval to authorize a new destination.
Change
- Store grants as
(user ID, team ID, Cloud API origin, folder)inCloudMoveAlwaysGrants, with no token data. - Require a signed-in user and selected team before offering the persistent Always choice; Move remains a one-time gesture.
- Capture the Cloud scope when the agent asks and check it again after folder lookup and when the person clicks the card. A stale card cannot start a move or persist a grant in a new Cloud scope.
- Ask first next time revokes only the grant in the original scope.
- Deliberately retire the old folder-only preference. It lacks the originating account/team and cannot be migrated safely; the user approves again once after upgrade.
- Preserve unfinished-move retry offers separately.
Validation
CloudMoveAlwaysGrantsTests.swift: user switch, team switch, API origin switch, folder mismatch, missing identity, corrupt preference, revocation isolation, persistence, and legacy preference refusal.- Updated the existing
CloudMoveTestscard expectation for signed-out or unselected-team states. - A standalone Swift 6 compilation and local deterministic assertion harness for the grant helper only passed (legacy data, user/team/origin/folder isolation, revoke). The native macOS app tests and full build require the project's fleet Mac runner and remain unexecuted in this session. CI and owner review are required before merge.
Threat and deliberately bounded scope
- Assumes a process/agent can request a Cloud move and the person may switch accounts or teams between requests.
- This fixes persistent consent reuse, not the separate caller-provenance concern in
CloudMoveAsks.isAgent/ action routing (an unmarked agent CLI invocation could be treated as a person's run), nor the remote-deletion-ack gap inCloudAccessRevokes. See private security audit HQ #5286. - A deeper consent contract should bind the authenticated actor, chat/session, canonical source path, exact destination machine/generation, authorized operation, expiry, and a server-verifiable approval receipt. Account switching, revocation, replay, and lost acknowledgments need end-to-end tests.
- This branch modifies no backend permissions or production policy. No deploy/merge.
Base: feat-cmux-next at 0ef18a094a45571024219225ad84b5ceff9db1fb (source reviewed first). Owner/reviewer: NEXT Mac Cloud consent/action-routing lane. HQ brief: SW-16.
Next action: fleet-run CloudMoveAlwaysGrantsTests + CloudMoveTests, verify stale account-switch card with an app-level fixture, and review before enabling Always again.
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes a security gap where the "Always move to Cloud" consent was stored per folder only, so a prior approval could silently authorize a move after the person switched Cloud accounts, teams, or backend origins. Persistent consent is now scoped to the signed-in user, team, Cloud API origin, and folder.
- Stores grants as (user ID, team ID, API origin, folder) with no token data; "Always" is only offered when signed in with a selected team, while "Move" stays a one-time gesture.
- Re-checks the Cloud scope after folder lookup and when the card is clicked, so a stale card can't start a move or persist a grant in a new scope.
- "Ask first next time" revokes only the grant in the original scope, even after a switch; the old folder-only preference is retired and the person approves once again.
- Updates
DebugCloudMoveto read and seed the scoped grants and its restore journey to expect the new scope label, fixing a compile error from the removed folder-only preference. - Adds tests covering account, team, and origin switches, revocation, corrupt data, and legacy preference refusal; the full Mac test suite still needs the fleet runner and CI, so owner review is required before merge.