cmux NEXT: a guest link an agent or script asks for waits for the owner's Allow (#30263)

Merged
TE
5d ago
+700-6

Fixes #30263.

Problem. agent.share (Share Chat with Guest, #27959) made a guest link for any caller. An agent (MCP), a script or the CLI could mint a link and get the URL back with its secret. Whoever opens that link reads the chat's whole transcript, tool output and reasoning included. The run also spent the owner's cmux install token with no one asking them. Approving guest prompts doesn't help: it guards what a guest sends, not what a guest reads.

Change. A run the owner didn't start in the app waits for the owner's answer. That covers the CLI, MCP, a script, a page, another client, an agent's run naming its own chat, and any control socket run, including one that claims origin: user (on the socket that's only a claim, viaControlSocket). The app shows a cmux dialog naming the chat, how long the link lasts and how many times it works. Only Allow goes on to the owner's token and the daemon. Declining, closing the dialog or not answering in time makes no link and returns share.declined. The owner's own palette or menu run is their consent, so nothing changes for it.

  • AgentShareHost.shareAskOwner(_:), a new requirement whose default answers no (fail closed). AppServices presents it with CmuxDialogCenter and dismisses it after personDeadline - 10s, so the waiting caller always gets an answer.
  • agent.share is marked waitsForPerson, so action.run with wait allows the person deadline (200 s) instead of 40 s.
  • The dialog strings and share.declined are added to AgentShare.xcstrings in all nine app locales.

Commits. The first commit adds only the failing test (AgentShareConsentTests): a CLI, MCP, script, page, remote or socket-user run spends the owner's token on today's base. The second commit adds the fix.

Kept. cmux share chat and the MCP tool keep working. I didn't use isPersonOnly, because it would have removed those surfaces, which the design asked for.

Risk. The diff is contained to agent.share's action, host, strings and catalog flag. It is a security fix with a behaviour change for scripted callers, so it takes a different-model review.

Follow-up. A composite action that nests agent.share inside a socket run claiming user would see origin user with viaControlSocket false, because ActionRunScope doesn't carry the socket bit. No composite does that today.

Agent session: agent session

šŸ¤– Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes #30263. agent.share now waits for the owner's Allow before creating a guest link. Previously any caller (MCP, script, CLI, page, or control socket run) could mint a link whose secret exposed the chat's entire transcript and spent the owner's install token. Runs the owner didn't start in the app now show a consent dialog; only Allow creates the link. Declining, closing, or timing out returns share.declined and makes no link. The owner's own run in the app is their consent and asks nothing.

Changes

  • Adds AgentShareHost.shareAskOwner(_:), which fails closed unless implemented. Consent is judged by ActionInvocation.isPersonsOwnRun, so runs that name a calling app, terminal, or agent session also ask.
  • Presents the dialog via CmuxDialogCenter, marked user-only so the debug socket can't answer it; Allow is a deliberate click, not the Return key. The sheet closes via DemandTimer before the person deadline so the caller always gets an answer.
  • Marks agent.share as waitsForPerson and adds the dialog strings plus share.declined to AgentShare.xcstrings in all nine app locales.
  • The CLI and cmux mcp now wait 210 s (the person deadline) for share chat and agent.share, so a late Allow reaches the caller instead of a link nobody receives.