Repository navigation
acpmux: a peer counts an enrolled Mac's signed one-time Allow (peer-route approvals 1/4) - #26131
Conversation
…oute approvals 1/3)
A chat the Mac's app reaches through its own daemon (--host) lives on a
peer, which refused every Allow from the card: the app holds no key of that
daemon's. The app now signs the Allow itself and only the signature crosses
the peer link; the peer checks it against the Mac keys enrolled in its
ACPMUX_HOME/mac-approvers.json (hub/mac_approvers.rs).
- The signature binds the daemon run, the Mac, the session, the permission,
the tool call id, the option, the prompt's nonce, the expiry (at most 300 s)
and the answers' digest; it counts once (the spent-nonce store).
- Only a one-time allow of a tool ask or a question: never a lasting allow,
a plan, a mode switch or a folder trust. Unsigned allows stay refused;
denies count from anyone.
- No RPC enrolls a key: only the file, which host approvals writes. A reload
(_acpmux/mac_approvers {reload: true}) takes the set's lock, so removing a
Mac waits for an answer being committed and then stops its signatures.
- _acpmux/mac_approver_set (the person only, memory only) gives this
machine's own app key, for host enroll to enroll on a peer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
|
Owned by an agent session · session |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Unclear: CI failed on
Failures:
Matched log linesNot re-run automatically: Written by red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 97e2f1b to compare; do not merge until green; see lane-rules Gallery before/after for
States
Pick other states with a |
|
Found 188 test failures on Blacksmith runners: Failures
...and 178 more test failures. View all on Blacksmith
|
|
Bead |
|
Shipped in:
|
|
Shipped in:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Peer-route approvals, part 1 of 3: the daemon side.
The problem. A chat opened through a peer (
acpmux new --host,cmux agent open-tab --host,--groupwith--host) shows its tool prompts on the Mac, but the Mac can never approve one from the card. The Allow reaches the peer through the Mac's own daemon, the peer's person gate wants the person's proof, and the app has no key bound for that machine. The card says "Remote approvals are off" and offers no way to turn them on.The fix. An enrolled Mac's own P-256 key signs a one-time Allow, and the peer checks the signature against the key it has on file. The private key never leaves the Mac. Only the signed answer crosses the peer link.
This PR adds the peer's half:
hub/mac_approvers.rsreads the Macs that may allow fromACPMUX_HOME/mac-approvers.json, in the form{"macs":[{id, publicKey, name}]}.acpmux host approvals addwrites it (part 2), so an agent that can only reach the daemon's socket cannot enroll a key.permission_respondcarries_meta.acpmux = {decisionNonce, approverDecision: {approver, expiresAt, signature}}. This is the phone approvers' shape, under a domain of its own:acpmux-mac-approver-decision-v1._acpmux/mac_approver_set {id, publicKey, name}: the app gives the daemon this Mac's own public key. Only the person may call it, and the daemon keeps the key in memory only.host enroll(part 2) reads it._acpmux/mac_approvers {reload?}: lists the enrolled Macs and this Mac's own key. It is refused for Web and peer origins.spec/acpmux-protocol.jsonand the generatedmethods.ts.Part 2 adds
acpmux host approvalsand the step inhost enrollthat enrolls the Mac's key. Part 3 makes the app sign and adds the card hint.The five conditions from the brief
personInstance), the Mac id, the session, the permission id,toolCall.toolCallId, the option, the prompt'sdecisionNonce,expiresAt, and the digest of any answers.expiresAtmust fall between now and now + 300 s.commit.a_macs_signature_counts_for_its_one_time_allow_and_every_change_failschanges each bound part in turn. In the integration test, a replay of the same signature on the next prompt is refused.may_allowaccepts only an option offered exactly once whose kind isallow_once, on a plain tool ask or an agent question.switch_mode, a folder-trust card, orExitPlanMode.a_mac_never_allows_a_plan_a_mode_switch_or_a_folder_trust, plus the signedalwayscase refused in the test above. The integration test checks that the peer's policy and modes are unchanged afterwards.host approvals rmrewrites the file, then calls_acpmux/mac_approvers {reload:true}.removing_a_mac_from_the_file_stops_its_signatures,a_reload_during_a_commit_waits_for_it_and_then_the_signature_counts_nothing, and the last leg of the integration test.Tests (they fail before the fix and pass after)
They ran remotely through
cmux-ci test(32 vCPU Linux).Before: the Mac check is taken out (
verifies_thenreturns false), which is what base does, since base has no Mac check.After:
No capture: the only webview path is the generated protocol method list (
methods.ts, two new method names); nothing on screen changes. Part 3 (the app) carries the before/after card captures.Risk. This is a second way to prove the person on a peer. It is limited to one-time allows of plain tool asks and questions. It is off until a Mac is enrolled in a file that only this user can write.
Agent session: agent session
🤖 Generated with Claude Code