Commit Graph

9153 Commits

Author SHA1 Message Date
Saurav Panda 8e98dd46b5 fix: upgrade aiohttp to 3.13.4 to patch memory exhaustion vulnerability (#4596)
## Summary

- Bumps `aiohttp` from `3.13.3` to `3.13.4` in
`browser_use/skill_cli/requirements-cli.txt`
- Fixes a security vulnerability where insufficient restrictions on
trailer header handling could allow uncapped memory usage, potentially
causing memory exhaustion from attacker-controlled requests/responses

Upstream patch: aio-libs/aiohttp@0c2e9da

Closes #26

## Test plan

- [ ] No functional code changes — version pin bump only
- [ ] Verify `install_lite.sh` installs the patched version

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Upgrade `aiohttp` to 3.13.4 in the CLI to patch a memory exhaustion
vulnerability in trailer header handling. Ensures the lite installer
pulls the fixed version.

- **Dependencies**
- Bump `aiohttp` from `3.13.3` to `3.13.4` in
`browser_use/skill_cli/requirements-cli.txt` to prevent uncapped trailer
header processing that could lead to memory exhaustion.

<sup>Written for commit 14ada65183.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-02 16:34:40 -07:00
Saurav Panda 14ada65183 Merge branch 'main' into worktree-fix+aiohttp-security-upgrade 2026-04-02 16:31:45 -07:00
Saurav Panda a7fb3884c4 Merge branch 'main' into worktree-fix+requests-security-upgrade 2026-04-02 16:31:38 -07:00
sauravpanda 59ef9adeb6 fix: upgrade requests to 2.33.0 to patch temp-file path-traversal vulnerability
Bumps requests from 2.32.5 to 2.33.0.
extract_zipped_paths() previously wrote to a predictable temp path with no
validation, allowing a local attacker to pre-create a malicious file that
would be loaded in its place. 2.33.0 extracts to a non-deterministic
location, eliminating the race condition.
2026-04-02 16:28:49 -07:00
Saurav Panda e2ce72fd94 fix: security and correctness issues found in #4514 review (#4590)
## Summary

Three security and correctness issues found during review of #4514,
which has since been merged. All three affect the new `skill_cli` layer
introduced by that PR.

- **`BROWSER_USE_API_KEY` env var silently ignored**
(`commands/cloud.py`): #4514 dropped the env var fallback in
`_get_api_key()` without a migration path, breaking CI/CD pipelines that
set it as a secret. Restored with a deprecation warning directing users
to `browser-use config set api_key`.

- **`_install_cloudflared()` downloads binary without integrity check**
(`commands/setup.py`, Linux only): Raw `urllib.request.urlretrieve`
wrote directly to the install destination with no verification. Now
downloads to a temp file, fetches the `.sha256sum` Cloudflare publishes
alongside each release, verifies SHA256 before installing, and cleans up
on failure. macOS (`brew`) and Windows (`winget`) were already safe —
they verify internally.

- **`write_config()` not atomic** (`config.py`): Direct
`path.write_text()` truncates `config.json` on `SIGKILL` mid-write;
`read_config()` catches `json.JSONDecodeError` and returns `{}`,
silently wiping the API key and all settings. Now uses
`tempfile.mkstemp(dir=same_dir)` + `fsync` + `os.replace()` — the same
pattern `_write_state()` in `daemon.py` already uses correctly.

## Test plan

- [ ] `BROWSER_USE_API_KEY=sk-xxx browser-use cloud connect` prints
deprecation warning and still authenticates
- [ ] After `browser-use config set api_key sk-xxx`, commands work
without the env var set
- [ ] On Linux: cloudflared install rejects a tampered binary with a
clear SHA256 mismatch error
- [ ] `SIGKILL` during `browser-use config set` leaves `config.json`
intact or absent, never truncated

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-02 16:27:36 -07:00
sauravpanda 22f0e501e9 fix: upgrade aiohttp to 3.13.4 to patch memory exhaustion vulnerability
Bumps aiohttp from 3.13.3 to 3.13.4 in requirements-cli.txt.
Fixes uncapped memory usage from insufficient trailer header restrictions
(aio-libs/aiohttp@0c2e9da).
2026-04-02 16:26:39 -07:00
sauravpanda 56d8aa8483 fix: address review violations — drop env var fallback, fix cross-fs move
- cloud.py: remove BROWSER_USE_API_KEY env var fallback (violates CLI
  policy of config.json as single source of truth); instead detect the
  env var in the error path and print a targeted migration hint
- setup.py: replace Path.rename() with shutil.move() so the temp file
  can be moved across filesystems (e.g. /tmp -> /usr/local/bin)
2026-04-02 16:21:24 -07:00
sauravpanda ea99055e53 fix: write config.json atomically via tmp+rename to prevent silent data loss
A SIGKILL mid-write truncates config.json; read_config() catches
json.JSONDecodeError and returns {}, silently wiping the API key and
all other settings. Mirror the pattern already used by _write_state():
write to a sibling temp file, fsync, chmod 600, then os.replace() into
place — which is atomic on POSIX and effectively atomic on Windows.
2026-04-02 13:08:37 -07:00
sauravpanda 7a887e156e fix: verify cloudflared binary SHA256 checksum before installing on Linux
Downloads to a temp file, fetches the .sha256sum file Cloudflare publishes
alongside each release, and verifies before moving to the install destination.
Protects against MITM/CDN tampering. Temp file is cleaned up on failure.
2026-04-02 13:08:37 -07:00
sauravpanda 96bb65dcb5 fix: warn on deprecated BROWSER_USE_API_KEY env var instead of silently ignoring it
The CLI previously accepted the env var as a fallback; this PR dropped it
without a migration path, breaking CI/CD pipelines that set it as a secret.
Restore backwards-compat by checking the env var after config.json and
printing a deprecation warning with the migration command.
2026-04-02 13:08:37 -07:00
shawn pana ad87b45200 another big cli update (#4514)
Multiple agents can share one browser via --connect without interfering
with each other. Each agent registers with `browser-use register` to get
a numeric index, then passes it with `--connect <index>` on every
command.

- Tab locking: mutating commands (click, type, open) lock the tab to the
agent. Other agents get an error if they try to mutate the same tab.
Read-only commands (state, screenshot) work on any tab.
- Agent registry: agents.json tracks registered agents with timestamps.
Expired agents (5min inactive) get cleaned up automatically.
- Session lock: prevents double BrowserSession creation when two agents
connect simultaneously.
- Focus swap: daemon swaps agent_focus_target_id and cached_selector_map
per-agent before each command, so element indices are isolated.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replaces multi‑session isolation with per‑session browsers via
`--session`, adds a lightweight direct‑action runtime, tab controls, and
a phase‑aware daemon. Deprecates `--connect`, enables zero‑config `cloud
connect` with config‑driven recording and config‑only cloud auth, adds a
`config` command, strengthens lifecycle probes/close behavior, speeds up
cloud connect by skipping per‑call profile validation, and shows
“Connecting…”/“Closing…” during slow operations.

- **New Features**
- Session isolation: each `--session` has its own daemon, socket,
PID/state files, and browser; sessions run in parallel; `sessions` shows
phase and includes CDP URL only in `--json` (including cloud).
- Tabs: `tab list`, `tab new [url]`, `tab switch <index>`, `tab close
[index...]`; clearer errors that suggest `new`; `close` uses logical
focus; JS dialogs auto‑dismiss and surface in `state`; `state` uses the
focused tab’s title and preserves empty titles.
- Lightweight runtime: `CLIBrowserSession` with direct actions via
`ActionHandler` (no event bus/watchdogs); faster, more reliable scroll
(JS `scrollBy` with left/right fix); `Page.enable` wrapped to avoid
root‑target failures.
- Connect & cloud: one‑time `connect` for local Chrome; `cloud connect`
reads `cloud_connect_proxy`, `cloud_connect_timeout`, and
`cloud_connect_recording` from `browser-use config` (CLI default
recording on; library default remains off); unified base via
`BROWSER_USE_CLOUD_BASE_URL` host (CLI appends `/api/{version}`); `cloud
signup` to get/save API key; API key is read only from
`~/.browser-use/config.json` (env var ignored) and forwarded to
`profile-use`; cloud profile ID is read from config without per‑call GET
validation and auto‑heals on invalid/missing IDs (created only when
needed), with daemon‑safe error handling; empty API key is treated as
unset.
- Config, setup & lifecycle: `browser-use config` (set/get/list/unset)
with secure `config.json`; `doctor` shows config state with docs link;
installers create `config.json` and lite requires `curl`; interactive
`setup` (`--yes` for CI); daemon writes a phase‑aware state file,
includes PID in pings, has idle timeout, cleans up orphaned daemons,
supports cross‑platform shutdown with SIGTERM fallback, and avoids
deleting sockets for live daemons with stale PID files.
- Tests & fixes: real subprocess lifecycle tests; cloud stop timeout;
cloud connect faster via skipped profile validation; tests read API keys
from `config.json`; tolerate shutdown race after SIGTERM.

- **Migration**
- Use `--session <NAME>` for isolation. Removed `--agent`, `browser-use
register`, and tab‑ownership locking.
- Use `tab` subcommands; standalone `switch` and `close-tab` are
removed.
- Run `browser-use connect` once for local Chrome; the `--connect` flag
is deprecated and now errors with guidance.
- For cloud, use `cloud connect` with zero flags; customize via
`browser-use config` (`cloud_connect_proxy`, `cloud_connect_timeout`,
`cloud_connect_recording`); set `BROWSER_USE_CLOUD_BASE_URL` to the host
(CLI appends `/api/{version}`); use `cloud signup` to obtain and save an
API key; set the API key via `browser-use config set api_key <KEY>` —
`BROWSER_USE_API_KEY` is ignored by the CLI.

<sup>Written for commit a7b476ee46.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-02 13:06:16 -07:00
shawn pana a7b476ee46 Merge branch 'main' into multi-session 2026-04-02 13:02:51 -07:00
ShawnPana 454dbfdaba style: flatten assert to pass pre-commit formatter 2026-04-02 12:34:27 -07:00
ShawnPana 7db93b7ac7 fix: accept shutting-down race in test_close_orphaned_daemon
SIGTERM may not kill daemon before the CLI re-probes, producing a
"daemon may still be shutting down" warning instead of "Browser closed".
2026-04-02 12:28:15 -07:00
ShawnPana f8fdddc66d fix: update cloud tests to use config.json for API key instead of env var 2026-04-02 11:56:54 -07:00
ShawnPana 0530545c1a fix: API key single source of truth (config.json only), daemon-safe profile creation
- Remove BROWSER_USE_API_KEY env var as a read source from CLI code; config.json is the only source of truth
- Split _create_cloud_profile into daemon-safe _inner (raises) and CLI wrapper (sys.exit)
- Daemon auto-heal no longer kills process on profile creation API errors
2026-04-02 11:51:38 -07:00
ShawnPana 47ba16b8ab ux: show Connecting.../Closing... status during slow operations 2026-04-02 11:29:03 -07:00
ShawnPana 1deb430f8f perf: skip profile validation HTTP call on cloud connect
_get_or_create_cloud_profile reads config instantly instead of
validating via GET /profiles/{id} on every connect. If the profile
is invalid, _provision_cloud_browser auto-heals by creating a new
one and retrying. Saves ~500ms-1s on every cloud connect.
2026-04-02 11:21:12 -07:00
MagMueller 329c67f069 chore: bump version to 0.12.6 0.12.6 2026-04-02 00:49:08 -07:00
Magnus Müller ef536d4ffe perf: fix O(n²) bottlenecks in DOM capture for heavy pages (#4587)
Four small fixes for DOM capture performance. **4 files changed, +51
lines, -7 lines.**

## Fixes

### 1. `enhanced_snapshot.py` — Convert `isClickable` list to set before
loop

`_parse_rare_boolean_data` used `index in list` (O(n) per call), called
per node = O(n²).

```python
# Before: O(n) list scan per node
return index in rare_data['index']  # List[int]

# After: O(1) set lookup
is_clickable_set = set(nodes['isClickable']['index'])  # once
return index in rare_data_set  # per node
```

**20k elements: 14,160ms → 2,973ms. 100k elements: 356s → 9s.**

### 2. `paint_order.py` — Cap `RectUnionPure` at 5,000 rects

Each `add()` can split existing rects into up to 4 pieces. With
overlapping translucent layers this grows exponentially. `_MAX_RECTS =
5000` stops it. When hit, `contains()` returns False — less aggressive
filtering, same correctness.

**20k elements: 372s → 4.7s.**

### 3. `service.py` — Skip JS listener detection on pages with >10k
elements

Early bail-out inside the existing JS expression. Interactive elements
still detected via accessibility tree + `ClickableElementDetector`.

### 4. `dom_watchdog.py` — Add 2s timeout to pending network request
check

`_get_pending_network_requests()` had no timeout. On slow CI machines or
pages mid-navigation, it hangs for 15s+ silently eating into the 30s
`BrowserStateRequestEvent` budget. This causes the DOM capture to
timeout, producing 5 consecutive failures → agent termination.

Found by tracing eval failures on eBay search results where the
DOMWatchdog produced zero log output for 15 seconds between "STARTING"
and the timeout.

## Benchmarks

Profiled every pipeline stage on `main` vs this branch:

| Stage | main (20k) | This PR (20k) |
|---|---:|---:|
| `build_snapshot_lookup` | 14,160ms | **2,973ms** |
| JS listener detection | 2,326ms | **0ms** |
| `calculate_paint_order` | 372,303ms | **4,683ms** |
| Everything else | ~12s | ~8s |
| **Total** | **~400s** | **~16s** |

## Eval results

Ran WebBench_READ_v5 (198 tasks) on both branches, 4 runs total.
Task-by-task comparison on 35 common tasks:

- Always pass (both branches): 6
- Always fail (both branches): 13
- **Main always better: 0**
- **Ours always better: 1**
- Flaky (random): 15

No regressions. Normal pages (<2k nodes) unaffected — same timings, same
behavior.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it changes DOM capture heuristics (timeouts and
early-bail conditions) that can affect which elements are marked
clickable/visible or filtered by paint order on very large pages.
> 
> **Overview**
> Speeds up DOM capture on very large pages by removing a key O(n²)
hotspot and adding protective caps/timeouts to avoid long stalls.
> 
> `enhanced_snapshot.build_snapshot_lookup` now converts CDP
`isClickable.index` to a `set` once and uses O(1) membership checks per
node. Paint-order filtering adds a 5k-rectangle safety cap in
`RectUnionPure.add()` to prevent runaway rect fragmentation, and JS
click-listener detection skips entirely when the page has >10k elements.
> 
> `DOMWatchdog` also wraps the pending-network-request precheck in a 2s
`asyncio.wait_for` and logs a timeout, preventing this step from
consuming most of the `BrowserStateRequestEvent` budget.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3aa68384ad. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2026-04-02 00:47:07 -07:00
ShawnPana 63904858f4 fix: CDP URL only in sessions --json, not in table output 2026-04-01 22:40:48 -07:00
ShawnPana 8c6d042a79 fix: truncate CDP URL in sessions table, full URL in --json 2026-04-01 22:39:30 -07:00
ShawnPana b1522b5e23 fix: sessions shows CDP URL for cloud sessions too
Ping response now returns live CDP URL from the browser session
(not just the constructor arg). Cloud sessions show their
provisioned CDP URL.
2026-04-01 22:37:23 -07:00
ShawnPana c09ea5a0b2 feat: sessions command shows CDP URL for each session 2026-04-01 22:35:11 -07:00
ShawnPana d6c9b8a24b fix: reject invalid boolean config values instead of silently coercing to False 2026-04-01 22:29:36 -07:00
ShawnPana f27c567aad fix: enable_recording defaults False in library, configurable in CLI
Library keeps recording off by default. CLI reads cloud_connect_recording
from config (defaults True). Users can disable with:
  browser-use config set cloud_connect_recording false
2026-04-01 22:22:33 -07:00
ShawnPana a2bcc1a3f9 fix: wrap Page.enable in try/except (not supported on root CDP client)
Page.enable fails on browser-level CDP targets. Wrap in try/except
like the library's PopupsWatchdog does. Dialog handler still
registers regardless — events may fire on some CDP implementations.
2026-04-01 22:13:55 -07:00
ShawnPana 1aa6f6dd3b fix: remove tests for deleted TunnelManager.is_available() 2026-04-01 22:06:02 -07:00
ShawnPana addefa0352 fix: add ActionHandler to test SessionInfo constructions
test_cli_upload and test_cli_coordinate_click create SessionInfo
without actions, hitting the type-narrowing guard. Now provide
ActionHandler in all test cases.
2026-04-01 21:58:58 -07:00
MagMueller 3aa68384ad style: use TimeoutError instead of asyncio.TimeoutError (py3.11+) 2026-04-01 21:54:58 -07:00
ShawnPana 778984af8e fix: remove unnecessary string quotes on BrowserSession type hint 2026-04-01 21:49:10 -07:00
ShawnPana 92181538d3 fix: type-narrow actions instead of assert (fixes pyright + test compat) 2026-04-01 21:43:56 -07:00
MagMueller dcc7e691c4 fix: add 2s timeout to pending network request check in DOM watchdog
On slow CI machines, _get_pending_network_requests() can hang for 15s+
when Chrome is busy loading/rendering after a navigation. This silently
eats into the 30s BrowserStateRequestEvent budget, leaving insufficient
time for the actual DOM capture — causing 5 consecutive timeouts and
agent termination.

Observed on eBay search results in eval runs: DOMWatchdog started but
produced zero log output for 15 seconds before the timeout killed it.
The pending network check was the first await after the URL log.
2026-04-01 21:40:06 -07:00
ShawnPana 080eeae62a fix: CI type errors and test compatibility
- type: ignore on each param line in sessions.py (pyright per-line)
- Remove ActionHandler assert in browser.py (breaks pre-existing tests)
- Ruff format
2026-04-01 21:39:51 -07:00
shawn pana f9f56b8392 Merge branch 'main' into multi-session 2026-04-01 21:31:42 -07:00
ShawnPana a3b6217e8f fix: SessionInfo.browser_session typed as BrowserSession
Fixes type errors in test_cli_upload and test_cli_coordinate_click
which construct SessionInfo with BrowserSession instances.
2026-04-01 21:31:18 -07:00
ShawnPana 51244762cc fix: ActionHandler accepts BrowserSession (not just CLIBrowserSession)
Fixes type errors in test_cli_upload and test_cli_coordinate_click
which pass BrowserSession. CLIBrowserSession inherits from it so
the runtime behavior is unchanged.
2026-04-01 21:26:08 -07:00
ShawnPana c312191e8e fix: update test_cli_connect for --connect deprecation 2026-04-01 20:06:42 -07:00
ShawnPana 0bf1f02d97 fix: CI failures — ruff formatting, type errors, test_setup_command
- Ruff format all skill_cli and test files
- Fix type: get_config_value returns str|int|None, callers cast properly
- Fix type: BrowserWrapper.actions is non-optional (always provided)
- Fix type: config comparison uses 'is' not '=='
- Rewrite test_setup_command for new setup.handle(yes=True) API
- Add None guard in test_cli_lifecycle for state file
2026-04-01 19:58:33 -07:00
ShawnPana bebb3d1a80 fix: treat empty BROWSER_USE_API_KEY as missing in ensure_daemon
Consistent with the same fix applied to config.py and browser.py.
2026-04-01 19:51:36 -07:00
ShawnPana 4163531dfd fix: title fallback checks if focused tab was found, not if title is empty
A tab with empty title (about:blank) should show empty, not
fall back to tabs[0]'s title.
2026-04-01 19:48:51 -07:00
ShawnPana 6759ba9783 fix: add curl prerequisite check to install_lite.sh 2026-04-01 19:40:00 -07:00
ShawnPana 254f117cf2 chore: remove mock-based robustness tests (violates no-mock policy)
test_cli_lifecycle.py already covers the important paths with real
daemon subprocesses. The mock-heavy tests duplicated coverage with
less confidence.
2026-04-01 19:36:16 -07:00
ShawnPana d750babbec fix: timeout cloud browser stop to prevent blocking event loop
5s timeout on stop_browser() API call. If it hangs, skip and
disconnect — cloud browser will time out server-side.
2026-04-01 19:34:53 -07:00
ShawnPana 1711a8918e fix: sessions won't delete socket of live daemon with stale PID
Check socket_reachable before cleaning up files. A daemon with a
corrupt/stale PID file but a live socket is still running.
2026-04-01 19:31:59 -07:00
ShawnPana 8577c789eb fix: close log file handle on successful daemon start in test 2026-04-01 19:29:27 -07:00
ShawnPana 4cd2f456ef fix: SKILL.md references doctor instead of cloud login for status check 2026-04-01 19:27:36 -07:00
ShawnPana 074ff9c2d2 fix: state reports focused tab's title instead of tabs[0]
In multi-tab sessions, state was always reporting the first tab's
title. Now uses agent_focus_target_id to find the correct tab,
falling back to tabs[0] if no focus is set.
2026-04-01 19:26:37 -07:00
ShawnPana b19a8bb0b0 fix: treat empty BROWSER_USE_API_KEY as unset
Empty string or whitespace-only env var now treated as missing
across all CLI code that checks the API key.
2026-04-01 19:24:23 -07:00
ShawnPana 7132935aa0 fix: detect Linux arch for cloudflared download (amd64 vs arm64) 2026-04-01 19:14:32 -07:00