From 13ec149c66b20d44ee94eec57443edc81cc2f60a Mon Sep 17 00:00:00 2001 From: Laith Weinberger Date: Wed, 10 Jun 2026 14:59:36 -0700 Subject: [PATCH 1/2] fix(tui): never pass about:blank or chrome:// targets to the macos opener - request_open_browser dropped its literal about:blank fallback; with no live/page url it now shows a notice instead of shelling out - open_external_url validates targets via new external_browser_target(): only http(s)/file urls reach open::that_detached; about:/chrome:// placeholders are rejected (macos can fuzzy-match junk open targets to app names, and "about*" uniquely resolves to About This Mac.app) - one test encodes the constraint; browser_panel test now seeds a real live url since the placeholder fallback is gone --- crates/browser-use-tui/src/main.rs | 57 +++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/crates/browser-use-tui/src/main.rs b/crates/browser-use-tui/src/main.rs index 52e14939..adf5413e 100644 --- a/crates/browser-use-tui/src/main.rs +++ b/crates/browser-use-tui/src/main.rs @@ -9077,8 +9077,15 @@ impl App { .live_url .as_deref() .or(state.browser.url.as_deref()) - .unwrap_or("about:blank") - .to_string() + .map(ToOwned::to_owned) + }; + // No placeholder fallback here: handing "about:blank" to the OS opener + // is never useful, and on macOS LaunchServices can fuzzy-match junk + // strings to application names ("about*" uniquely resolves to + // /System/Library/CoreServices/Applications/About This Mac.app). + let Some(target) = target else { + self.browser_notice = Some("No browser URL to open yet.".to_string()); + return Ok(()); }; self.request_open_browser_target(target) } @@ -10704,21 +10711,35 @@ fi | sh -s -- --no-launch .with_context(|| format!("download and run installer script {url}")) } -#[cfg(not(test))] -fn open_external_url(target: &str) -> Result<()> { +/// Validate a string before handing it to the OS URL opener (`/usr/bin/open` +/// on macOS). Only http(s)/file URLs are externally openable; browser-internal +/// placeholders like `about:blank` or `chrome://...` must never reach +/// LaunchServices — it cannot open them, and on macOS an unresolvable string +/// can be fuzzy-matched as an application name ("about*" uniquely resolves to +/// "About This Mac.app"), popping the About This Mac window mid-task. +fn external_browser_target(target: &str) -> Result<&str> { let target = target.trim(); if target.is_empty() { anyhow::bail!("browser target is empty"); } + let scheme = target + .split_once(':') + .map(|(scheme, _)| scheme.to_ascii_lowercase()); + match scheme.as_deref() { + Some("http" | "https" | "file") => Ok(target), + _ => anyhow::bail!("not an externally openable URL: {target}"), + } +} + +#[cfg(not(test))] +fn open_external_url(target: &str) -> Result<()> { + let target = external_browser_target(target)?; open::that_detached(target).with_context(|| format!("launch external browser for {target}")) } #[cfg(test)] fn open_external_url(target: &str) -> Result<()> { - if target.trim().is_empty() { - anyhow::bail!("browser target is empty"); - } - Ok(()) + external_browser_target(target).map(|_| ()) } #[cfg(not(test))] @@ -18972,6 +18993,8 @@ wire_api = "responses" fn browser_panel_actions_record_explicit_events() -> Result<()> { let temp = tempfile::tempdir()?; let mut app = ready_app(&temp)?; + app.browser = BROWSER_USE_CLOUD.to_string(); + app.store.set_setting("browser", BROWSER_USE_CLOUD)?; let session = app.store.create_session(None, std::env::current_dir()?)?; app.store.append_event( &session.id, @@ -18998,6 +19021,24 @@ wire_api = "responses" Ok(()) } + #[test] + fn external_browser_target_rejects_browser_internal_placeholders() { + // Only externally openable URLs may reach the OS opener. Passing + // browser-internal placeholders like about:blank to `open` on macOS can + // fuzzy-match an application name instead ("about*" resolves uniquely + // to About This Mac.app), popping About This Mac during agent runs. + assert!(external_browser_target("about:blank").is_err()); + assert!(external_browser_target("chrome://inspect/#remote-debugging").is_err()); + assert!(external_browser_target("").is_err()); + assert!(external_browser_target("javascript:alert(1)").is_err()); + assert_eq!( + external_browser_target(" https://live.browser-use.com/?wss=example ").unwrap(), + "https://live.browser-use.com/?wss=example" + ); + assert!(external_browser_target("file:///tmp/x/.capture.frames/live.html").is_ok()); + assert!(external_browser_target("http://127.0.0.1:9222/json/version").is_ok()); + } + #[test] fn browser_live_url_is_visible_in_browser_panel() -> Result<()> { let temp = tempfile::tempdir()?; From 78f26289bc9f4a80bedbd511a0b3fa5ff7b6b5d6 Mon Sep 17 00:00:00 2001 From: laithrw <70768382+laithrw@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:30:34 -0700 Subject: [PATCH 2/2] clean --- crates/browser-use-tui/src/main.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/browser-use-tui/src/main.rs b/crates/browser-use-tui/src/main.rs index adf5413e..46c3d221 100644 --- a/crates/browser-use-tui/src/main.rs +++ b/crates/browser-use-tui/src/main.rs @@ -9079,9 +9079,8 @@ impl App { .or(state.browser.url.as_deref()) .map(ToOwned::to_owned) }; - // No placeholder fallback here: handing "about:blank" to the OS opener - // is never useful, and on macOS LaunchServices can fuzzy-match junk - // strings to application names ("about*" uniquely resolves to + // No placeholder fallback here: on macOS LaunchServices, about:blank can fuzzy-match + // to application names (ex: "about" resolves to // /System/Library/CoreServices/Applications/About This Mac.app). let Some(target) = target else { self.browser_notice = Some("No browser URL to open yet.".to_string()); @@ -10711,12 +10710,10 @@ fi | sh -s -- --no-launch .with_context(|| format!("download and run installer script {url}")) } -/// Validate a string before handing it to the OS URL opener (`/usr/bin/open` -/// on macOS). Only http(s)/file URLs are externally openable; browser-internal +/// Validate a string before handing it to the OS URL opener +/// Only http(s)/file URLs are externally openable; browser-internal /// placeholders like `about:blank` or `chrome://...` must never reach -/// LaunchServices — it cannot open them, and on macOS an unresolvable string -/// can be fuzzy-matched as an application name ("about*" uniquely resolves to -/// "About This Mac.app"), popping the About This Mac window mid-task. +/// LaunchServices fn external_browser_target(target: &str) -> Result<&str> { let target = target.trim(); if target.is_empty() { @@ -19023,10 +19020,7 @@ wire_api = "responses" #[test] fn external_browser_target_rejects_browser_internal_placeholders() { - // Only externally openable URLs may reach the OS opener. Passing - // browser-internal placeholders like about:blank to `open` on macOS can - // fuzzy-match an application name instead ("about*" resolves uniquely - // to About This Mac.app), popping About This Mac during agent runs. + // Only externally openable URLs may reach the OS opener assert!(external_browser_target("about:blank").is_err()); assert!(external_browser_target("chrome://inspect/#remote-debugging").is_err()); assert!(external_browser_target("").is_err());