Repository navigation
Audit 2026-10-04: password-field refusal, multi-monitor cursor, extension policy/injection fixes - #15
Merged
Merged
Conversation
The type_text tool description and the README promise that typing into password fields is refused by default (COMPUTER_USE_ALLOW_SECURE_FIELD_INPUT=1 opts in), and the macOS server does refuse. The Rust server never checked, so on Windows and Linux an agent could type or set_value a credential into a password field, and Windows set_value echoed the value back into the transcript. Mirror the Swift behaviour: when type_text is given an element_id, or set_value targets an element, that is a password field (UIA IsPassword on Windows, AT-SPI role PasswordText on Linux), focus it for the user and hand control back instead of writing into it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a click, right-click, hover, scroll or drag had to fall back to the real pointer (Chromium, WPF, UWP and anything else that ignores posted messages), the agent path used uiautomation's Mouse::move_to / click / right_click / drag_to. Those send absolute SendInput moves normalised against SM_CXSCREEN/SM_CYSCREEN without MOUSEEVENTF_VIRTUALDESK, which Windows maps onto the primary display only, so any coordinate on a second monitor (or left of / above the primary) landed clamped at the primary display's edge. The remote-control path had already been moved to SetCursorPos for this reason, but its drag still finished with drag_to and had the same problem. Use SetCursorPos (jump_cursor) for every real-pointer move, and drag with an explicit button down, stepped SetCursorPos moves and a button up that is sent even if a move fails. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… reads Two ways an agent could get past the guarantees the extension makes: - browser_click's index was spliced verbatim into the script CLICK_JS evaluates in the page. The macOS server only forwards an integer, but the Windows/Linux server forwards whatever JSON the tool call carried, so a string index ran as arbitrary page script through the debugger (for example reading a password field the snapshot deliberately hides). clickElement now accepts only a non-negative integer. - return_state snapshots were taken after the action without applying the site rules, so a click on a link, a redirecting navigate or a submitted form that landed on a blocked site returned that site's page to the model. The README promises following a link into a blocked site does not get around the rule. withState now runs the same checkTab browser_snapshot runs before reading. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ot hosts - The snapshot script tags elements with data-cu-idx but never cleared the tags an earlier snapshot left. An element hidden since then (an earlier step of a multi-step form) kept its old number, and querySelector returns the first match, so a click or a credential fill could go to that hidden element instead of the one the agent was shown. Clear old tags first. - There was no chrome.debugger.onDetach listener. When Chrome ended a session itself (the user pressing Cancel on the "is debugging this browser" bar, DevTools taking the tab), the tab stayed in `attached`, attach() skipped re-attaching and every later command on that tab failed until it was closed or the worker restarted. - Site rules compared hostnames verbatim, so https://bank.example./ (same site, trailing dot) matched neither "bank.example" nor its subdomains and slipped past a block rule. Trailing dots are now dropped from both hosts and patterns. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes from the 2026-10-04 audit. Each one is small and has its own commit. Nothing is released or published.
Rust server (windows-linux)
e61762d). Thetype_texttool text and the README both say typing into password fields is refused by default (COMPUTER_USE_ALLOW_SECURE_FIELD_INPUT=1turns it on), and the Swift server does refuse. The Rust server never checked. On Windows,set_valuealso echoed the value back into the transcript. It now matches Swift: whentype_textgets anelement_id, orset_valuetargets a password field (UIAIsPasswordon Windows, AT-SPIPasswordTexton Linux), it focuses the field and hands control back to the user.cargo teston Windows, 93/93, including a new test for the flag and the message. The Linux build is left to this PR's ubuntu CI job, because no Linux toolchain was available.03d7b1c). The fallback path for clicks, right-clicks, hovers, scrolls and drags used uiautomation'sMouse::move_to/click/right_click/drag_to. Those sendMOUSEEVENTF_ABSOLUTEscaled bySM_CXSCREEN/SM_CYSCREENwithoutMOUSEEVENTF_VIRTUALDESK, which Windows maps onto the primary display only. Coordinates on other monitors were clamped to the primary display's edge. This is the problem already noted and fixed for remote control, except that remote drags still useddrag_to. Every real-pointer move now usesSetCursorPos. Drag sends button down, stepped moves, then button up, and the button up is sent even if a move fails.cargo test -j 2 --locked), checked against the uiautomation 0.25.1 source and the SendInput docs. Not run against a real multi-monitor desktop.Chrome extension
browser_clickindex (df79624).CLICK_JSputindexverbatim into the script it evaluates in the page. The Rust bridge passes any JSON value through, so a string index ran as arbitrary page script through the debugger. That script could, for example, read a password field the snapshot deliberately hides. Only non-negative integers are accepted now.return_stateread blocked sites (df79624). The snapshot taken after an action skipped the site rules. A link click, a redirect or a form submit that landed on a blocked site returned that page's content, even though the README says following a link into a blocked site does not get around the rule. The samecheckTabthatbrowser_snapshotuses now runs before the read.83ebab7). Olddata-cu-idxtags were never cleared, so an element hidden since the previous snapshot kept its number.querySelectorcould then send a click or a credential fill to that hidden element.83ebab7). There was nochrome.debugger.onDetachlistener. After the user cancelled Chrome's debugging bar, every later command on that tab failed until the tab was closed.83ebab7).https://bank.example./slipped past abank.exampleblock rule. Trailing dots are now dropped from both hosts and patterns.Verified:
node chrome-extension/background.test.mjspasses 41/41. Each of the 5 new tests fails without its fix.scripts/build-extension.test.mjsandcheck-tool-parity.mjspass, andtool-defs.mjs --checkpasses on an LF checkout.Found but not fixed (for the owner)
.rpc) use the default DACL, which gives Everyone read access. The pipe namemunim-computer-use-bridge-<USERNAME>is predictable. On a multi-user Windows machine, another local user could create that pipe first. The victim's server would then join as a peer, sending its browser commands to that user. The victim's native host would connect to it too, letting that user drive the victim's signed-in Chrome. Fixing this needs an owner-only security descriptor plus a check of the pipe server's identity (GetNamedPipeServerProcessIdand its token SID). I could not test this across users here. Unix sockets are fine (0600 inside a 0700 directory).check_app_policy(main.rs). Iflist_apps()errors, the rule is matched against the raw query string. A numeric pid then only hits the*rule, so a pid can get around a name-specific block. Failing closed would block every app on sessions where xcap cannot list windows (WSLg), so this is left as a decision for you.browser_request_credentialscan come back to the agent. A page's "show password" toggle turns the field intotype=text, and username and code values are always shown as snapshot labels.kindof each field.request_credentialsis allowed ondata:andabout:pages, where the origin isnull.approvedOriginsis never cleared per client, so "ask" approvals outlive the task. The per-tab badgeonUpdatedlistener leaks one listener per closed tab.🤖 Generated with Claude Code