You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add outbound ACP vision delegation: a workflow harness performs the vision work over ACP and bobby stores no provider credentials. Bounded packet and result validation, isolated child-session lifecycle, image capability negotiation, and harness-advertised authentication. bobby vision connect --backend acp writes the config and bobby doctor covers it; direct providers are unchanged.
An isolated ACP vision harness that requests interactive permission fails closed β the request is cancelled and the child session closed, so it cannot produce an accepted result after asking for authority the parent session did not grant.
The optional session visionNode selector is exposed through the MCP session_create input/output schemas and the TypeScript SDK contract validator. MCP agents could configure ACP vision but session_create rejected the selector, leaving the configured route unreachable through that interface. The versioned Rust session contract is unchanged.
A rejected advertised ACP authentication is classified as AcpClientError::Authentication and fails before the isolated child session is created, instead of surfacing as transport loss.
Firefox executes vision-assisted intents: bounded accessibility snapshots become semantic intent candidates, and vision-selected coordinates execute through native BiDi pointer actions. Unsupported candidate scopes are rejected.
The Firefox companion popup pairs and re-pairs the profile through the native host's enrollProfile control path, so the credential never leaves the host. First-time enroll bootstraps the companion; a day-2 enroll reuses the live bobby serve descriptor. The install persists browser-selection.json and the enroll defaults, sharing one selection builder with the CLI. Adds the toolbar icons. The guide prefers popup Pair; bobby enroll-firefox-profile remains for CI.
bobby install --companion and make firefox upgrade a bobby-managed native host instead of failing with native-host installation destination already exists whenever the installed wrapper or manifest differed by bytes β JSON key order, or a different bobby path. Operator-owned files still refuse. Manifest keys serialize alphabetically so a repeat install is idempotent.
scripts/check-version-agreement.py covers packages/firefox-companion/manifest.json. It had drifted to 0.3.1, which is the version about:addons showed.
The Chromium worker never holds the pages mutex across browser I/O. The guard was held across every CDP round trip β navigate (up to 300s), inspect, click, humanized typing, screenshot, HAR dump β so one hung page call serialized every other page on the session, and close/terminate blocked on the same mutex, making a single stuck call unrecoverable. All 30 lookup sites clone the Arc-backed page handle under the lock and drop the guard before I/O; the remaining guard uses are map mutations.
The envelope deadline is enforced mid-flight, not only at admission. inspect, click, screenshot, and the cookie operations carry no timeout of their own, so a hung call stalled its command forever. Execution now races the deadline at the single dispatch site and fails retryable DeadlineExceeded; callers declare the budget via timeoutMs, clamped to the 300s ceiling. An aborted call may still execute browser-side, as with any timeout.
cookie_set and cookie_delete held the pages mutex across a recursive get_cookies acquisition of the same non-reentrant lock. Every cookie command hung its session permanently.
download_url is advertised, required, parsed, and threaded through the MCP gateway. The gateway always sent pageId: None while the executor requires one, so every call failed invalidRequest.
Firefox page_close returns Evidence::Page captured before teardown. Without it the executor recorded every successful close as a retryable failure, so agents retried a destructive operation.
Extraction and the Firefox JavaScript-result path truncate on a character boundary. Byte-index truncation panicked inside a multi-byte codepoint, so any non-ASCII page could kill the command.
Two check-then-act races are single-flighted: concurrent leases on one session launched two browsers against one profile directory (SingletonLock), and concurrent network_log calls spawned duplicate HAR collectors that split entries into a flaky verificationFailed.
Security:get_job and cancel_job checked the capability but never ownership, so any principal holding job:read could read another principal's job payload and result, and job:cancel could cancel their work. Jobs record their submitting owner (serde-compatible with pre-ownership journals; a None owner stays readable) and cross-principal access answers as absence.
Security: the cookie_get URL filter matched by substring, so example.com matched notexample.com and path=/ matched every URL β agents received other origins' cookies as correct results and re-injected them with cookie_set. Matching is now dot-boundary host suffix plus real path prefix.
Security: an idempotency permit dropped between reserve and finish β a cancel or panic mid-request β wedged its key until deadline for every retry, and enough wedges exhausted the principal's capacity. Permits abandon on Drop, disarmed by finish or explicit abandon.
Security: companion reconnect credentials are stored as a SHA-256 digest and compared in constant time. They were stored in plaintext and compared with an early-exit !=.
Security: a live MCP SSE stream re-evaluates its SubscribeEvents guard on every poll, so a token rotation pauses event delivery instead of the stream running on the capability set it opened with. Revoked or expired principals still close the channel.
Security: the SSRF deny path covers IPv4-compatible IPv6 (::127.0.0.1), the 6to4 and Teredo prefixes, IPv4 broadcast, and CGNAT 100.64.0.0/10.
Security: vision endpoint responses check Content-Length up front and enforce the size bound while reading chunked, instead of buffering the whole body before the check.
vision-proxy's validate_extract enforces a 64 KiB serialized bound. It was a no-op stub, so unbounded upstream extraction JSON reached the runtime unchecked.
Checkpoint files are written 0600 on Unix; they previously took the process umask.
authority.json syncs the file and its directory before the rename β flush() is userspace-only, so a crash could leave a torn file β and an unparsable file is quarantined to <path>.corrupt instead of failing boot with every token stranded.
A corrupt artifact-ownership record is quarantined instead of permanently rejecting all future registrations.
ArtifactStore construction sweeps crash-orphaned staging directories, which each leaked up to max_bytes for the life of the installation.
The session manager releases the worker before unregistering the session. The old order dropped the API handle first, so a failed release leaked the browser with nothing left to retry through; on failure the session now stays registered.
events_read no longer journals a receipt per poll into the 64-event ring, where its own traffic evicted real events and poisoned resume cursors.
Page.getFrameTree awaits the page lock instead of fabricating an about:blank frame tree from a try_lock fallback under contention.
The task scheduler re-checks terminal state when inserting an abort handle; a job that finished faster than the insert left the handle in place forever.
bobby doctor's 15s MCP handshake deadline applies to the read. The blocking read_line ignored it, so a mute gateway hung; the read runs on a thread with recv_timeout.
The checkpoint store prunes uncontended entries from its per-workflow lock map, which otherwise held one Arc<Mutex> per workflow id for the life of the process.
The page registry URL update after navigate (a stale page_list) and the expected-URL settle wait before click verification are logged instead of silently discarded.
The Firefox companion removes pending prompts when their context is destroyed, and serializes cookie names and values as JSON into the document.cookie statement β the hand-rolled escape let a ; or newline break out of or inject into it.
A bootstrap.env parse error reports the line number instead of the offending line, which is the bearer token.
The MCP stdio read loop shares the 64-deep in-flight bound the notification branch already had. A client pipelining thousands of tools/call frames without reading responses could exhaust memory and browser processes.
replace_session's timeout error states its contract: the timeout reports while the owned cleanup finishes in the background, and the swap lands exactly once afterwards.
The MCP gateway tests run against a real RuntimeService β journal, worker pool, recovery coordinator β over an evidence-producing fake worker, and assert terminal outcomes: intentKind, resolutionPath, candidates, verification evidence, and the resolved evidence inside a checkpoint read back through a real RecoveryCoordinator. The RuntimeService::default() fixture behind ~50 of them had no journal, workers, or recovery, so every dispatched command failed downstream and the assertions (assert_ne!(code, -32602), dispatch counts) passed under total downstream failure.
Twelve of the fourteen security release cases run without a browser. They prove auth, framing, quota, store, and lifecycle boundaries and never lease a worker, but ran only behind the installed-Chromium ignore gate, so CI proved none of them. The release matrix still runs all fourteen; canary leakage and principal isolation open real pages and stay Chrome-gated.
page-runtime pins that a typed value which never lands fails verificationFailed. The positive type_text test passed whether or not the post-type comparison ran, because the fake echoed the expected value after any write.
The default fingerprint profile and the behavioral benchmark are pinned to reviewed literals β UA, platform, locale, timezone, WebGL vendor and renderer, screen and client-hint fields, plus the seeded overall, category, and 19 per-dimension scores β in addition to matching generated output. Both previously compared against values the code under test produced, so regeneration blessed any drift.