Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions crates/browser-use-tui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9077,8 +9077,14 @@ 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: 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());
return Ok(());
};
self.request_open_browser_target(target)
}
Expand Down Expand Up @@ -10704,21 +10710,33 @@ 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
/// Only http(s)/file URLs are externally openable; browser-internal
/// placeholders like `about:blank` or `chrome://...` must never reach
/// LaunchServices
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))]
Expand Down Expand Up @@ -18972,6 +18990,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,
Expand All @@ -18998,6 +19018,21 @@ wire_api = "responses"
Ok(())
}

#[test]
fn external_browser_target_rejects_browser_internal_placeholders() {
// 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());
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()?;
Expand Down
Loading