chore(release): 0.27.0-fork.29 — plugin overflowTest fix, popup-free auto-connect, ab-connect rebrand+icon, README
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled

- stealth(plugins): stop overwriting real native navigator.plugins in headed
  mode (the JS fake had a non-native item(), broken uint32 wrap → incolumitas
  overflowTest FAIL, and an anachronistic Native Client plugin). Leave native
  plugins untouched when present; modernize the headless-escape fallback to the
  real 5 PDF-viewer set with masked-native item()/namedItem().
- connect: auto_connect_cdp() now prefers the dialog-free ab-connect relay over
  the raw :9222 CDP port, so Chrome 136+'s "Allow remote debugging?" consent
  modal no longer fires when the extension relay is live. Gated by a bare-TCP
  relay_is_live() probe (+3 unit tests).
- extension: rename ab-connect to "agent-browser-stealth" + new stealth icon set
  (16/32/48/128).
- docs(README): hero/shield/fingerprint images, expanded detector results
  (CreepJS 0% stealth, incolumitas all-OK, BrowserScan CDP-clean), and a
  "Verify it yourself" section. .gitignore: allow assets/ + extension icons.
This commit is contained in:
leeguooooo
2026-06-10 11:17:41 +09:00
parent a7f9c24fdb
commit 839aaa5586
15 changed files with 152 additions and 27 deletions
+67
View File
@@ -773,6 +773,26 @@ fn running_process_cmdlines() -> Option<Vec<String>> {
}
pub async fn auto_connect_cdp() -> Result<String, String> {
// Prefer the dialog-free `ab-connect` extension relay when it is live.
// The relay drives the user's REAL Chrome via the extension's
// `chrome.debugger` permission, which — unlike a raw `--remote-debugging-port`
// CDP attach — never triggers Chrome 136+'s per-connection
// "Allow remote debugging?" consent modal. The native-messaging host writes
// ~/.agent-browser/relay-cdp-url while connected and removes it on exit, so a
// present URL means the relay is up. This must win over the DevToolsActivePort
// / :9222 probes below: if the user's Chrome happens to also be listening on a
// debug port, attaching there would pop the consent dialog and defeat the
// whole zero-interaction extension path.
if let Some(relay) = crate::connect::relay_url() {
// The relay is a local CDP-over-WS endpoint we connect to like Chrome.
// A bare TCP liveness check (no WS upgrade) confirms it is actually
// accepting before we commit, mirroring the consent-free probe used for
// DevToolsActivePort.
if relay_is_live(&relay).await {
return Ok(relay);
}
}
let user_data_dirs = get_chrome_user_data_dirs();
for dir in &user_data_dirs {
@@ -850,6 +870,22 @@ async fn tcp_port_alive(port: u16) -> bool {
)
}
/// Consent-free liveness for the `ab-connect` relay ws URL (`ws://127.0.0.1:<port>/…`).
/// Parses the port and does a bare TCP connect — a stale relay-cdp-url file
/// (host exited without cleanup) must not divert auto-connect away from the
/// working port path.
async fn relay_is_live(ws_url: &str) -> bool {
let port = ws_url
.strip_prefix("ws://")
.and_then(|rest| rest.split('/').next())
.and_then(|hostport| hostport.rsplit(':').next())
.and_then(|p| p.parse::<u16>().ok());
match port {
Some(p) => tcp_port_alive(p).await,
None => false,
}
}
/// Returns the default Chrome user-data directory paths for the current platform.
/// Includes Chrome, Chrome Canary, Chromium, and Brave.
pub fn get_chrome_user_data_dirs() -> Vec<PathBuf> {
@@ -2200,4 +2236,35 @@ mod tests {
let result = resolve_cdp_from_active_port(port, "/devtools/browser/dead").await;
assert!(result.is_err(), "should fail when nothing is listening");
}
#[tokio::test]
async fn test_relay_is_live_true_when_listening() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
let url = format!("ws://127.0.0.1:{}/abc-guid", port);
assert!(
relay_is_live(&url).await,
"relay_is_live should be true while the port is accepting"
);
}
#[tokio::test]
async fn test_relay_is_live_false_when_dead() {
// Bind to grab a free port, then drop so nothing is listening.
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
drop(listener);
let url = format!("ws://127.0.0.1:{}/abc-guid", port);
assert!(
!relay_is_live(&url).await,
"relay_is_live must be false for a stale relay-cdp-url (host exited)"
);
}
#[tokio::test]
async fn test_relay_is_live_false_on_malformed_url() {
assert!(!relay_is_live("not-a-ws-url").await);
assert!(!relay_is_live("ws://127.0.0.1/no-port").await);
assert!(!relay_is_live("ws://127.0.0.1:notaport/x").await);
}
}