feat(stealth): inject anti-detection patches in native Rust architecture

- Created cli/src/native/stealth.rs with stealth JS injection via CDP
- Extracted 32 patch IIFEs from TS stealth.ts into stealth_scripts.js
- Injected via Page.addScriptToEvaluateOnNewDocument on every launch/connect
- Added stealth Chrome args (disable AutomationControlled, use ANGLE GL)
- Auto-detects and cleans HeadlessChrome from User-Agent string
- Overrides navigator.userAgentData high-entropy hints
- Stealth enabled by default, disable with AGENT_BROWSER_STEALTH=0

Track 2 of native-stealth migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leeguooooo
2026-05-08 23:47:50 +09:00
co-authored by Claude Opus 4.6
parent 6addc80aa1
commit 77616a209c
7 changed files with 1515 additions and 0 deletions
+35
View File
@@ -33,6 +33,7 @@ use super::recording::{self, RecordingState};
use super::screenshot::{self, ScreenshotOptions};
use super::snapshot::{self, SnapshotOptions};
use super::state;
use super::stealth;
use super::storage;
use super::stream::{self, StreamServer};
use super::tracing::{self as native_tracing, TracingState};
@@ -1550,6 +1551,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
apply_launch_init_scripts(state).await;
try_auto_restore_state(state).await;
try_load_storage_state(state, &storage_state_path).await;
apply_stealth_to_browser(state).await;
return Ok(());
}
@@ -1563,6 +1565,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
apply_launch_init_scripts(state).await;
try_auto_restore_state(state).await;
try_load_storage_state(state, &storage_state_path).await;
apply_stealth_to_browser(state).await;
return Ok(());
}
@@ -1633,6 +1636,8 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
apply_launch_init_scripts(state).await;
try_auto_restore_state(state).await;
try_load_storage_state(state, &storage_state_path).await;
// Apply stealth anti-detection patches after browser is ready
apply_stealth_to_browser(state).await;
Ok(())
}
@@ -1683,6 +1688,36 @@ async fn apply_launch_init_scripts(state: &DaemonState) {
}
}
/// Inject stealth scripts into the active browser session.
/// Called after every successful launch / CDP connect / auto-connect.
async fn apply_stealth_to_browser(state: &DaemonState) {
if env::var("AGENT_BROWSER_STEALTH").map(|v| v == "0").unwrap_or(false) {
return; // Explicitly disabled
}
let Some(ref mgr) = state.browser else {
return;
};
let Ok(session_id) = mgr.active_session_id() else {
return;
};
let locale = env::var("AGENT_BROWSER_LOCALE").ok();
if let Err(e) = stealth::apply_stealth(
&mgr.client,
session_id,
locale.as_deref(),
)
.await
{
eprintln!("[stealth] Failed to apply stealth patches: {}", e);
}
// Also inject into the current page (already loaded before our init script)
if let Err(e) =
stealth::apply_stealth_to_current_page(&mgr.client, session_id, locale.as_deref()).await
{
eprintln!("[stealth] Failed to patch current page: {}", e);
}
}
fn launch_options_from_env() -> LaunchOptions {
let headed = env::var("AGENT_BROWSER_HEADED")
.map(|v| v == "1" || v == "true")