Commit Graph

293 Commits

Author SHA1 Message Date
Magnus Müller c7da2c0e10 Fix Rust PR review feedback 2026-06-06 04:59:30 +00:00
Magnus Müller 1474c60da1 Merge remote-tracking branch 'upstream/main' into magnus/browser-use-rust-core-integration
# Conflicts:
#	browser_use/__init__.py
#	browser_use/agent/service.py
#	browser_use/browser/watchdogs/downloads_watchdog.py
#	browser_use/code_use/utils.py
#	browser_use/llm/models.py
#	browser_use/tokens/service.py
#	browser_use/utils.py
2026-06-04 16:14:57 +00:00
Magnus Müller 20e70519cf Filter generic network auto-downloads 2026-06-04 09:10:02 +00:00
Saurav Panda f0413fbd8c refactor(downloads): tighten download-sanitization comments
Drop the per-call-site rationale text; the helper names already convey
what's happening, and the WHY lives in the commit history. Shrink both
helper docstrings to one line.

No behavior change; 16/16 tests still pass.
2026-05-18 17:31:09 -07:00
Saurav Panda 29b03e43b4 Merge branch 'main' into fix/sec-download-path-traversal 2026-05-18 17:28:52 -07:00
Saurav Panda 4866656bba refactor(security): tighten _is_ip_address comments
Condense the running narrative across four prior fix commits into a single
docstring summarizing what canonicalization the function mirrors and the
never-throw invariant. Drop the per-step justifications — those live in
the commit messages where they belong.

No behavior change; 40/40 tests still pass.
2026-05-18 16:46:55 -07:00
Saurav Panda 7d061a0327 fix(security): fold IDNA label separators before IPv4 classification
Addresses third P1 codex review on #4866.

Per RFC 3490 / UTS46, four code points act as label separators in IDNA
processing — `.` (U+002E), `。` (U+3002 IDEOGRAPHIC FULL STOP),
`.` (U+FF0E FULLWIDTH FULL STOP), and `。` (U+FF61 HALFWIDTH IDEOGRAPHIC
FULL STOP). WHATWG URL parsing folds all four to `.` before resolution,
so `http://127。0。0。1/` and `http://127。0。0。1/` reach 127.0.0.1.

NFKC handles only U+FF0E and partially maps U+FF61 → U+3002, leaving the
IPv4 parser unable to match the dominant ideographic-dot form. Explicitly
replace U+3002 and U+FF61 with `.` after NFKC.

Test `test_idna_dot_separators_blocked` covers all four dot variants
both at `_is_url_allowed` and `_is_ip_address` levels, plus a combined
case with circled digits (`①②⑦。⓪。⓪。①`).
2026-05-18 16:40:24 -07:00
Saurav Panda e34d2cc6c2 fix(security): NFKC-normalize host before IPv4 classification
Addresses second P1 codex review on #4866.

WHATWG URL canonicalization maps fullwidth digits (`127.0.0.1`),
circled digits (`①②⑦.⓪.⓪.①`), and Unicode-prefixed hex forms
(`0x7f000001`) to ASCII IPv4 literals (`127.0.0.1` and `0x7f000001`)
before resolution. The classifier saw only the original string and
returned False, so `block_ip_addresses=True` was still bypassable by
re-encoding the IP with any equivalent Unicode digit variant.

Add a `unicodedata.normalize('NFKC', ...)` step after percent-decoding
and before parsing. NFKC handles all the bot's example forms and is
stdlib-only (no IDNA dependency). Wrapped in try/except to preserve the
never-throw invariant.

Tests:
- test_unicode_normalized_ipv4_blocked covers fullwidth, fullwidth+ASCII
  hex, and circled-digit forms at both `_is_url_allowed` and
  `_is_ip_address` boundaries.
- test_idn_domains_not_misclassified_as_ip is a false-positive guard
  ensuring legitimate IDN domains (`café.example`, `日本.example`, their
  punycode equivalents) remain classified as domains.
2026-05-18 16:26:51 -07:00
Saurav Panda b6ef0e2888 fix(security): percent-decode host before IPv4 classification
Addresses second codex review on #4866 (P1).

Chromium percent-decodes the host component before applying its IPv4
parser, so a URL like `http://%30x7f000001/` (decodes to `0x7f000001`,
i.e. 127.0.0.1) or `http://%31%32%37.0.0.1/` (decodes to `127.0.0.1`)
still reaches the IP address despite `block_ip_addresses=True`. Without
decoding, `_is_ip_address` sees the literal `%`-encoded string and
returns False — bypassing the block.

