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/).
|
//! - Both packages share the same cache directory (~/Library/Caches/camoufox/).
|
||||||
//! We use the pinned `camoufox-js` for fetch, `camoufox-browser` for daemon.
|
//! We use the pinned `camoufox-js` for fetch, `camoufox-browser` for daemon.
|
||||||
|
|
||||||
|
use std::process::Stdio;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use tokio::process::Command;
|
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") {
|
if lower.contains("error while loading shared libraries") {
|
||||||
return Some("Missing Linux system 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
|
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.
|
/// Initialize the browser daemon and return a ready client.
|
||||||
///
|
///
|
||||||
/// Steps:
|
/// Steps:
|
||||||
@@ -122,7 +152,11 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
|||||||
if !is_browser_installed() {
|
if !is_browser_installed() {
|
||||||
info!("Downloading Camoufox browser using camoufox-js@{}...", CAMOUFOX_JS_VERSION);
|
info!("Downloading Camoufox browser using camoufox-js@{}...", CAMOUFOX_JS_VERSION);
|
||||||
match run_fetch(npx, CAMOUFOX_JS_VERSION).await {
|
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) => {
|
Err(e) => {
|
||||||
return BrowserInitResult::Unavailable(format!(
|
return BrowserInitResult::Unavailable(format!(
|
||||||
"Failed to download Camoufox browser: {}\n\n\
|
"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
|
// 3. Ensure the `camoufox-browser` npm package is globally installed for daemon
|
||||||
if !is_npm_package_installed(npx, "camoufox-browser", CAMOUFOX_BROWSER_VERSION).await {
|
if !is_npm_package_installed(npx, "camoufox-browser", CAMOUFOX_BROWSER_VERSION).await {
|
||||||
info!("Installing camoufox-browser@{} (daemon)...", CAMOUFOX_BROWSER_VERSION);
|
info!("Installing camoufox-browser@{} (daemon)...", CAMOUFOX_BROWSER_VERSION);
|
||||||
|
|||||||
Reference in New Issue
Block a user