fix(stealth): neutralize disable-devtool auto bootstrap
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
# agent-browser
|
||||
|
||||
## 0.16.1-fork.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Harden runtime stealth against anti-debug self-destruct flows on high-risk sites:
|
||||
- neutralize `disable-devtool` auto bootstrap probes by hiding the `[disable-devtool-auto]` selector entry point
|
||||
- preserve normal selector behavior for non-target queries to minimize side effects
|
||||
- add regression tests covering the selector patch boundary
|
||||
- Expand security design docs with the anti-debug execution-plane model and clarify why page self-close/redirect is a separate surface from fingerprint scoring.
|
||||
|
||||
## 0.15.2-fork.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "agent-browser-stealth"
|
||||
version = "0.16.1-fork.4"
|
||||
version = "0.16.1-fork.5"
|
||||
edition = "2021"
|
||||
description = "Stealth browser automation CLI for AI agents with anti-bot evasions"
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -109,6 +109,27 @@ flowchart TD
|
||||
|
||||
> 执行链路保护优先于信号修饰。
|
||||
|
||||
### 1.6 反调试执行面:与指纹评分面并行
|
||||
|
||||
很多站点并不只依赖指纹评分,还会部署“主动处置型反调试”脚本。
|
||||
|
||||
典型路径:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["页面启动"] --> B["反调试探测"]
|
||||
B --> C{"命中?"}
|
||||
C -->|是| D["close/back/redirect"]
|
||||
C -->|否| E["继续业务逻辑"]
|
||||
```
|
||||
|
||||
该路径与指纹评分面的关系:
|
||||
|
||||
1. 指纹评分决定“挑战/放行/降权”
|
||||
2. 反调试处置决定“页面是否继续可用”
|
||||
|
||||
因此,“页面自关闭”不能直接推断为“指纹被识别”,更常见是反调试链路触发。
|
||||
|
||||
---
|
||||
|
||||
## 二、控制面(分层设计)
|
||||
@@ -157,6 +178,18 @@ flowchart LR
|
||||
- 优先治理高频、可解释的探测路径
|
||||
- 对跨域挑战链路对象设置严格注入边界
|
||||
|
||||
#### 2.4.1 反调试脚本治理(以 disable-devtool 类库为例)
|
||||
|
||||
反调试脚本通常通过固定启动入口触发(例如 `disable-devtool-auto` 标记)。
|
||||
|
||||
可行控制策略:
|
||||
|
||||
1. 仅抑制其自动启动入口,避免触发主动处置
|
||||
2. 不改写通用查询/脚本加载语义,避免影响业务页面
|
||||
3. 将治理范围限制在高置信度触发点,控制副作用面
|
||||
|
||||
这类策略的本质是“执行面隔离”,不是“伪造更多指纹”。
|
||||
|
||||
### 2.5 行为与会话控制
|
||||
|
||||
目标:塑形时间分布,减少上下文漂移。
|
||||
@@ -183,7 +216,7 @@ Turnstile 场景下的关键控制面可抽象为:
|
||||
|
||||
控制面设计通常按以下优先级推进:
|
||||
|
||||
1. 执行完整性(保证链路可运行)
|
||||
1. 执行完整性(保证链路可运行,包含反调试触发面治理)
|
||||
2. 一致性约束集合(消除跨表面矛盾)
|
||||
3. 稀有性控制(避免低频组合叠加)
|
||||
4. 时序分布塑形(降低机械统计特征)
|
||||
|
||||
@@ -76,7 +76,21 @@ flowchart TD
|
||||
|
||||
风险评分的关键不是“拟合某种固定画像”,而是“同一身份在多表面是否自洽”。
|
||||
|
||||
### 1.5 作用域绑定:hostname / action / cdata
|
||||
### 1.5 反调试脚本是独立攻击面,不等同于指纹命中
|
||||
|
||||
在高风控站点中,常见一类“主动防御脚本”(例如 `disable-devtool`):
|
||||
|
||||
1. 页面启动后检测开发者工具、控制台钩子、调试停顿特征
|
||||
2. 命中后执行主动处置(`window.close` / `history.back` / 跳转错误页)
|
||||
|
||||
这条链路的关键点:
|
||||
|
||||
- 它是“反调试执行面”,不是“指纹评分面”的简单子集
|
||||
- 会表现为“页面自关闭/自跳转”,但根因可能是运行时反调试触发
|
||||
|
||||
工程上必须将其与“指纹问题”分层处理,否则容易误判。
|
||||
|
||||
### 1.6 作用域绑定:hostname / action / cdata
|
||||
|
||||
服务端校验时提供用于绑定业务语义的字段:
|
||||
|
||||
@@ -96,7 +110,7 @@ flowchart LR
|
||||
D --> G["降低跨流程重放收益"]
|
||||
```
|
||||
|
||||
### 1.6 Token 状态机(能力令牌视角)
|
||||
### 1.7 Token 状态机(能力令牌视角)
|
||||
|
||||
从能力令牌视角,token 生命周期可抽象为:
|
||||
|
||||
@@ -114,7 +128,7 @@ stateDiagram-v2
|
||||
|
||||
设计目标是让“非法路径”快速失败,并且失败类型可被服务端语义区分。
|
||||
|
||||
### 1.7 攻击树(高层)
|
||||
### 1.8 攻击树(高层)
|
||||
|
||||
Turnstile 的主要攻击目标可以抽象为:
|
||||
|
||||
@@ -171,6 +185,12 @@ flowchart LR
|
||||
- “执行完整性”是上游条件
|
||||
- “信号修饰”是下游优化
|
||||
|
||||
补充:反调试脚本属于执行控制的一部分。
|
||||
|
||||
- 若页面含有主动防御脚本(如 `disable-devtool-auto` 入口),需要把“自动触发入口”与“正常业务脚本”分开处理
|
||||
- 最小化目标是阻断其自动化处置链路,避免误伤挑战脚本/跨域组件
|
||||
- 处理边界应是“只抑制高风险触发入口,不重写通用 DOM 语义”
|
||||
|
||||
### 2.3 服务端消费控制:把 token 当作能力消费
|
||||
|
||||
服务端消费控制的设计关键在于“放行条件定义”,而不是“接口调用细节”。
|
||||
@@ -228,7 +248,7 @@ Pre-clearance 通过后可产生 clearance,用于后续 WAF 挑战联动。
|
||||
Turnstile 攻防设计通常按以下优先级推进:
|
||||
|
||||
1. 服务端消费语义闭环(真实性 + 作用域 + 绑定 + 单次性 + 时效性)
|
||||
2. 挑战执行链路完整性(跨域语义保护)
|
||||
2. 挑战执行链路完整性(跨域语义保护 + 反调试触发面治理)
|
||||
3. 信号一致性(减少跨字段矛盾)
|
||||
4. 行为时序(降低机械分布)
|
||||
5. 体验优化(clearance 等摩擦控制)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "agent-browser-stealth",
|
||||
"version": "0.16.1-fork.4",
|
||||
"version": "0.16.1-fork.5",
|
||||
"description": "Stealth browser automation CLI for AI agents with anti-bot evasions",
|
||||
"type": "module",
|
||||
"main": "dist/daemon.js",
|
||||
|
||||
@@ -126,6 +126,34 @@ describe('Stealth mode', () => {
|
||||
expect(signals.inNavigator).toBe(false);
|
||||
});
|
||||
|
||||
it('hides disable-devtool auto bootstrap selector while keeping normal selectors intact', async () => {
|
||||
browser = new BrowserManager();
|
||||
await browser.launch({ headless: true, stealth: true });
|
||||
|
||||
const signals = await browser.getPage().evaluate(() => {
|
||||
const marker = document.createElement('meta');
|
||||
marker.setAttribute('data-ab-marker', 'ok');
|
||||
document.head.appendChild(marker);
|
||||
|
||||
const dd = document.createElement('script');
|
||||
dd.setAttribute('disable-devtool-auto', '');
|
||||
document.head.appendChild(dd);
|
||||
|
||||
const hiddenOne = document.querySelector('[disable-devtool-auto]') === null;
|
||||
const hiddenCount = document.querySelectorAll('[disable-devtool-auto]').length;
|
||||
const normalSelectorWorks = !!document.querySelector('[data-ab-marker="ok"]');
|
||||
|
||||
dd.remove();
|
||||
marker.remove();
|
||||
|
||||
return { hiddenOne, hiddenCount, normalSelectorWorks };
|
||||
});
|
||||
|
||||
expect(signals.hiddenOne).toBe(true);
|
||||
expect(signals.hiddenCount).toBe(0);
|
||||
expect(signals.normalSelectorWorks).toBe(true);
|
||||
});
|
||||
|
||||
it('neutralizes creepjs prefers-color-scheme light probe', async () => {
|
||||
browser = new BrowserManager();
|
||||
await browser.launch({ headless: true, stealth: true });
|
||||
|
||||
@@ -292,6 +292,7 @@ function buildStealthScript(options: StealthScriptOptions): string {
|
||||
configScript,
|
||||
patchNavigatorWebdriver(),
|
||||
patchCssSupportsWebdriverHeuristic(),
|
||||
patchDisableDevtoolAutoBootstrap(),
|
||||
patchChromeRuntime(),
|
||||
patchChromeLegacyApis(),
|
||||
patchIframeContentWindow(),
|
||||
@@ -366,6 +367,65 @@ function patchCssSupportsWebdriverHeuristic(): string {
|
||||
})();`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some high-risk sites ship disable-devtool with auto bootstrap via
|
||||
* document.querySelector('[disable-devtool-auto]').
|
||||
*
|
||||
* Hiding only this selector prevents the default self-close/redirect behavior
|
||||
* without affecting normal selector queries.
|
||||
*/
|
||||
function patchDisableDevtoolAutoBootstrap(): string {
|
||||
return `(function(){
|
||||
if (typeof Document === 'undefined') return;
|
||||
const AUTO_SELECTOR = '[disable-devtool-auto]';
|
||||
const NEVER_MATCH_SELECTOR = 'script[__ab_disable_devtool_never_match__="1"]';
|
||||
const normalize = (value) => {
|
||||
if (typeof value !== 'string') return '';
|
||||
return value.replace(/\\s+/g, '').toLowerCase();
|
||||
};
|
||||
const shouldHideSelector = (selector) => normalize(selector) === AUTO_SELECTOR;
|
||||
|
||||
const patchQueryMethod = (proto, method) => {
|
||||
if (!proto) return;
|
||||
const native = proto[method];
|
||||
if (typeof native !== 'function') return;
|
||||
|
||||
const wrapped = function(selector, ...args) {
|
||||
if (shouldHideSelector(selector)) {
|
||||
return native.call(this, NEVER_MATCH_SELECTOR, ...args);
|
||||
}
|
||||
return native.call(this, selector, ...args);
|
||||
};
|
||||
|
||||
try {
|
||||
Object.defineProperty(wrapped, 'name', {
|
||||
value: native.name,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(wrapped, 'toString', {
|
||||
value: () => native.toString(),
|
||||
configurable: true,
|
||||
});
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
Object.defineProperty(proto, method, {
|
||||
value: wrapped,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
} catch {}
|
||||
};
|
||||
|
||||
patchQueryMethod(Document.prototype, 'querySelector');
|
||||
patchQueryMethod(Document.prototype, 'querySelectorAll');
|
||||
if (typeof Element !== 'undefined') {
|
||||
patchQueryMethod(Element.prototype, 'querySelector');
|
||||
patchQueryMethod(Element.prototype, 'querySelectorAll');
|
||||
}
|
||||
})();`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove navigator.webdriver entirely.
|
||||
* Modern detection checks both value and property presence (`'webdriver' in navigator`).
|
||||
|
||||
Reference in New Issue
Block a user