Call `urllib.parse.unquote` on the host before passing it to both
`ipaddress.ip_address` and `socket.inet_aton`. Wrap in try/except to
preserve the never-throw invariant for the classifier.

Adds two regression tests:
- test_percent_encoded_ipv4_blocked covers mixed (`%30x7f000001`), fully
  encoded canonical (`%31%32%37.0.0.1`), and fully encoded decimal
  (`%32%31%33%30%37%30%36%34%33%33`) bypass forms.
- test_malformed_percent_encoding_does_not_crash covers lone `%`, `%zz`,
  `%2` — `unquote` leaves these as-is and the classifier must not throw.
2026-05-18 16:17:26 -07:00
Saurav Panda b0d543fff4 fix(security): catch non-OSError failures from inet_aton
Addresses codex review on #4866.

`socket.inet_aton` raises `UnicodeEncodeError` (not `OSError`) for
hostnames containing lone surrogates — common in URLs produced by
URL-decoding malformed UTF-8. The earlier patch only caught `OSError`,
so `_is_ip_address('\udcff')` would propagate the exception through
`_is_url_allowed` and crash the navigation security check whenever
`block_ip_addresses=True`.

Restore the original code's defensive `except Exception` posture for
both `ipaddress.ip_address` and `socket.inet_aton`. The classifier
should never throw — it returns True (recognized IP) or False (not a
recognizable IP); downstream domain-allowlist handling then applies.

Regression test covers lone-surrogate hostnames in
`test_malformed_unicode_hostnames_do_not_crash_classifier`.
2026-05-18 15:52:46 -07:00
Saurav Panda c2ac67ab90 fix(downloads): sanitize attacker-controlled filenames and verify containment
GHSA-rv9j-wqjp-2fv4 (critical), GHSA-66xh-g88g-2h8j, GHSA-hpr4-fqgr-xhj9.

`DownloadsWatchdog` joined attacker-controlled filenames from CDP
(`Page.downloadWillBegin.suggestedFilename`) and `Content-Disposition`
headers directly into the configured `downloads_path`. Strings like
`../../escape.bin` or `/etc/shadow.bak` would `os.path.join` outside the
downloads directory, writing the fetched bytes (also attacker-controlled
— the response body is the exploit content) to an arbitrary location
with the agent's process privileges.

`download_file_from_url` triggers passively for any
`Content-Disposition: attachment` response, so this is reachable from any
visited site — `allowed_domains` does not mitigate it.

Add two private helpers on DownloadsWatchdog:

- `_sanitize_download_filename(name)`: keep only the basename, normalize
  Windows separators, strip null bytes, fall back to `'download'` for
  empty / pure-traversal inputs.
- `_is_path_contained(path, dir)`: realpath containment check for the
  on-disk sinks.

Wire the sanitizer at every attacker-controlled filename ingress:

- `download_will_begin_handler` (CDP suggestedFilename → cache + events)
- `_handle_cdp_download` (same field, separate path)
- Network-monitor Content-Disposition parser
- `download_file_from_url` (suggested_filename argument)
- `_handle_download` (Playwright `download.suggested_filename`)

Wire the containment check at every on-disk write site:

