Previously __import__ was a plain closure whose __globals__ contained
this module's real __builtins__, so sandboxed code could escape via
__import__.__globals__['__builtins__']['__import__']('os').
Wrap _safe_import in _SafeBuiltin (same hardening as other builtins) so
its __globals__ attribute is not reachable from user code. Also resolve
_real_import at wrap-time rather than closing over __builtins__ to keep
the closure minimal.
Replace closure wrapper with a callable class (_SafeBuiltin) built in an
isolated exec namespace. Closures leak this module's globals via
wrapper.__globals__ (print.__globals__['io'], print.__globals__['Path']).
The callable class has no __globals__ attribute at instance level, and
the class methods' __globals__ is the isolated namespace instead of this
module.
Also block pathlib and asyncio imports — both modules import os directly,
so Path.cwd.__globals__['os'] and asyncio.subprocess were escape routes.
Remove os from the default namespace and restrict __builtins__ to a
curated safe set (no open/exec/eval/compile). Add an import guard that
blocks os, subprocess, shutil, sys, builtins, and other system-access
modules. Preserve __build_class__ so class definitions still work.
Wrap builtin functions to prevent __self__ leaking the builtins module.
Remove Path and asyncio from pre-loaded namespace to close
Path.cwd.__globals__ and asyncio.sys.modules escape routes.
…k guidance
When `browser-use connect` fails to discover a running Chrome, the error
now points to the correct `chrome://inspect/#remote-debugging` URL. The
SKILL.md also guides agents to prompt users with two options: enable
remote debugging or use managed Chromium with a Chrome profile.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes the connect failure UX by pointing to the correct Chrome remote
debugging page and adding clear fallback steps. The error now links to
`chrome://inspect/#remote-debugging`, and SKILL.md guides users to
either enable remote debugging or use managed Chromium with their Chrome
profile.
<sup>Written for commit d0fbf4c580.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
When `browser-use connect` fails to discover a running Chrome, the error
now points to the correct `chrome://inspect/#remote-debugging` URL. The
SKILL.md also guides agents to prompt users with two options: enable
remote debugging or use managed Chromium with a Chrome profile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- pin the privileged stale workflow action to the immutable commit
behind `actions/stale@v9`
- preserve current behavior by keeping the same major tag target and
only removing tag-retarget drift
- leave existing workflow permissions and stale policy settings
unchanged
## Why
This scheduled workflow runs with `issues: write` and `pull-requests:
write`, so pinning the marketplace action to a commit reduces
supply-chain drift without changing workflow behavior.
## Validation
- `git diff --check`
- `python -c "from pathlib import Path; import yaml;
yaml.safe_load(Path('.github/workflows/stale-bot.yml').read_text(encoding='utf-8'));
print('yaml-parse-ok')"`
- local PC Control review-coder queued against the staged diff; it
remained running during push, so I treated it as degraded evidence and
completed a bounded manual preflight on the one-line workflow change
## Notes
- This intentionally stays scoped to one workflow line.
- I did not change permissions, timing, or stale policy behavior.
- `uv` / `pre-commit` and `actionlint` were not available on this
Windows shell, so I did not claim those checks ran locally.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Pin `actions/stale` in the scheduled workflow to the immutable v9 commit
to eliminate tag drift and reduce supply-chain risk. Behavior,
permissions, schedule, and stale policy remain unchanged.
<sup>Written for commit b1f755d509.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
thousands of users have attempted to use close, so why not add it
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds `close()` to `BrowserSession` as an alias for `stop()`, so
`session.close()` cleanly stops the session and matches common APIs.
<sup>Written for commit 76604913ad.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
Resolves#4610
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Prefer the `playwright`-bundled Chromium over system Chrome by default
to make local launches consistent across machines and CI. Also switches
installation to `uvx playwright install chromium`.
- **Refactors**
- Reordered search priority: channel-specific (non-default) ->
Playwright Chromium -> system Chrome -> other native browsers ->
Playwright headless-shell.
- Unified pattern ordering to always prioritize the target browser
group, then fall back to others.
- Switched install command and error messages from `chrome` to
`chromium`.
<sup>Written for commit 03e2bc4da8.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Gracefully handle MCP stdio disconnections by catching BrokenPipeError,
preventing crashes and shutting down the server cleanly.
- **Bug Fixes**
- Wrap `server.run` in a `try/except BrokenPipeError`.
- Log a warning and exit cleanly when the MCP client disconnects.
<sup>Written for commit df4e2f9f15.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
Resolves#4620
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fix pagination button detection to prefer semantic labels ("first",
"last") over shared glyphs ("«", "»") across sites. Removed those glyphs
from first/last patterns and reordered checks so first/last win before
next/prev, treating "«" and "»" only as prev/next fallbacks.
<sup>Written for commit 9ad4c63cdb.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
Resolves#4609
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes#4609 by preventing substring leaks during sensitive data
redaction. Redaction now replaces longer secrets first and uses shared
utils for consistent behavior.
- **Bug Fixes**
- Redact longest matches first to avoid partial/substring leaks.
- Support both legacy flat and domain-scoped `sensitive_data` formats.
- Apply consistent redaction across message manager and views.
- **Refactors**
- Added `collect_sensitive_data_values` and `redact_sensitive_string` in
`browser_use/utils.py`.
- Replaced inline redaction logic in
`browser_use/agent/message_manager/service.py` and
`browser_use/agent/views.py`.
<sup>Written for commit 65f87b7fca.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
Fixes#4626
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replace deprecated asyncio.get_event_loop/run_until_complete with
asyncio.run() in the CLI to restore Python 3.14 compatibility. Fixes
#4626 and prevents runtime errors in the `doctor` and `tunnel` commands.
<sup>Written for commit 99a8674214.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
Resolves#4631
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Clears the DOM watchdog cache after scrolls to prevent stale extraction
data and ensure DOM reads reflect the current page state. Addresses
#4631 where post-scroll data could be outdated.
- **Bug Fixes**
- Clear `_dom_watchdog` cache after both element-target and CDP gesture
scrolls.
- Wait ~200ms before clearing after element-target scroll so the DOM can
settle.
<sup>Written for commit 60e7767228.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
## Summary
- Add UTM tracking params to all cloud-bound links across README, CLI,
and error messages — enables measuring which OSS surfaces drive cloud
sign-ups
- Rewrite README "Open Source vs Cloud" section: position cloud browsers
as the recommended pairing for OSS users, remove separate "Use Both"
section
- Rewrite error messages for `use_cloud=True` and `ChatBrowserUse()` to
clearly state what is wrong and what to do next (not vague
"authentication failed" messages)
- Add missing URLs to dead-end errors: invalid API key now links to key
page, insufficient credits now links to billing page
- Add cloud browser nudge on captcha detection (`logger.warning`)
- Add cloud browser nudge on local browser launch failure (Chromium not
installed, etc.)
- Fix pre-existing pyright error with `readline.add_history` on Windows
## Changes by file
**README.md** — UTM params on 8 cloud links, "Use Both" section folded
into "Use Open Source" with cloud browsers as recommended pairing
**browser_use/agent/service.py** — Captcha nudges rewritten as
`logger.warning` with cloud URL + UTM
**browser_use/browser/cloud/cloud.py** — Error messages rewritten:
"BROWSER_USE_API_KEY is not set" / "is invalid" + UTM on URLs
**browser_use/browser/session.py** — Auth error re-raise simplified (no
rewrap), new nudge on local browser launch failure
**browser_use/llm/browser_use/chat.py** — Error messages rewritten,
added URL to 401 (invalid key) and billing URL to 402 (insufficient
credits)
**browser_use/cli.py** — UTM params added to all cloud URLs, fix
`readline.add_history` pyright error on Windows by using `getattr` at
import time
**browser_use/init_cmd.py** — UTM param added to API key URL
**browser_use/skill_cli/commands/cloud.py** — UTM param added to API key
URL
## UTM scheme
| utm_source | utm_medium | Where |
|---|---|---|
| `github` | `readme` | README links |
| `oss` | `cli` | CLI init/TUI messages |
| `oss` | `use_cloud` | `Browser(use_cloud=True)` errors |
| `oss` | `chat_browser_use` | `ChatBrowserUse()` errors |
| `oss` | `captcha_nudge` | Runtime captcha detection |
| `oss` | `browser_launch_failure` | Local browser launch failure |
- Add UTM params to all cloud-bound links across README, CLI, and error messages
- Rewrite README Open Source vs Cloud section: position cloud browsers as
recommended pairing for OSS users, remove separate Use Both section
- Rewrite error messages for use_cloud=True and ChatBrowserUse() to clearly
state what is wrong and what to do next
- Add missing URLs: invalid API key now links to key page, insufficient
credits now links to billing page
- Add cloud browser nudge on captcha detection (logger.warning)
- Add cloud browser nudge on local browser launch failure
## Summary
- Updates `browser-use-sdk` dependency from `2.0.15` to `3.4.2`
- All existing imports verified working with the new version
## Test plan
- [x] `uv sync` succeeds
- [x] All SDK imports (`AsyncBrowserUse`, type imports) verified working
- [ ] CI tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Upgrade `browser-use-sdk` from 2.0.15 to 3.4.2 and align our code with
v3 API changes. No behavior changes.
- **Dependencies**
- Updated `browser-use-sdk` to `3.4.2` in `pyproject.toml`.
- **Refactors**
- Moved to new top‑level SDK imports (`AsyncBrowserUse`,
`ExecuteSkillResponse`, `SkillListResponse`, `ParameterSchema`,
`SkillResponse`).
- Handle UUID skill IDs from the SDK by casting to and comparing as
`str`.
- On error, return `ExecuteSkillResponse` with `result=None`,
`stderr=None`, and `latencyMs=None` for schema compatibility.
<sup>Written for commit 1a94f96ce9.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Guard `mcp.server.stdio` startup against missing stdin. When stdin is
absent, raise a clear RuntimeError so the process fails fast instead of
producing ambiguous startup errors.
<sup>Written for commit 83317179cb.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes runtime crashes when patching LLM ainvoke on Pydantic-backed
models by using `object.__setattr__` to assign `tracked_ainvoke`. This
bypasses Pydantic attribute validation and keeps the tracking wrapper
working.
<sup>Written for commit 3242d9dbc2.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
## Fixes#4463
### Problems
**1. `AgentHistoryList.load_from_dict` — KeyError on missing keys**
The current implementation uses direct dict subscript access that
crashes when loading history saved with an older browser-use version or
when a step entry is missing expected keys:
```python
for h in data['history']: # KeyError if 'history' key absent
if h['model_output']: # KeyError if 'model_output' absent
if 'interacted_element' not in h['state']: # KeyError if 'state' absent
```
**2. `AgentHistoryList.final_result()` — IndexError on empty result
list**
```python
if self.history and self.history[-1].result[-1].extracted_content:
# ^^^^^^^^^^^^^^^^ IndexError when result == []
```
Every sibling method (`is_done`, `is_successful`, `judgement`,
`is_judged`, `is_validated`) already guards with
`len(self.history[-1].result) > 0`. `final_result` was the only one
missing this check.
### Fix
- **`load_from_dict`**: use `data.get('history', [])` and `h.get(key)`
instead of direct subscript access, matching standard defensive Python
dict usage.
- **`final_result`**: add `len(self.history[-1].result) > 0` guard
before accessing `result[-1]`, consistent with all other methods in the
class.
### Changes
Only `browser_use/agent/views.py` is modified (14 additions, 8
deletions).
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Prevent crashes when loading agent history with missing fields and when
reading the final result on empty lists. This makes `AgentHistoryList`
safer and backward-compatible with legacy or partial histories.
- **Bug Fixes**
- `load_from_dict`: use `data.get('history', [])` and `h.get()` for safe
access; initialize missing `state` and default
`state.interacted_element` to `None`; validate `model_output` only when
it’s a dict.
- `final_result`: check `len(self.history[-1].result) > 0` before
indexing; return `None` when empty or no extracted content, consistent
with other methods.
<sup>Written for commit 6129ceb0b0.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
In response to #4214
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Revamped the LLM model docs with a quick-reference table, use‑case
recommendations, and clearer provider setup. Adds native coverage for
more providers, updates examples, and improves env/installation
guidance.
- **Refactors**
- Added Quick Reference (provider → class → env) and use‑case picks
based on benchmarks.
- Renamed to “Browser Use Cloud,” updated pricing/models, and listed
`browser-use/bu-30b-a3b-preview`.
- Expanded provider docs: DeepSeek, Mistral, Cerebras, OpenRouter,
LiteLLM; improved OpenAI/Anthropic/Gemini/Azure/Bedrock/Vercel/OCI
guidance with links.
- Standardized examples to `Agent` + `Chat*` classes and noted key env
vars.
- Moved OpenRouter out of OpenAI-compatible section; trimmed/reorganized
OpenAI-compatible examples.
- Minor fixes: `GOOGLE_API_KEY` deprecation note, Anthropic coordinate
clicking note, “LangChain” capitalization.
<sup>Written for commit c1151715d3.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
## Summary
- **Root cause**: The daemon accepted any connection on its socket
without authentication. On Windows the port is deterministic
(`adler32(session)`-derived), so any local process could connect and
dispatch the `python` action, executing arbitrary code via
`eval()`/`exec()` as the daemon owner. On Unix the socket may be
world-writable under a permissive umask.
- **Fix**: On `Daemon.run()`, generate a `secrets.token_hex(32)` token,
write it atomically to `~/.browser-use/{session}.token` with `chmod
0o600`. Validate every incoming request with `hmac.compare_digest`
before dispatching. Delete the token file on shutdown.
- **Client**: `send_command()` in `main.py` now reads the token file and
attaches it to every request. Falls back to `''` for old daemons
(no-op).
## Test plan
- [ ] Start daemon, run `browser-use python "1+1"` — should work
normally
- [ ] Send a raw request without token to socket — should get
`{"success": false, "error": "Unauthorized"}`
- [ ] Unauthenticated `shutdown` action should be ignored (daemon stays
up)
- [ ] After daemon stops, `~/.browser-use/default.token` should be
deleted
- [ ] All CI tests pass (`uv run pytest -vxs tests/ci`)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Secure the daemon socket with a per-session auth token to block
unauthorized local connections and arbitrary code execution via the
`python` action. The CLI now reads the token and includes it with each
command.
- **Bug Fixes**
- Generate a session token in `Daemon.run()` (`secrets.token_hex(32)`);
write it atomically to `~/.browser-use/{session}.token` via a `*.tmp`
file created with `0o600`, then replace; raise if write fails; delete on
shutdown.
- Validate every request using `hmac.compare_digest`; unauthorized calls
return `{"success": false, "error": "Unauthorized"}` and `shutdown` is
honored only for authorized, successful requests.
- Client `send_command()` reads the token and sends it as `token`; falls
back to `''` for older daemons.
<sup>Written for commit ca2185ba61.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->
- Use os.open() with mode 0o600 instead of write-then-chmod to eliminate
the permission race window where the temp file is briefly world-readable.
- Raise instead of warn when token file write fails: a daemon that cannot
persist its auth token is permanently unauthorized for all clients, so
failing fast is correct (identified by cubic).
Generate a secrets.token_hex(32) on daemon startup, write it atomically
to ~/.browser-use/{session}.token (chmod 0o600), and validate it on every
incoming request via hmac.compare_digest. The client reads the token file
and includes it in each send_command() call.
This closes the arbitrary-code-execution vector where any local process
could connect to the deterministic Windows TCP port (or a world-readable
Unix socket) and dispatch the 'python' action to run eval()/exec() as the
daemon owner.
## Summary
- Bumps `requests` from `2.32.5` to `2.33.0` in `pyproject.toml`
- Fixes a local privilege escalation / file substitution vulnerability
in `requests.utils.extract_zipped_paths()`: prior versions extracted to
a predictable temp filename with no validation, allowing a local
attacker with write access to the temp directory to pre-create a
malicious file that the library would load instead
- 2.33.0 now extracts to a non-deterministic location, eliminating the
race condition
> **Note:** Standard usage of the Requests library is not affected. Only
applications calling `extract_zipped_paths()` directly are impacted.
## Test plan
- [ ] No functional code changes — version pin bump only
- [ ] `uv sync` resolves cleanly with the new pin
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Upgrade `requests` from `2.32.5` to `2.33.0` to fix a temp-file path
traversal/race in `requests.utils.extract_zipped_paths()` that could
allow local file substitution. Standard `requests` usage isn’t affected;
only direct callers of that helper were exposed, and no app changes are
needed.
<sup>Written for commit a7fb3884c4.
Summary will update on new commits.</sup>
<!-- End of auto-generated description by cubic. -->