Repository navigation
Chief: import browser cookies from Chrome on request - #32983
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Chief turn denied every app action of the cmux MCP server, so the app's Import Cookies from Browser (`browser.importCookies`) never reached it, and only the subagent prompt said how to import the user's sign-ins. - Chief turns keep `app_browser_import_cookies` (optchat-chief CHIEF_CMUX_TOOLS and acpmux CHIEF_TURN_TOOLS). The app still shows the user its consent panel first; a turn another person started still refuses it (not in approval::turn_allowed_tools). - The Chief's own prompt (MCP and CLI launcher forms) says to import only when the user asks, with the fewest sites and a reason. - `caller.home_chat` now reaches the action invocation, so the consent panel names "Chief" and Show Agent Chat reveals Home's chat instead of showing the turn's temporary session id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
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)
Default changesThis pull request changes 1 default at
Signed off on this commit by Merge receipt: merged as Regenerate the snapshot with Shipped in:
|
…ions Review of the first push: 53f549c / 1298092 keep every `app_*` tool out of Chief turns (agent_tools_tests chief_turn_tools_contain_no_mac_app_actions), and allowing `app_browser_import_cookies` would reverse that security decision without its owner's sign-off. The Chief's owner turns already have a shell, so both prompt forms now give `cmux browser import-cookies` from the shell with a 4-minute timeout (the CLI waits 210 s for the person, the panel 180 s), and the Chief tool allowlists stay as they were. A turn another person started still refuses the shell, so it cannot import. Also from the review: a Chief's Home tab is the store's home workspace (`kind == "home"`, as ControlCallerLocation.homeChat finds it), and a CLI run that names only `caller.home_chat` must give a reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (correctness subagent)Round 1, head 5acc9ad: FIX
Round 2, head 8af92c6: SHIP
Runs
|
|
Found 6 test failures on Blacksmith runners: Failures
|
|
Review (independent-subagent): SHIP at 8af92c6 Correctness and security review of the full diff at this head (16 files, +93 -13), done independently of the earlier "## Review" comment. Tier: risk:normal is correct. No file matches a high path in docs/ci/risk-tiers.md. The change also adds no high kind. The consent gate ( Consent: checked every entry point.
Scope and secrecy. One source profile, filtered with Keychain. Undo / data. Import has no undo, and the skill does not say it has: the undo it mentions is for Requester labeling (the app change). The order is: a tab whose session matches, then Tests. The new Non-green checks on this head (none caused by this PR).
Follow-ups (not blocking, and not caused by this diff).
|
… panel The Allow/Deny panel before `cmux browser import-cookies` reads another browser was a custom panel that screen capture showed and that any automation could answer: an Accessibility press, a programmatic click or a click another process posts (cmux's own Computer Use, any computer-use tool, a script) allowed the import. A prompt-injected agent could ask and then click Allow itself. Now, as macOS's own privacy prompts and Chrome's permission prompts do: - The panel is out of screen capture and screen sharing (sharingType = .none): an agent that reads the screen never sees Allow. - Allow answers only the person's own click, read from the click's events in the panel's dispatch with the app's strict HID-system check (PersonInputCheck, as the join consent sheet): no Accessibility press, programmatic click, key, posted click or DEBUG automation allows. - A click that begins less than a double-click interval after the panel shows or moves, or after another click, is not taken (Chrome's InputEventActivationProtector). - Its buttons are the standard CmuxDialogButtonView: Deny with Escape, Show Agent Chat, and Allow with the default look and no key. - cmux's Computer Use helper refuses any target window that keeps itself out of screen capture (kCGWindowSharingState none), whatever app shows it. Deny, Escape, the window's close and the time limit still deny, by anyone. The join consent sheet's person-click logic moves into CmuxNextDesign as CmuxDialogPersonClick, so both prompts share one checked implementation (the join sheet keeps its behavior: no activation delay). Review of #32983 (issuecomment-6595849975).
|
Follow-up 1 of the independent review (the consent panel) is in #33066. It does not block this PR. #33066 makes Allow on the cookie import consent panel a person-only answer:
Follow-up 2 ( |
…nt (#32983) (#33111) * test(defaults guard): a call owner's SHIP counts when the REST quota is spent (failing, #32983) A private org member reads as CONTRIBUTOR to the workflow's token. The guard then asks the REST API for their write access. On #32983 that call got "API rate limit exceeded for installation", so the guard trusted nobody and stayed red on a valid SHIP from lawrencecchen, a call owner. Red on the base: python3 tests/test_defaults_guard.py fails test_a_call_owners_receipt_ships_with_the_rest_quota_spent and test_a_receipt_whose_lookup_failed_is_named_in_the_error. * fix(defaults guard): trust a call owner's receipt without the REST lookup (#32983) A private org member reads as CONTRIBUTOR to the workflow's token, so the guard asked the REST API for each receipt author's write access. That call spends the installation's REST quota, which other jobs share. On #32983 the quota was spent ("API rate limit exceeded for installation"), the lookup trusted nobody, and the guard stayed red on lawrencecchen's valid SHIP; the merge service left the PR out as "defaults guard red". The call owners come from the trusted base's .github/defaults-call-owners.json, the same file that lets them approve a default outright, so their receipts now need no lookup. Any other member is still looked up; when that lookup fails for a reason other than 404, the error now names the receipt that was not counted and says to re-run when the quota resets. python3 tests/test_defaults_guard.py: red on the base (2 failures: test_a_call_owners_receipt_ships_with_the_rest_quota_spent, test_a_receipt_whose_lookup_failed_is_named_in_the_error), green on this commit (67 tests OK).
…alls reach a Tunable helper The review of #33161 found Tunable forms the exact helper match missed. Self.points(...) was skipped because Self is no declared type, and the guard before #33161 caught it only by the shared-name match. Now: - Self.helper resolves like a bare helper: the caller's own types. - A caller's types also include the superclasses and protocols its types name, so a subclass reaches its base's helper and a conforming type a protocol extension's helper. Only the caller side takes inheritance, so a helper file that conforms to Sendable does not reach every Sendable type. - A typealias of a Tunable, or an alias of such an alias, counts as a Tunable token (TD.number, `: TD = .number`). The guard before #33161 missed this too. - `class func` / `class var` no longer read as types named func / var. feat-cmux-next tree: still 449 fingerprints, the same set, all of them in the old 6182. #32983 and #32979 still report no tunable-source change. python3 tests/test_defaults_guard.py: 6 subtests red on the test commit, 73 tests OK here. Not resolved: a helper reached through a generic parameter (T.points).
…9); an unchecked BLOCK still counts (#33161) * test(defaults guard): a call owner's SHIP counts when the REST quota is spent (failing, #32983) A private org member reads as CONTRIBUTOR to the workflow's token. The guard then asks the REST API for their write access. On #32983 that call got "API rate limit exceeded for installation", so the guard trusted nobody and stayed red on a valid SHIP from lawrencecchen, a call owner. Red on the base: python3 tests/test_defaults_guard.py fails test_a_call_owners_receipt_ships_with_the_rest_quota_spent and test_a_receipt_whose_lookup_failed_is_named_in_the_error. * fix(defaults guard): trust a call owner's receipt without the REST lookup (#32983) A private org member reads as CONTRIBUTOR to the workflow's token, so the guard asked the REST API for each receipt author's write access. That call spends the installation's REST quota, which other jobs share. On #32983 the quota was spent ("API rate limit exceeded for installation"), the lookup trusted nobody, and the guard stayed red on lawrencecchen's valid SHIP; the merge service left the PR out as "defaults guard red". The call owners come from the trusted base's .github/defaults-call-owners.json, the same file that lets them approve a default outright, so their receipts now need no lookup. Any other member is still looked up; when that lookup fails for a reason other than 404, the error now names the receipt that was not counted and says to re-run when the quota resets. python3 tests/test_defaults_guard.py: red on the base (2 failures: test_a_call_owners_receipt_ships_with_the_rest_quota_spent, test_a_receipt_whose_lookup_failed_is_named_in_the_error), green on this commit (67 tests OK). * test(defaults guard): a shared helper name is no tunable source; an unchecked BLOCK still counts (failing) #32983 and #32979 were flagged only because `toggle` and `make` are Tunable helpers: any token with that name selected its declaration, and through AppActions.bindApp every handler's `bind` (6182 tunable-source entries). Two findings from the review of #33111: a BLOCK whose author's lookup failed was dropped, so an owner's earlier SHIP passed; and a failed lookup at the earlier head of a carried SHIP gave only a warning. python3 tests/test_defaults_guard.py on this commit: 4 failures (test_a_member_that_only_shares_a_helpers_name_is_not_a_tunable_source, test_an_unchecked_block_after_an_owners_ship_keeps_the_guard_red, test_an_unchecked_receipt_one_base_merge_back_is_named_in_the_error, and test_a_receipt_whose_lookup_failed_is_named_in_the_error for its new wording). * fix(defaults guard): match a Tunable helper by the call that reaches it; an unchecked BLOCK still counts A Tunable helper (points, make, toggle) selected every declaration that had a token of that name, so `registry.bind { model.toggle() }` in AppActions.bindApp, and through it every handler's `bind`, was a tunable-source default. That is 6182 fingerprints on feat-cmux-next. Now a helper counts only as `Type.helper` where the helper's file declares Type, or as a bare `helper` in a file that declares one of the helper file's types (the same type or an extension of it). `x.helper` and `.helper` are other members. The dependency walk does not change. On feat-cmux-next (38369c1) the count goes from 6182 to 449, and no fingerprint is added. On the diffs of #32983 and #32979 the old guard reports 1 and 2 tunable-source changes, and this one reports none. Two findings from the review of #33111: a user whose write-access lookup failed now counts for a BLOCK (never for a SHIP), so an unchecked receipt can only keep the guard red. A failed lookup at the earlier head of a carried SHIP is now named in the error too. python3 tests/test_defaults_guard.py: 4 failures on the test commit, 72 tests OK on this one. The real-default fixtures (helper, helper body, cross-file Factory.number, symbol, external symbol, dynamic) still fail without a review. * test(defaults guard): Self., typealias, subclass and protocol helper calls stay tunable sources (failing) The review of #33161 found forms that reach a Tunable or its helper with no Tunable token and no Type.helper of a type in the helper's file: Self.points(...), a typealias of Tunable (TD.number, `: TD = .number`), a subclass calling an inherited helper (bare or Sub.helper), and a type calling a helper of a protocol extension it conforms to. On this commit all 6 subtests fail. On the guard before #33161, the 4 Self, subclass and protocol subtests pass and the 2 typealias subtests fail: that guard missed a typealias too. * fix(defaults guard): Self., typealias, subclass and protocol helper calls reach a Tunable helper The review of #33161 found Tunable forms the exact helper match missed. Self.points(...) was skipped because Self is no declared type, and the guard before #33161 caught it only by the shared-name match. Now: - Self.helper resolves like a bare helper: the caller's own types. - A caller's types also include the superclasses and protocols its types name, so a subclass reaches its base's helper and a conforming type a protocol extension's helper. Only the caller side takes inheritance, so a helper file that conforms to Sendable does not reach every Sendable type. - A typealias of a Tunable, or an alias of such an alias, counts as a Tunable token (TD.number, `: TD = .number`). The guard before #33161 missed this too. - `class func` / `class var` no longer read as types named func / var. feat-cmux-next tree: still 449 fingerprints, the same set, all of them in the old 6182. #32983 and #32979 still report no tunable-source change. python3 tests/test_defaults_guard.py: 6 subtests red on the test commit, 73 tests OK here. Not resolved: a helper reached through a generic parameter (T.points). --------- Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>
…een capture; Computer Use refuses it (#33066) * test(cookie-import): only the person may answer the cookie import consent panel Red on base: the panel shows in screen capture (sharingType is readOnly), an Accessibility press or programmatic click on Allow allows the import, and cmux's Computer Use acts on a window that keeps itself out of screen capture when its app is one the policy allows. Review of #32983 (issuecomment-6595849975). * fix(cookie-import): only the person answers the cookie import consent panel The Allow/Deny panel before `cmux browser import-cookies` reads another browser was a custom panel that screen capture showed and that any automation could answer: an Accessibility press, a programmatic click or a click another process posts (cmux's own Computer Use, any computer-use tool, a script) allowed the import. A prompt-injected agent could ask and then click Allow itself. Now, as macOS's own privacy prompts and Chrome's permission prompts do: - The panel is out of screen capture and screen sharing (sharingType = .none): an agent that reads the screen never sees Allow. - Allow answers only the person's own click, read from the click's events in the panel's dispatch with the app's strict HID-system check (PersonInputCheck, as the join consent sheet): no Accessibility press, programmatic click, key, posted click or DEBUG automation allows. - A click that begins less than a double-click interval after the panel shows or moves, or after another click, is not taken (Chrome's InputEventActivationProtector). - Its buttons are the standard CmuxDialogButtonView: Deny with Escape, Show Agent Chat, and Allow with the default look and no key. - cmux's Computer Use helper refuses any target window that keeps itself out of screen capture (kCGWindowSharingState none), whatever app shows it. Deny, Escape, the window's close and the time limit still deny, by anyone. The join consent sheet's person-click logic moves into CmuxNextDesign as CmuxDialogPersonClick, so both prompts share one checked implementation (the join sheet keeps its behavior: no activation delay). Review of #32983 (issuecomment-6595849975). * fix(cookie-import): review fixes for the person-only consent panel - The activation guard arms again on every move of the parent window (AppKit moves the child panel with it, so the frame check missed it) and on every frame change of the panel itself (an Accessibility client can set its position). - Only the person's own mouse-downs count for the fast-click guard, so another process's posted clicks cannot hold the person's Allow off. - A run with the user origin (the palette, an Import card) skips the panel only when the input that started it is the person's own HID input; a click or key that a computer-use tool posts there now gets the panel. - Comments: the Computer Use guard checks only calls that name the window; the join consent sheet is not capture-excluded. * fix(cookie-import): arm the click guard by the panel frame; a person's run needs recent person input Second review of #33066: - No setFrame/setFrameOrigin overrides (they need the ObjC selector probe). Instead a mouse-down on a panel whose frame changed since it was armed arms it again at that click, so that click is not taken; show and every parent move arm it too. - A user-origin run skips the panel only when the event in dispatch is the person's own key or mouse-button event from the last second. An Accessibility press makes no event, so the event then is an older one (a mouse move, an earlier click), which does not count. - needsConsent has no default for byPerson (a new caller fails closed). - Tests: a click right after the panel moves is not taken; the recent person input rule. * fix(cookie-import): the person-click dispatch body is main-actor isolated The window's own dispatch runs on the main actor; saying so lets a caller's body closure touch main-actor state (Swift 6 refused the move test's capture: sending 'current' risks causing data races). * test(cookie-import): the move test follows the person-click test's shape (Swift 6 isolation) Reverts the main-actor body of the person-click dispatch (it broke the join consent tests' compile) and builds the move test's click the way the person-click test does, which compiles. * fix(cookie-import): a person's run skips the panel only inside the person's own input dispatch Third review of #33066: - The person's-run check reads the input event being dispatched now (InputDispatchContext), not NSApp.currentEvent, which keeps the last event after its dispatch: an Accessibility press within a second of the person's click no longer finds that click. - needsConsent uses ActionInvocation.isPersonsOwnRun: a cmux app's run during the person's gesture in it, or a socket run that claims the user origin, now asks first. - An Import card's cookie run keeps the caller's app and socket marks. - Tests: an app caller and a socket caller ask first.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Fixes #32960
What
A Chief can now import the user's sign-ins from another browser (Chrome, or any other browser cmux reads) when the user asks. The app's existing consent panel asks the user first.
The app action already existed (
browser.importCookies, CLIcmux browser import-cookies, MCPapp_browser_import_cookies). A Chief could not use it in practice:caller.home_chat. The consent panel would have named the Chief's temporary turn session id ("Agent chat “optchat-turn-…”") with no chat to reveal.Changes
chief-kernelprompt (MCP and CLI-launcher forms): only when the user asks, runcmux browser import-cookies --from chrome[:PROFILE] --domain SITE --reason "why"from the shell with a 4-minute timeout, using the fewest sites. The CLI waits up to 210 s and the panel up to 180 s, so a shorter shell timeout would report a failure while the panel is still open. A denial imports nothing and the Chief says so. Goldens updated.ActionInvocation.callerHomeChatis filled from the control request.CookieImportRequesternames a Chief's run "Chief", and Show Agent Chat reveals the home workspace's docked chat (found the same way asControlCallerLocation.homeChat). A CLI run that names onlycaller.home_chatmust give a reason.TabModel.isHomeDockPageis new. CmuxNextApp lines: -1.Why the CLI rather than the MCP tool
The first push added
app_browser_import_cookiesto the Chief turn's cmux MCP allowlist. The review flagged that this reverses 1298092 / 53f549c ("Chief presets expose no Mac actions", pinned byagent_tools_tests::chief_turn_tools_contain_no_mac_app_actions). That push was reverted in 8af92c6.An owner's Chief turn already has a shell, so the CLI gives it the same capability without widening its tool grant. If the MCP form is wanted later, it needs that policy owner's sign-off through #13742.
Trust
The consent gate does not change. Every run that is not the user's own in-app run must give a reason, and nothing is read from the other browser until the user clicks Allow on the panel. macOS's Keychain prompt follows. In a turn started by another person in a shared chat, the host refuses the shell (and every tool outside
approval::turn_allowed_tools), so that turn cannot import.Tests
CmuxNextAppTests.CookieImportCommandTests:requesterOfAChiefTurnIsTheChiefInHome(new), andagentsMustGiveAReasonnow covers ahome_chat-only CLI run. On the base this suite does not compile (callerHomeChatdoes not exist).chief-kernelrender_systemgoldens (all six local prompt forms carry the new line;cloud-plainis unchanged) andtools.scripts/ci/chief-first-turn-cost.sh) at the head.Observed / Not run
Observed at head 8af92c6:
CmuxNextAppTests.CookieImportCommandTests: 11/11 passed (cmux-ci step 5d0e9e47361b194d1c687fd4, aws-m4pro-7). This also compiles the CmuxNext package.chief-kernelrender_system: 14/14;tools: 6/6 (rbx, lane nx-32960-kernel).count_tokensreturned 502, so these are the script's local estimates.32960-chief-cookie-import: cmux-ci job 19fcbbacfa2ca937388b5a7a, published.ActionInvocation.callerHomeChatdoes not exist there).optchat-chiefcloud_app_tools11/11,harness28/28 andapproval_gates21/21. Those files are unchanged at the final head.Not run:
Changelog
🤖 Generated with Claude Code