- `download_file_from_url` write
- `_handle_download` (Playwright save_as path)
- `trigger_pdf_download` write (defense in depth — already basename'd)
2026-05-18 13:52:24 -07:00
Saurav Panda 626bda9072 fix(security): canonicalize non-standard IPv4 forms in block_ip_addresses
GHSA-xrfv-gg9f-wwjp, GHSA-g27c-8gp4-28cv.

`SecurityWatchdog._is_ip_address` only recognized IP strings that
`ipaddress.ip_address()` accepts — i.e. the canonical dotted-quad form
(`127.0.0.1`) and full IPv6. Chromium and the kernel resolver, however,
also accept several non-standard IPv4 representations:

  http://2130706433/     → 127.0.0.1   (decimal int)
  http://0x7f000001/     → 127.0.0.1   (hex)
  http://0177.0.0.1/     → 127.0.0.1   (octal)
  http://127.1/          → 127.0.0.1   (short-form)
  http://127.0.1/        → 127.0.0.1   (short-form)

`block_ip_addresses=True` was therefore trivially bypassed by re-encoding
the IP in any of these forms.

Fall back to `socket.inet_aton` after `ipaddress.ip_address()` fails — it
accepts the same liberal IPv4 forms the kernel resolver does, so the
classifier matches the browser's behavior.

The existing `test_ipv4_lookalike_domains_allowed` test was codifying the
buggy behavior for `1.2.3` (which IS a short-form IPv4 == 1.2.0.3).
Removed that assertion and added a dedicated `TestNonStandardIPv4Representations`
class covering decimal/hex/octal/short-form blocking, lookalike-domain
non-interference, and the interaction with `allowed_domains`.
2026-05-18 13:47:17 -07:00
Larsen Cundric 02dd650a19 fix: handle multi-character upper/lower in CDP typing
ord() raises TypeError when called on a multi-character string. Several
Unicode characters expand on case conversion ('ß'.upper() == 'SS',
'ffi'.upper() == 'FFI', 'ʼn'.upper() == 'ʼN'). Typing any of these caused
'Failed to input text via CDP: TypeError: ord() expected a character,
but string of length 2 found' and aborted the action.

Fall back to the original character's code point when upper()/lower()
returns more than one code point. The Windows VK code is only an
approximation anyway; the 'text' field passed to dispatchKeyEvent
carries the actual character.
2026-05-11 08:03:49 +00:00
Saurav Panda 98a86bead7 Merge branch 'main' into main 2026-05-01 15:43:05 -07:00
Saurav Panda 132756dabb Address PR review feedback for record start/stop
- `on_BrowserConnectedEvent` now catches `RuntimeError` from
  `start_recording()` so sessions with `record_video_dir` configured but
  missing `[video]` extras (or a viewport that can't be sized) keep
  starting — prior graceful-degradation behavior is restored.
- Lazy `RecordingWatchdog` in the CLI handler now calls
  `attach_to_session()`, so `AgentFocusChangedEvent` / `BrowserStopEvent`
  handlers are wired correctly if the session dispatches them.
- Daemon shutdown finalizes any in-progress recording before tearing the
  browser down, preventing truncated MP4s on `close`, idle timeout, or
  signal-driven exit.
- Added regression test that monkeypatches `start_recording` to raise and
  asserts `on_BrowserConnectedEvent` swallows it without breaking startup.
2026-04-20 15:11:49 -07:00
Saurav Panda b1d933258c Add record start/stop CLI command for session video capture
Closes #4533.

- `RecordingWatchdog` gains public `start_recording(path, size?, framerate?)`,
  `stop_recording() -> Path`, and `is_recording`; the existing
  `BrowserConnectedEvent`/`BrowserStopEvent` path is refactored to use them,
  so profile-driven recording behavior is unchanged.
- `browser-use record start <path>` / `record stop` / `record status`
  subcommands wired through argparse, daemon dispatch, and the browser
  command handler. `record stop` prints the saved file path so it can be
  captured programmatically, matching the issue's requested UX. Works with
  `--session NAME` via the existing named-daemon infrastructure.
- The CLI's `CLIBrowserSession` intentionally skips watchdogs; the handler
  lazily instantiates `RecordingWatchdog` on first `record start` so CLI
  recording doesn't pay the watchdog-setup cost for non-recording sessions.
- Output format is `.mp4` (libx264) since that's what the existing
  `VideoRecorderService` encodes; optional dependency gate is unchanged
  (`pip install "browser-use[video]"`).
- New `tests/ci/test_action_record.py` exercises the full stack against a
  real headless browser + `pytest-httpserver`, verifying decodable MP4
  output, double-start rejection, stop-without-start no-op, that the
  existing `profile.record_video_dir` flow still works, and the argparse /
  dispatch wiring.
2026-04-20 14:50:52 -07:00
Laith Weinberger 4476f6e16e fix input clear fallbacks and clarify clear-then-type behavior 2026-04-15 17:31:04 -04:00
Alisha Ukani 628d8f44a7 Search for playwright-installed Chromium in chrome-mac or chrome-mac-arm64 folders 2026-04-14 13:10:31 -07:00
Laith Weinberger 03e2bc4da8 prefer Playwright chromium over sys Chrome by default 2026-04-12 11:41:50 -04:00
Laith Weinberger 534eaafe7a clear dom cache after scroll to prevent stale extract data 2026-04-11 18:03:06 -04:00
MagMueller 3aa68384ad style: use TimeoutError instead of asyncio.TimeoutError (py3.11+) 2026-04-01 21:54:58 -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
Laith Weinberger 393d2c5b0f rm pass 2026-03-25 16:56:36 -04:00
Saurav Panda 4a65aea6a9 Merge branch 'main' into auto-switch-new-tab 2026-03-21 17:17:32 -07:00
Saurav Panda e7a05cbe86 fix: auto-switch to new tabs after click and add checkbox toggle fallback
- AGI-569: after any click that opens a new tab, automatically dispatch
  SwitchTabEvent so the agent lands on the new page immediately instead
  of requiring a manual switch step (~877 occurrences)

- AGI-548: for <input type="checkbox/radio">, capture checked state
  before the CDP mouse click and verify it toggled afterward; if
  unchanged (custom-styled or shadow-DOM-backed inputs), fall back to
  JS element.click() and report the final checked state in metadata
  (~1,241 occurrences)
2026-03-20 12:43:58 -07:00
Saurav Panda c0b2fc1210 prevent highlight cleanup from blocking 2026-03-16 17:46:42 -07:00
Saurav Panda f8c729d468 fix(browser): prevent remove_highlights() from blocking screenshot handler on slow pages 2026-03-16 10:05:53 -07:00
Saurav Panda 2344df3fac fix: pass through timeout_ms and clip params in event handlers 2026-03-07 14:34:08 -08:00
Laith Weinberger f5df080b41 lint 2026-03-01 16:21:49 -05:00
Laith Weinberger 209866ed5b fix channel param to select correct browser binary 2026-03-01 16:17:18 -05:00
Laith Weinberger 8ebe4385ed fix: preserve non-ASCII chars in all remaining JSON file outputs 2026-03-01 14:19:38 -05:00
laithrw 744c2be4ae Merge branch 'main' into issue-3523-fix 2026-02-26 21:05:37 -05:00
Aitor f60a62bc76 Merge branch 'main' into feat/add-captcha-solver-handling 2026-02-24 07:13:06 +01:00
Saurav Panda 84babc6b75 fix radio button click occlusion when label overlaps input 2026-02-23 16:19:05 -08:00
reformedot 45e9a6cea3 chore: linting 2026-02-23 11:03:14 -08:00
reformedot b4058359a9 refactor: enhance CaptchaWatchdog with typed event handling and result types
- Introduced type aliases for captcha results to improve clarity and maintainability.
- Updated event handler signatures to use specific event types from the CDP module.
- Ensured consistent typing for captcha result attributes within the CaptchaWaitResult class.
- Added assertion for timeout handling in the captcha waiting logic.
2026-02-23 10:57:48 -08:00
reformedot ed28e50806 feat: added automatic captcha solver handling via CAPTCHA watchdog 2026-02-23 10:51:02 -08:00
LarsenCundric c67c31932e Fix 2026-02-20 10:57:00 -08:00
Laith Weinberger 4b81a4212d fix(storage_state): typing + format cleanup 2026-02-18 14:37:20 -05:00
Laith Weinberger 6db6678a83 style: format downloads_watchdog 2026-02-16 17:36:10 -05:00
Laith Weinberger 0536f5808c Merge origin/main into fix-minimal-issue-7JLZB 2026-02-16 17:33:02 -05:00
Laith Weinberger c6c431c95d fix(#3515): avoid stale download caches 2026-02-16 17:32:12 -05:00
Laith Weinberger fa51754895 fix(storage_state): await restore, normalize session cookies, scope storage to origin 2026-02-14 13:23:10 -05:00
Saurav Panda 4857d190e3 comment linting issue 2026-02-11 15:01:25 -08:00
Saurav Panda 30dbf9e535 fix: retry select_dropdown when options are lazily populated 2026-02-10 18:50:05 -08:00
laithrw 9d81f64b2e Merge branch 'main' into kalil0321/har_watchdog 2026-02-04 21:17:10 -05:00
Saurav Panda 5d7ff041d3 improved concatenation heuristic 2026-02-03 18:02:58 -08:00
Saurav Panda 2a5768f3da fixed pyright issues 2026-02-03 13:02:03 -08:00
Saurav Panda 749fa56d1a improve text field handling 2026-02-03 12:52:56 -08:00
laithrw a8ce4a2a43 Merge branch 'main' into wait-file-download 2026-01-31 18:22:30 -05:00