Expand description
Agent-facing UI automation (UIScreenshot / UIClick / UIQuery, and
the browser-pane deep-control tools BrowserNavigate / BrowserBack /
BrowserForward / BrowserReload / BrowserEval / BrowserDispatchKey
/ BrowserFocusElement / BrowserFocusInfo) — proxies to the paired CEF
host’s /agentmux/browser/* CDP routes (crates/cef/src/browser_api/).
See docs/specs/SPEC_AGENT_BROWSER_PANE_DEEP_CONTROL_2026_09_20.md for the
browser-pane tools’ own design (they additionally require the caller’s
own pane to be a dedicated browser pane — enforced host-side, not here).
block_id is never a client-supplied field on any request here
(2026-08-19, reagent + Codex review, PR #2662 — a bare client-supplied
block_id was a real cross-agent content-disclosure vulnerability:
/api/v1/ui/* shares the same instance-wide X-AuthKey every App-API
route trusts, and any agent can read that key from its own environment,
so no amount of “the MCP schema never exposes it” convention actually
stopped a bypassing agent from supplying a different pane’s real
block_id). Every request instead carries UiAutomationAuth — an
HMAC-SHA256 signature over the caller’s own agent_id, using that
agent’s own AGENTMUX_JEKT_KEY (the same per-agent key already used
for jekt sender authentication, see agentmux_common::jekt_sign).
verified_block_id verifies that signature against the claimed
agent’s key on file, and — only once verification succeeds — looks up
that agent’s actual current block_id server-side via the global
ReactiveHandler registry. The block_id a call actually operates on is
never taken from the client at all, so there’s nothing left to spoof:
an agent without a valid signature for identity X cannot act as X, full
stop, regardless of what it claims in the request body.
See agentmux_common::api_types’s “UI automation” section and
docs/specs/SPEC_AGENT_UI_AUTOMATION_CLICK_SCREENSHOT_2026_08_18.md.
Constants§
- POPUP_
CHAIN_ 🔒LIMIT - How many openers up a popup’s chain the checks follow. A chain this deep is a page opening popups from popups on purpose; driving stops there.
- SCREENSHOT_
RETENTION 🔒 - Delete
*.pngfiles indirolder thanSCREENSHOT_RETENTION. Called on everyUIScreenshotwrite since this directory only ever grows otherwise (reagent P2, PR #2662) — no background scheduler needed for a directory nothing else writes to. Best-effort: any I/O error for an individual entry (permissions, a concurrent delete, a non-UTF8 name) is skipped rather than failing the screenshot request that triggered it. - UI_
AUTOMATION_ 🔒SIG_ MAX_ AGE_ SECS - Signatures older than this are rejected — bounds replay of a leaked/
logged signature to a short window. Mirrors
server/reactive.rs’sJEKT_SIG_MAX_AGE_SECS(same threat model: a host-tier HMAC signature verified locally, not a one-time nonce scheme).
Functions§
- banner_
for 🔒 - Where a request to the person about
targetshows: in its own banner, or, for a popup window (which has no AgentMux header), in its opener’s, with the window’s address so the banner can say which window it means. - chain_
root 🔒 - The pane a chain of popups started in:
paneitself unless it is a popup pane, else its opener’s root, as far up as the panes still exist. Popups are counted over its whole tree, so a popup opening popups shares its root’s cap rather than getting its own. - err_
response 🔒 - from_
host 🔒 - Is this request from the CEF host?
X-Host-Tokenmust be the IPC token the host registered with (host_ipc.Register), which agents never see; the instance auth key alone, which every agent has, isn’t enough. - get_
host_ 🔒ipc - handle_
host_ 🔒browser_ attention POST /api/v1/host/browser_attention— the user’s answer to a hand-off or approval banner, relayed by the CEF host. Only the host can call it:X-Host-Tokenmust be the IPC token the host registered with (host_ipc.Register), which agents never see; the instance auth key alone, which every agent has, isn’t enough.- handle_
host_ 🔒browser_ popup POST /api/v1/host/browser_popup— a browser pane’s page opened a popup (docs/specs/SPEC_BROWSER_PANE_POPUPS_ADOPTED_2026_10_08.md). Only the host calls it, fromon_before_popup. Answers{admitted: true, pane}when srv opened the popup as a pane beside its opener, or{admitted: false, reason}, and the host opens it in the system browser as it always did.- handle_
host_ 🔒browser_ popup_ window POST /api/v1/host/browser_popup_window— the host created, navigated or closed a popup window (a native window a browser pane’s page opened; SPEC_BROWSER_PANE_NATIVE_POPUPS_AGENT_DRIVEN_2026_10_08.md §4). Body{event: "opened"|"navigated"|"closed", popup, opener, url}. Only the host can call it. The window’s owner is the opener’s, from srv’s own record; the opener’s popup-windows strip is kept current.- handle_
ui_ 🔒browser_ act POST /api/v1/ui/browser/act— backsBrowserClick,BrowserFill,BrowserSelectandBrowserCheck(spec §4.2).- handle_
ui_ 🔒browser_ back POST /api/v1/ui/browser/back— backsBrowserBack.- handle_
ui_ 🔒browser_ dispatch_ key POST /api/v1/ui/browser/dispatch_key— backsBrowserDispatchKey.- handle_
ui_ 🔒browser_ eval POST /api/v1/ui/browser/eval— backsBrowserEval. Returns the host’s{result, type, exception}shape verbatim underdata, same passthrough patternhandle_ui_queryuses formatches.- handle_
ui_ 🔒browser_ focus_ element POST /api/v1/ui/browser/focus_element— backsBrowserFocusElement.- handle_
ui_ 🔒browser_ focus_ info POST /api/v1/ui/browser/focus_info— backsBrowserFocusInfo. Returns the host’s{focused}shape verbatim underdata.- handle_
ui_ 🔒browser_ forward POST /api/v1/ui/browser/forward— backsBrowserForward.- handle_
ui_ 🔒browser_ handoff POST /api/v1/ui/browser/handoff— backsBrowserHandoff(spec §5.2): a banner in the pane asks the user to do something the agent mustn’t (sign in, a CAPTCHA), and the call waits for their Done or Cancel. The agent’s tools on that pane are refused meanwhile (target_block_id).- handle_
ui_ 🔒browser_ navigate POST /api/v1/ui/browser/navigate— backsBrowserNavigate. Own pane only, and only when it’s a dedicated browser pane (checked host-side).- handle_
ui_ 🔒browser_ open POST /api/v1/ui/browser/open— backsOpenBrowser. Opens a browser pane next to the caller’s own pane and records the caller as its owner, so theBrowser*/UI*tools accept it aspane(docs/specs/SPEC_AGENT_DRIVEN_BROWSER_PANES_2026_10_07.md §3).- handle_
ui_ 🔒browser_ reload POST /api/v1/ui/browser/reload— backsBrowserReload.- handle_
ui_ 🔒browser_ set_ files POST /api/v1/ui/browser/set_files— backsBrowserSetFiles. Only files inside the calling agent’s own workspace (its block’s launch directory, host agents only) are allowed (spec §5.3).- handle_
ui_ 🔒browser_ snapshot POST /api/v1/ui/browser/snapshot— backsBrowserSnapshot(docs/specs/SPEC_AGENT_DRIVEN_BROWSER_PANES_2026_10_07.md §4.1).- handle_
ui_ 🔒browser_ wait_ for POST /api/v1/ui/browser/wait_for— backsBrowserWaitFor.- handle_
ui_ 🔒click POST /api/v1/ui/click— backs theUIClickMCP tool. Synthesizes a real mouse click at the firstselectormatch within the caller’s own pane subtree.- handle_
ui_ 🔒query POST /api/v1/ui/query— backs theUIQueryMCP tool. Returns matched elements (tag/text/attrs/rect/focused) within the caller’s own pane subtree — the same shapebrowser_api::types::QueryDatareturns.- handle_
ui_ 🔒screenshot POST /api/v1/ui/screenshot— backs theUIScreenshotMCP tool. Captures a PNG clipped to the caller’s own pane, writes it to<mux_data_dir>/tmp/ui-screenshots/<uuid>.png, and returns both the path (openable viaOpenMedia) and the base64 bytes inline.- not_
waiting_ 🔒on_ user - Refuse a tool call on a pane that is waiting for the user: a hand-off (they’re working in it) or an approval (they’re reading the form the approved click will send, which mustn’t change under them).
- opener_
allows 🔒 - A popup pane (
browser:popup_of) is driven as part of its opener: while the opener exists, the agent must still own it (the person’s Take over on the opener ends its hold on the popups it opened too), and the opener must not be waiting on the person (a hand-off there pauses its popups). A popup whose opener was closed stands on its own. - owning_
agent 🔒 - The agent that owns
block_id, if one does and the person hasn’t taken the pane over: the same test everypaneargument passes. - popup_
listing 🔒 opener’s open popups for its snapshot: each one’s pane id, address, and whetheragent_idmay drive it (not if the person took it over).- popup_
of 🔒 - The pane that opened
blockas a popup pane, if it is one. - popup_
window_ 🔒target - A popup window (a native window a pane’s page opened, id
popup-…) may be driven by the agent recorded when it opened, while that agent still owns the pane that opened it and that pane isn’t waiting on the person (SPEC_BROWSER_PANE_NATIVE_POPUPS_AGENT_DRIVEN_2026_10_08.md §5). - proxy_
ack 🔒 - Proxies
routewithbodyand checks the host’s{ok, ...}envelope. Shared by the write-only browser-pane tools (navigate/back/forward/ reload/focus_element/dispatch_key) — they differ only in which route/body they send, not in how they interpret an ack-only reply. - proxy_
data 🔒 - proxy_
to_ 🔒host - POST
bodyto/agentmux/browser/{route}on the paired host and return its parsed JSON response (the host’s own{ok, data}/{ok, error}envelope — seebrowser_api::types::ApiResponse). - proxy_
to_ 🔒host_ timeout proxy_to_hostwith a request timeout of its own, for host routes that legitimately take longer than the shared client’s default (a snapshot of a large page,wait_for).- proxy_
value 🔒 - Proxy a browser-pane request to the host and pass its
datathrough: the shared tail of the snapshot and act handlers. - prune_
old_ 🔒screenshots - refuse_
on_ 🔒driven_ pane - On a browser pane an agent drives (opened with
OpenBrowser), refuse the general-purpose routes that could submit, send or delete without the approval banner (spec §5.4): arbitrary script, clicks by CSS selector, and the keys that press buttons. The reference tools do the same work with the approval check in front of committing clicks. - reset_
popup_ 🔒windows - Forget every popup window and clear every opener’s strip. Called when the host registers: after either side restarts, the windows srv knew of are gone or no longer known, and the strip, which is saved with the pane, must not keep listing them.
- target_
block_ 🔒id - The block a UI-automation request acts on: the caller’s own pane, or,
when
paneis given, a browser pane the caller opened withOpenBrowser(docs/specs/SPEC_AGENT_DRIVEN_BROWSER_PANES_2026_10_07.md §3). The caller’s identity is verified first either way;paneis then checked againstbrowser_owner’s map, never trusted on its own. - update_
popup_ 🔒windows_ strip - Rewrite
opener’s popup-windows strip (browser:popup_windows). The opener may be closing; then there’s nothing to update. - verified_
block_ 🔒id - Verify
authproves the caller genuinely isauth.agent_id(via that agent’s own jekt key — an attacker without agent_id’s key cannot produce a valid signature no matter what it claims), then derive that agent’s ACTUAL current block_id server-side. Never trusts a block_id from the client — there isn’t one to trust. See this module’s own doc comment for the full rationale.pub(crate): also reused byapp_api::pane::handle_close_pane(and any future own-pane-resolving handler) — the identity-verification mechanism isn’t UI-automation-specific, just first built for it. See docs/specs/SPEC_AGENT_PANE_LIFECYCLE_CONTROL_2026_09_10.md §5.0.