fix(browser): auto-remove macOS Gatekeeper quarantine — prevent SIGKILL
Root cause: On Apple Silicon macOS, the Camoufox browser binary inherits com.apple.quarantine from npm download. When camoufox-browser launches it headlessly, Gatekeeper silently kills the process with SIGKILL (exitCode=null, signal=SIGKILL) before it can run. Fix: 1. remove_macos_quarantine() — runs 'xattr -dr com.apple.quarantine' on the Camoufox cache directory after every fetch and on every subsequent startup (in case cache was from a prior npx run) 2. check_known_errors() — detects 'sigkill' in daemon stderr and shows the exact fix command to the user 3. Runs quarantine removal in TWO places: - Immediately after camoufox-js fetch downloads the binary - Always, even when binary already exists in cache
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
//! - Both packages share the same cache directory (~/Library/Caches/camoufox/).
|
||||
//! We use the pinned `camoufox-js` for fetch, `camoufox-browser` for daemon.
|
||||
|
||||
use std::process::Stdio;
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::process::Command;
|
||||
@@ -95,9 +96,38 @@ fn check_known_errors(log: &str) -> Option<&'static str> {
|
||||
if lower.contains("error while loading shared libraries") {
|
||||
return Some("Missing Linux system libraries.");
|
||||
}
|
||||
if lower.contains("sigkill") {
|
||||
return Some("macOS Gatekeeper killed the browser (SIGKILL). Run: xattr -dr com.apple.quarantine ~/Library/Caches/camoufox");
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Remove macOS quarantine attribute from the Camoufox cache directory.
|
||||
/// Gatekeeper kills unsigned headless browsers with SIGKILL on Apple Silicon.
|
||||
async fn remove_macos_quarantine() {
|
||||
if !cfg!(target_os = "macos") {
|
||||
return;
|
||||
}
|
||||
let cache = browser_cache_dir();
|
||||
if !cache.exists() {
|
||||
return;
|
||||
}
|
||||
let output = Command::new("xattr")
|
||||
.args(&["-dr", "com.apple.quarantine", &cache.to_string_lossy()])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.output()
|
||||
.await;
|
||||
match output {
|
||||
Ok(o) if o.status.success() => {
|
||||
info!("Removed macOS quarantine from {}", cache.display());
|
||||
}
|
||||
_ => {
|
||||
warn!("Could not remove quarantine (xattr not available or failed)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the browser daemon and return a ready client.
|
||||
///
|
||||
/// Steps:
|
||||
@@ -122,7 +152,11 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
if !is_browser_installed() {
|
||||
info!("Downloading Camoufox browser using camoufox-js@{}...", CAMOUFOX_JS_VERSION);
|
||||
match run_fetch(npx, CAMOUFOX_JS_VERSION).await {
|
||||
Ok(msg) => info!("Camoufox browser fetch: {}", msg),
|
||||
Ok(msg) => {
|
||||
info!("Camoufox browser fetch: {}", msg);
|
||||
// On macOS, remove Gatekeeper quarantine immediately after download
|
||||
remove_macos_quarantine().await;
|
||||
}
|
||||
Err(e) => {
|
||||
return BrowserInitResult::Unavailable(format!(
|
||||
"Failed to download Camoufox browser: {}\n\n\
|
||||
@@ -133,6 +167,10 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
}
|
||||
}
|
||||
|
||||
// On macOS, always try to remove Gatekeeper quarantine from cached binary
|
||||
// (even if it was already installed — prior npx downloads may have set it)
|
||||
remove_macos_quarantine().await;
|
||||
|
||||
// 3. Ensure the `camoufox-browser` npm package is globally installed for daemon
|
||||
if !is_npm_package_installed(npx, "camoufox-browser", CAMOUFOX_BROWSER_VERSION).await {
|
||||
info!("Installing camoufox-browser@{} (daemon)...", CAMOUFOX_BROWSER_VERSION);
|
||||
|
||||
Reference in New Issue
Block a user