full native (#754)

* full native

* fix: apply cargo fmt formatting

* fix: prevent zip path traversal in Chromium installer

Use enclosed_name() to sanitize zip entry paths, preventing malicious
archives from writing outside the extraction directory.

* improvements

* fix: apply cargo fmt formatting

* benchmarks

* bench

* updates

* fixes
This commit is contained in:
Chris Tate
2026-03-13 19:59:21 -05:00
committed by GitHub
parent d4b948c1d4
commit 8e43469c8b
88 changed files with 2511 additions and 22629 deletions
+35 -119
View File
@@ -233,7 +233,6 @@ pub struct DaemonOptions<'a> {
pub allowed_domains: Option<&'a [String]>,
pub action_policy: Option<&'a str>,
pub confirm_actions: Option<&'a str>,
pub native: bool,
pub engine: Option<&'a str>,
}
@@ -359,137 +358,54 @@ pub fn ensure_daemon(session: &str, opts: &DaemonOptions) -> Result<DaemonResult
}
let exe_path = env::current_exe().map_err(|e| e.to_string())?;
// Canonicalize to resolve symlinks (e.g., npm global bin symlink -> actual binary)
let exe_path = exe_path.canonicalize().unwrap_or(exe_path);
// On Windows, canonicalize() returns \\?\ prefixed extended-length paths.
// Node.js cannot handle these, so strip the prefix.
#[cfg(windows)]
let exe_path = {
let p = exe_path.to_string_lossy();
if let Some(stripped) = p.strip_prefix(r"\\?\") {
PathBuf::from(stripped)
} else {
exe_path
}
};
#[allow(unused_assignments)]
let mut daemon_child: Option<std::process::Child> = None;
if opts.native {
// Native mode: spawn self as daemon (Rust/CDP, no Node.js needed)
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let mut cmd = Command::new(&exe_path);
cmd.env("AGENT_BROWSER_DAEMON", "1");
apply_daemon_env(&mut cmd, session, opts);
let mut cmd = Command::new(&exe_path);
cmd.env("AGENT_BROWSER_DAEMON", "1");
apply_daemon_env(&mut cmd, session, opts);
unsafe {
cmd.pre_exec(|| {
libc::setsid();
Ok(())
});
}
daemon_child = Some(
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to start native daemon: {}", e))?,
);
unsafe {
cmd.pre_exec(|| {
libc::setsid();
Ok(())
});
}
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
daemon_child = Some(
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to start daemon: {}", e))?,
);
}
let mut cmd = Command::new(&exe_path);
cmd.env("AGENT_BROWSER_DAEMON", "1");
apply_daemon_env(&mut cmd, session, opts);
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
const DETACHED_PROCESS: u32 = 0x00000008;
let mut cmd = Command::new(&exe_path);
cmd.env("AGENT_BROWSER_DAEMON", "1");
apply_daemon_env(&mut cmd, session, opts);
daemon_child = Some(
cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to start native daemon: {}", e))?,
);
}
} else {
// Default mode: spawn Node.js daemon (Playwright)
let exe_dir = exe_path.parent().unwrap();
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
const DETACHED_PROCESS: u32 = 0x00000008;
let mut daemon_paths = vec![
exe_dir.join("daemon.js"),
exe_dir.join("../dist/daemon.js"),
PathBuf::from("dist/daemon.js"),
];
if let Ok(home) = env::var("AGENT_BROWSER_HOME") {
let home_path = PathBuf::from(&home);
daemon_paths.insert(0, home_path.join("dist/daemon.js"));
daemon_paths.insert(1, home_path.join("daemon.js"));
}
let daemon_path = daemon_paths
.iter()
.find(|p| p.exists())
.ok_or("Daemon not found. Set AGENT_BROWSER_HOME environment variable or run from project directory.")?;
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let mut cmd = Command::new("node");
cmd.arg(daemon_path);
apply_daemon_env(&mut cmd, session, opts);
unsafe {
cmd.pre_exec(|| {
libc::setsid();
Ok(())
});
}
daemon_child = Some(
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to start daemon: {}", e))?,
);
}
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
// Use node.exe explicitly to avoid Git Bash/MSYS2 shell wrapper resolution
let mut cmd = Command::new("node.exe");
cmd.arg(daemon_path)
.env("MSYS_NO_PATHCONV", "1")
.env("MSYS2_ARG_CONV_EXCL", "*");
apply_daemon_env(&mut cmd, session, opts);
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
const DETACHED_PROCESS: u32 = 0x00000008;
daemon_child = Some(
cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to start daemon: {}", e))?,
);
}
daemon_child = Some(
cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to start daemon: {}", e))?,
);
}
for _ in 0..50 {