Compare commits

..
Author SHA1 Message Date
leeguooooo 6d740093dc chore(release): 0.27.0-fork.39 — cookies set --curl preserves full attributes
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
2026-06-11 21:09:04 +09:00
leeguooooo c884fb4f57 fix(cookies): preserve full cookie attributes in cookies set --curl JSON import
The JSON-array branch of parse_curl_cookies dropped every field except
name/value, so importing a full cookie export (httpOnly session tokens,
per-domain cookies spanning multiple hosts, secure/sameSite/expiry) could
not reconstruct a usable auth state — a single --domain override cannot
cover an export that spans .chatgpt.com, .openai.com, etc.

Pass through url/domain/path/secure/httpOnly/sameSite/expires when present,
accepting common aliases from DevTools / EditThisCookie exports
(http_only, same_site, no_restriction, expirationDate). Bare {name,value}
exports are unchanged. Added a round-trip test.
2026-06-11 21:09:04 +09:00
leeguooooo 57ef011817 docs: document humanize (human-like input) + silent operation
- README + README.zh: new Anti-detection subsections — "Human-like input
  (behavioural stealth)" (curved trajectories / jitter / cadence / eased
  scroll-drag, adaptive per-page escalation, off|fast|human) with the
  trajectory contrast table, and "Silent operation" (background tabs, no
  foreground stealing, focus-emulated). Added AGENT_BROWSER_HUMANIZE to the
  tuning-knobs table.
- skill core: agents told operation is silent by default and how/when to use
  --humanize (leave on auto; force human for known behavioural targets).
2026-06-11 21:04:35 +09:00
7 changed files with 136 additions and 4 deletions
+20
View File
@@ -227,6 +227,25 @@ When connected to your real Chrome, we inject **zero** JavaScript patches. Your
When using `--launch` mode (standalone browser), a full suite of stealth patches is applied instead, and it still passes the suite above. When using `--launch` mode (standalone browser), a full suite of stealth patches is applied instead, and it still passes the suite above.
### Human-like input (behavioural stealth)
Fingerprint stealth isn't the whole story — the strongest anti-bot vendors (Akamai, PerimeterX, DataDome) also score *behaviour*. A click that teleports the cursor to an element's exact centre with no approach path and zero press delay is a tell, **even though our CDP events are `isTrusted`**.
With humanize on, the cursor moves like a hand: clicks follow a curved, decelerating Bézier path and land on a jittered point *inside* the element (never the dead centre); typing uses variable inter-keystroke timing; scrolling eases in segments; drags follow a curve. It's **adaptive** — every navigation is probed for known anti-bot vendors (cookies / scripts / globals) and a guarded page auto-escalates to full human motion, while ordinary sites stay instant (zero overhead).
What the page's own `mousemove` stream sees (this *is* what a behavioural detector analyses):
| | trajectory |
|---|---|
| **off** (default) | straight lines · dead-centre · instant |
| **human** | curved trails · slow-in/slow-out · off-centre landings |
Control with `--humanize off\|fast\|human` or `AGENT_BROWSER_HUMANIZE`. Default `off`; the adaptive detector escalates per page.
### Silent operation
Driving your real Chrome should never interrupt your work. The agent operates **entirely in the background**: new tabs open un-focused (in their own colored per-session tab group), the agent **never force-fronts a tab**, and `Emulation.setFocusEmulationEnabled` keeps each agent tab rendering and reporting `document.hasFocus()` / `visibilityState: 'visible'`. So screenshots still work, pages aren't render-throttled, and "the tab was hidden the whole session" never becomes its own bot tell. You keep working in your active tab; the agent works alongside you, silently. (Surfacing a tab stays available as an explicit command.)
### Verify it yourself ### Verify it yourself
Don't take our word for it — point your connected Chrome at the toughest public detectors and compare: Don't take our word for it — point your connected Chrome at the toughest public detectors and compare:
@@ -244,6 +263,7 @@ We deliberately **don't ship our own bot detector** — the strongest, most hone
| Variable | Default | Effect | | Variable | Default | Effect |
|---|---|---| |---|---|---|
| `AGENT_BROWSER_CAPTURE_CONSOLE` | off | Enable `Runtime` domain so `console` / `errors` capture page output. Off keeps the stealthiest profile. | | `AGENT_BROWSER_CAPTURE_CONSOLE` | off | Enable `Runtime` domain so `console` / `errors` capture page output. Off keeps the stealthiest profile. |
| `AGENT_BROWSER_HUMANIZE` | off | Human-like input motion: `off` (instant), `fast` (light eased trajectory), `human` (full curved trajectory + landing jitter + typing cadence + eased scroll/drag). Also `--humanize`. Default `off`; the adaptive detector auto-escalates pages guarded by Akamai/PerimeterX/DataDome to `human`. |
| `AGENT_BROWSER_TIMEZONE` | unset | `--launch` only. An IANA id (e.g. `Asia/Tokyo`) sets the timezone natively (Intl + Date follow, no JS lie) to match a proxy; `auto` derives one from the locale. | | `AGENT_BROWSER_TIMEZONE` | unset | `--launch` only. An IANA id (e.g. `Asia/Tokyo`) sets the timezone natively (Intl + Date follow, no JS lie) to match a proxy; `auto` derives one from the locale. |
| `AGENT_BROWSER_BLOCK_WEBRTC` | auto | `--launch` only. Auto-forces WebRTC through the proxy when one is set (no real-IP leak). `1` hides the local IP without a proxy; `0` opts out. | | `AGENT_BROWSER_BLOCK_WEBRTC` | auto | `--launch` only. Auto-forces WebRTC through the proxy when one is set (no real-IP leak). `1` hides the local IP without a proxy; `0` opts out. |
| `AGENT_BROWSER_HIDE_CANVAS` | off | `--launch` only. Adds session-stable canvas/audio fingerprint noise. Off by default (noise is itself a "lie"). | | `AGENT_BROWSER_HIDE_CANVAS` | off | `--launch` only. Adds session-stable canvas/audio fingerprint noise. Off by default (noise is itself a "lie"). |
+19
View File
@@ -148,6 +148,25 @@ agent-browser --launch --profile auto open https://x.com/home
`--launch` 独立模式下会改用一整套隐身补丁,同样过上述检测。 `--launch` 独立模式下会改用一整套隐身补丁,同样过上述检测。
### 类人输入(行为隐身)
指纹隐身只是一半——最强的反爬厂商(Akamai、PerimeterX、DataDome)还会给**行为**打分。点击时光标瞬移到元素正中心、没有接近轨迹、按下即抬起,这本身就是破绽,**哪怕我们的 CDP 事件是 `isTrusted`**。
开启 humanize 后,光标像手在动:点击走带减速的贝塞尔曲线、落在元素内**偏离正中心**的抖动点;打字用变速的击键间隔;滚动分段缓动;拖拽走曲线。而且**自适应**——每次导航探测页面是否有已知反爬厂商(cookie/脚本/全局变量),命中就自动升到全套类人动作,普通站点保持瞬时(零开销)。
页面自己的 `mousemove` 流看到的(行为检测器分析的正是这个):
| | 轨迹 |
|---|---|
| **off**(默认) | 直线 · 死磕正中心 · 瞬时 |
| **human** | 曲线 · 先慢后快再慢 · 落点偏移 |
`--humanize off\|fast\|human``AGENT_BROWSER_HUMANIZE` 控制。默认 `off`,自适应检测器按页面自动升档。
### 静默操作
操作你的真实 Chrome 不该打断你的工作。agent **全程在后台操作**:新标签后台打开(在自己的彩色会话标签组里),**从不强制把标签拽到前台**,并用 `Emulation.setFocusEmulationEnabled` 让每个 agent 标签照常渲染、`document.hasFocus()` / `visibilityState` 仍报 `visible`。于是截图正常、页面不被降频,"标签全程隐藏"也不会变成新的机器人信号。你在自己的标签里照常工作,agent 在旁边默默干活。(想置顶某个标签仍可显式调用命令。)
## 与上游的差异 ## 与上游的差异
基于 [agent-browser v0.27.0](https://github.com/vercel-labs/agent-browser) 基于 [agent-browser v0.27.0](https://github.com/vercel-labs/agent-browser)
+1 -1
View File
@@ -45,7 +45,7 @@ dependencies = [
[[package]] [[package]]
name = "agent-browser-stealth" name = "agent-browser-stealth"
version = "0.27.0-fork.38" version = "0.27.0-fork.39"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"async-trait", "async-trait",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "agent-browser-stealth" name = "agent-browser-stealth"
version = "0.27.0-fork.38" version = "0.27.0-fork.39"
edition = "2021" edition = "2021"
description = "Fast browser automation CLI for AI agents" description = "Fast browser automation CLI for AI agents"
license = "Apache-2.0" license = "Apache-2.0"
+80 -1
View File
@@ -101,7 +101,62 @@ pub fn parse_curl_cookies(raw: &str) -> Result<Vec<Value>, String> {
.get("value") .get("value")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.ok_or_else(|| format!("cookies[{}] missing string value", i))?; .ok_or_else(|| format!("cookies[{}] missing string value", i))?;
out.push(json!({ "name": name, "value": value })); let mut cookie = json!({ "name": name, "value": value });
let obj = cookie.as_object_mut().unwrap();
// Preserve any CDP Network.setCookie attributes present on the
// source object so a full auth state round-trips: httpOnly session
// tokens, per-domain cookies (a single export spans .chatgpt.com,
// .openai.com, ...), and secure/sameSite/expiry. A bare
// {name,value} export is unchanged. Common aliases from DevTools /
// EditThisCookie / extension exports are accepted.
if let Some(v) = c.get("url").and_then(|v| v.as_str()) {
obj.insert("url".into(), json!(v));
}
if let Some(v) = c.get("domain").and_then(|v| v.as_str()) {
obj.insert("domain".into(), json!(v));
}
if let Some(v) = c.get("path").and_then(|v| v.as_str()) {
obj.insert("path".into(), json!(v));
}
if let Some(v) = c.get("secure").and_then(|v| v.as_bool()) {
obj.insert("secure".into(), json!(v));
}
if let Some(v) = c
.get("httpOnly")
.or_else(|| c.get("httponly"))
.or_else(|| c.get("http_only"))
.and_then(|v| v.as_bool())
{
obj.insert("httpOnly".into(), json!(v));
}
if let Some(v) = c
.get("sameSite")
.or_else(|| c.get("samesite"))
.or_else(|| c.get("same_site"))
.and_then(|v| v.as_str())
{
let norm = match v.to_lowercase().as_str() {
"strict" => "Strict",
"lax" => "Lax",
"none" | "no_restriction" => "None",
_ => "",
};
if !norm.is_empty() {
obj.insert("sameSite".into(), json!(norm));
}
}
// CDP `expires` is seconds since the Unix epoch (f64). Accept
// `expires` or EditThisCookie's `expirationDate`.
if let Some(v) = c
.get("expires")
.or_else(|| c.get("expirationDate"))
.and_then(|v| v.as_f64())
{
if v > 0.0 {
obj.insert("expires".into(), json!(v));
}
}
out.push(cookie);
} }
return Ok(out); return Ok(out);
} }
@@ -2859,6 +2914,27 @@ mod tests {
assert_eq!(cmd["action"], "cookies_clear"); assert_eq!(cmd["action"], "cookies_clear");
} }
#[test]
fn test_parse_curl_cookies_json_preserves_attributes() {
// A full cookie export (httpOnly session token, per-domain, secure,
// sameSite, expiry) must round-trip — not get flattened to name/value.
let input = r#"[
{"name":"__Secure-next-auth.session-token","value":"eyJ.tok","domain":".chatgpt.com","path":"/","secure":true,"httpOnly":true,"sameSite":"Lax","expires":1893456000},
{"name":"cf_clearance","value":"abc","domain":".openai.com","path":"/","secure":true,"http_only":true,"same_site":"no_restriction"}
]"#;
let out = parse_curl_cookies(input).unwrap();
assert_eq!(out.len(), 2);
assert_eq!(out[0]["domain"], ".chatgpt.com");
assert_eq!(out[0]["secure"], true);
assert_eq!(out[0]["httpOnly"], true);
assert_eq!(out[0]["sameSite"], "Lax");
assert_eq!(out[0]["expires"], 1893456000.0);
// alias keys (http_only, same_site=no_restriction) normalize to CDP shape
assert_eq!(out[1]["domain"], ".openai.com");
assert_eq!(out[1]["httpOnly"], true);
assert_eq!(out[1]["sameSite"], "None");
}
#[test] #[test]
fn test_parse_curl_cookies_json_array() { fn test_parse_curl_cookies_json_array() {
let input = r#"[{"name":"a","value":"1"},{"name":"b","value":"2"}]"#; let input = r#"[{"name":"a","value":"1"},{"name":"b","value":"2"}]"#;
@@ -2866,6 +2942,9 @@ mod tests {
assert_eq!(out.len(), 2); assert_eq!(out.len(), 2);
assert_eq!(out[0]["name"], "a"); assert_eq!(out[0]["name"], "a");
assert_eq!(out[0]["value"], "1"); assert_eq!(out[0]["value"], "1");
// bare {name,value} stays minimal — no spurious attribute keys
assert!(out[0].get("domain").is_none());
assert!(out[0].get("secure").is_none());
assert_eq!(out[1]["name"], "b"); assert_eq!(out[1]["name"], "b");
assert_eq!(out[1]["value"], "2"); assert_eq!(out[1]["value"], "2");
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "agent-browser-stealth", "name": "agent-browser-stealth",
"version": "0.27.0-fork.38", "version": "0.27.0-fork.39",
"description": "Browser automation CLI for AI agents — stealth fork with anti-detection", "description": "Browser automation CLI for AI agents — stealth fork with anti-detection",
"type": "module", "type": "module",
"packageManager": "pnpm@11.1.3", "packageManager": "pnpm@11.1.3",
+14
View File
@@ -102,6 +102,20 @@ connect) > a headed launched browser > headless (forbidden).** A genuine human
browser has no headless/automation tells at all, so prefer it for anything browser has no headless/automation tells at all, so prefer it for anything
anti-bot-sensitive. anti-bot-sensitive.
**Silent by default.** When driving the user's real Chrome the agent works
entirely in the background — new tabs open un-focused, the agent never force-
fronts a tab, and focus is emulated so the page still renders and reports
`visibilityState: 'visible'`. You don't need to do anything; just don't expect
the user's view to follow you (use the explicit `bringToFront` only if you
deliberately want to surface a tab).
**Human-like input for behavioural anti-bot.** Beyond fingerprint stealth,
`--humanize off|fast|human` (or `AGENT_BROWSER_HUMANIZE`) makes clicks follow a
curved, decelerating path with in-element landing jitter, typing use variable
cadence, and scroll/drag ease. Default `off`; a per-navigation detector
auto-escalates pages guarded by Akamai/PerimeterX/DataDome to `human`. Leave it
on auto; force `human` only when you already know the target scores behaviour.
## Two ways to drive a page — and when to drop to `eval` ## Two ways to drive a page — and when to drop to `eval`
You have a **real Chrome with the user's DOM**. Two layers, mix them freely: You have a **real Chrome with the user's DOM**. Two layers, mix them freely: