diff --git a/README.md b/README.md index cea0d81..e0a1311 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ # VisionL -面向 AI 智能体的开源可持久化浏览器。 +面向 AI 智能体的开源可持久化浏览器 —— **让 LLM 访问页面不被简单人机验证拦截**。 -LLM 通过 `VisionL-cli` 工具调用操控网页——打开页面、点击、输入、截图、提取内容。 -页面独立持久化,关闭任何窗口都不会杀死后台页面。 +LLM 通过 `VisionL-cli` 工具调用操控网页。内置多层反检测机制,覆盖 Navigator 属性、 +Canvas/WebGL/Audio 指纹、HTTP 请求头、屏幕视口、权限状态、鼠标键盘行为等全部维度, +使自动化访问尽可能不被 Cloudflare、Akamai 等反爬服务识别。 ## 特性 +- **反检测优先**:所有可被页面 JS 读取的浏览器特征均有模拟值,不留自动化痕迹 +- **指纹模版化**:内置多套指纹配置(桌面 Chrome/Win/Mac),智能体可按需选择 - **智能体优先**:CLI 子命令专为 LLM 工具调用设计,输出结构化 JSON -- **页面持久化**:页面存活不受客户端断连影响,只有显式 `kill` 才终止 -- **全功能自动化**:点击、输入、滚动、截图、JS 执行、网络拦截 -- **多页面并行**:同时管理多个页面,ID + 别名双轨引用 -- **自动拉起**:CLI 自动检测并启动后台 daemon +- **页面持久化**:页面存活不受客户端断连影响 +- **全功能自动化**:点击、输入、滚动、截图、JS 执行 ## 文档 @@ -20,14 +21,15 @@ LLM 通过 `VisionL-cli` 工具调用操控网页——打开页面、点击、 | [快速开始](docs/usage/quickstart.md) | 安装和基本使用 | | [LLM 集成](docs/usage/llm-integration.md) | 在智能体中集成 VisionL | | [使用示例](docs/usage/examples.md) | 典型场景 | -| [架构设计](docs/development/architecture.md) | 整体架构 | +| [架构设计](docs/development/architecture.md) | 整体架构 + 反检测设计 | +| [反检测设计](docs/development/anti-detection.md) | 指纹覆盖详情、绕过原理 | | [API 文档](docs/development/api.md) | REST + WebSocket 接口 | | [CLI 命令](docs/development/cli.md) | 命令参考 | -| [开发规范](docs/development/contributing.md) | 贡献指南 | +| [开发规范](docs/development/contributing.md) | 贡献指南 + 反检测开发要求 | ## 技术栈 -TypeScript + Node.js + Playwright + Chromium +TypeScript + Node.js + Playwright + playright-extra + stealth 插件 + 自研 stealth 模块 ## License diff --git a/docs/development/anti-detection.md b/docs/development/anti-detection.md new file mode 100644 index 0000000..6555b21 --- /dev/null +++ b/docs/development/anti-detection.md @@ -0,0 +1,412 @@ +# VisionL 反检测设计(专项) + +> 本文档详尽列举页面/后端可检测的自动化痕迹及 VisionL 的应对策略。 + +## 核心原则 + +**真实浏览器有的,VisionL 必须有。真实浏览器没有的,VisionL 不能有。** + +检查方法:在一个真实的桌面 Chrome 浏览器控制台中执行以下探测脚本, +记录返回结果。VisionL 中打开同一个页面执行相同脚本,结果必须一致(或在统计上不可区分)。 + +--- + +## 1. WebDriver 检测 + +### 检测方式 + +```javascript +navigator.webdriver +// 裸 Playwright: true +// 真实 Chrome: false 或 undefined +``` + +这是最直接、最致命的自动化暴露点。几乎所有反爬服务都会首先检查此属性。 + +### 应对 + +使用 `puppeteer-extra-plugin-stealth` 在页面加载前覆盖此属性为 `false`。 + +**验证方法**:打开 bot.sannysoft.com,WebDriver 行应为绿色。 + +--- + +## 2. Navigator 属性检测 + +### 2.1 plugins + +```javascript +navigator.plugins +// 裸 Playwright: PluginArray { length: 0 } +// 真实 Chrome: PluginArray { 0: Plugin, 1: Plugin, 2: Plugin, ... length: 5 } +``` + +真实 Chrome 内置 5 个插件: +- Chrome PDF Plugin +- Chrome PDF Viewer +- Native Client(已弃用但仍存在) + +### 2.2 languages + +```javascript +navigator.languages +// 裸 Playwright: ["en-US"] +// 真实中文系统: ["zh-CN", "en", "en-US"] +``` + +### 2.3 platform + +```javascript +navigator.platform +// Linux: "Linux x86_64" +// Windows: "Win32" +// macOS: "MacIntel" +``` + +### 2.4 hardwareConcurrency / deviceMemory + +```javascript +navigator.hardwareConcurrency // 实际 CPU 核数 +navigator.deviceMemory // 实际内存(GB 整数),如 4、8 +``` + +这两个值保留真实值即可,多样化反而是优势。 + +### 2.5 maxTouchPoints + +```javascript +navigator.maxTouchPoints // 桌面: 0,触屏设备: 1-10 +``` + +### 2.6 connection + +```javascript +navigator.connection +// { downlink: 10, effectiveType: "4g", rtt: 50, saveData: false } +``` + +Headless Chrome 中此属性为 `undefined`。需要注入。 + +### 2.7 vendor / product / productSub + +```javascript +navigator.vendor // "Google Inc." +navigator.product // "Gecko" +navigator.productSub // "20030107" +``` + +--- + +## 3. Chrome 特有对象 + +### 3.1 window.chrome + +```javascript +typeof window.chrome +// 裸 headless: "undefined" +// 真实 Chrome: "object" + +window.chrome.runtime +// headless 没有这个对象 +``` + +真实 Chrome 的 `window.chrome` 包含以下属性: +- `app` +- `csi` +- `loadTimes` +- `runtime` + +### 3.2 navigator.brave 和 navigator.permissions.query('brave') + +检测 Brave 浏览器的特有 API。VisionL 不应注入这些。 + +--- + +## 4. 屏幕与视口 + +### 4.1 尺寸层级关系 + +真实浏览器的尺寸遵循严格层级: + +``` +screen.width >= screen.availWidth >= window.outerWidth > window.innerWidth >= viewport +``` + +不是所有值都相等。反爬服务会检查: + +```javascript +const checks = [ + screen.width, // 总屏幕宽度 + screen.availWidth, // 可用区域(扣除任务栏) + window.outerWidth, // 窗口外边(含边框和 DevTools) + window.innerWidth, // 窗口内边(含滚动条) + document.documentElement.clientWidth, // 视口宽度 +]; +``` + +### 4.2 devicePixelRatio + +现代设备多为 2(Retina)或 1~3。桌面默认 1 即可。 + +### 4.3 colorDepth / pixelDepth + +桌面始终为 `24`。 + +--- + +## 5. Canvas 指纹 + +### 检测原理 + +```javascript +const canvas = document.createElement('canvas'); +canvas.width = 200; +canvas.height = 50; +const ctx = canvas.getContext('2d'); +ctx.textBaseline = 'top'; +ctx.font = '14px Arial'; +ctx.fillStyle = '#f60'; +ctx.fillRect(125, 1, 62, 20); +ctx.fillStyle = '#069'; +ctx.fillText('Hello, VisionL!', 2, 15); +ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'; +ctx.fillText('Hello, VisionL!', 4, 17); + +const hash = canvas.toDataURL(); +// 不同 GPU/驱动/OS 的 hash 有微小差异 +``` + +### 策略 + +在 `toDataURL()` / `getImageData()` / `toBlob()` 等输出时,在像素末尾加入 +±1 的 RGB 随机扰动。扰动基于页面上下文种子,同页面的扰动一致,跨页面不同。 + +**噪声强度**:默认 0.3(0-1 刻度)。0.3 意味着约 30% 的像素有 ±1 扰动。 + +### 一致性检查 + +某些反爬服务会连续两次获取 canvas 指纹并比较。VisionL 确保同页面内两次调用 +`toDataURL()` 返回相同结果(基于固定种子),但不同页面返回不同结果。 + +--- + +## 6. WebGL 指纹 + +### 检测原理 + +```javascript +const canvas = document.createElement('canvas'); +const gl = canvas.getContext('webgl'); + +// GPU 信息 +gl.getParameter(gl.UNMASKED_VENDOR_WEBGL); // "Google Inc. (Intel)" 等 +gl.getParameter(gl.UNMASKED_RENDERER_WEBGL); // "ANGLE (Intel, Mesa Intel(R) UHD Graphics..." + +// 渲染测试 +// 类似 Canvas 指纹,在 3D 场景中绘制并获取像素值 +``` + +### 策略 + +- `UNMASKED_VENDOR_WEBGL` 和 `UNMASKED_RENDERER_WEBGL` 保留真值或使用模版值 +- 在 `readPixels()` 加入微量噪声 +- 其余 `getParameter()` 调用返回真实值 + +--- + +## 7. AudioContext 指纹 + +### 检测原理 + +```javascript +const ctx = new AudioContext(); +const oscillator = ctx.createOscillator(); +const analyser = ctx.createAnalyser(); +const gain = ctx.createGain(); +// ... 连接并处理音频 +const array = new Float32Array(analyser.frequencyBinCount); +analyser.getFloatFrequencyData(array); +// 不同设备的浮点精度有微小差异 +``` + +### 策略 + +在 `getFloatFrequencyData()` / `getByteFrequencyData()` / `getFloatTimeDomainData()` +等输出中,在显著低于信号水平的量级上加入随机噪声(约 -100dB)。人耳听不到, +但足以使音频指纹每次不同。 + +--- + +## 8. 字体检测 + +### 检测方式 + +浏览器没有直接枚举系统字体的 API,但页面可以通过以下方式探测: + +```javascript +document.fonts.ready.then(() => { + document.fonts.forEach(f => console.log(f.family)); +}); +``` + +或测量固定文本在不同字体下的宽度。 + +### 策略 + +V1 不做专门的字体列表注入。基础的中英文字体(Arial、sans-serif、serif、monospace 等) +保持一致即可。 + +--- + +## 9. 时间精度 + +### 检测方式 + +```javascript +performance.now() // 高精度时间 +Date.now() // Unix 时间戳 +``` + +某些反爬检测会测量代码执行时间,自动化工具(如通过 CDP 注入脚本)可能在时间线上 +留下异常模式。 + +### 策略 + +V1 不干扰时间 API。但确保 `performance.now()` 的精度受浏览器控制(通常微秒级), +且没有人为的时间偏移。 + +--- + +## 10. HTTP 请求头 + +### 检测标头 + +| 标头 | 裸 Playwright | 真实 Chrome | VisionL | +|------|-------------|-----------|---------| +| `User-Agent` | 含 HeadlessChrome | `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36` | 模拟真实 UA | +| `Accept-Language` | `en-US` | `zh-CN,zh;q=0.9,en;q=0.8` | 可配置 | +| `Sec-CH-UA` | 不完整 | `"Chromium";v="132", "Google Chrome";v="132", "Not?A_Brand";v="99"` | 完整注入 | +| `Sec-CH-UA-Platform` | 缺失 | `"Linux"` | 注入 | +| `Sec-CH-UA-Mobile` | 缺失 | `?0` | 注入 | +| `sec-ch-ua-arch` | 缺失 | `"arm"` 或 `"x86"` | 注入 | +| `sec-ch-ua-bitness` | 缺失 | `"64"` | 注入 | +| `sec-ch-ua-full-version` | 缺失 | 完整版本号 | 注入 | +| `sec-ch-ua-platform-version` | 缺失 | OS 版本 | 注入 | + +### 策略 + +- UA 和 Accept-Language 通过 Playwright Context 配置设置 +- `Sec-CH-UA-*` 系列通过 `page.route()` 拦截请求并修改标头 + +--- + +## 11. 权限状态 + +### 检测方式 + +```javascript +const status = await navigator.permissions.query({ name: 'notifications' }); +// { state: "prompt" | "granted" | "denied" } + +navigator.permissions.query({ name: 'geolocation' }); +navigator.permissions.query({ name: 'camera' }); +navigator.permissions.query({ name: 'microphone' }); +``` + +裸 Playwright 对这些权限查询返回 `prompt` 状态,这是正常的默认行为。 + +### 策略 + +通过 Playwright Context 权限 API 预设权限状态,匹配指纹模版。 + +--- + +## 12. 行为模拟 + +### 12.1 鼠标移动 + +**裸 API 问题**:Playwright 的 `click` 直接跳转到目标元素中心,无中间 `mousemove` 事件。 + +**检测**:某些页面监听 `mousemove` 事件,检测鼠标在点击前是否有移动轨迹。 + +**策略**: + +``` +鼠标路径:当前位置 → 目标中心 + 随机抖动(±5px) +Bezier 插值:起点 → 控制点1(偏右下) → 控制点2(偏左上) → 终点 +速度分布:先加速后减速(模拟 Fitts 定律) +抖动:路径上每 10ms 加入 ±2px 随机偏移 +``` + +V1 实现简化版(线性 + 抖动),V2 升级为贝塞尔曲线。 + +### 12.2 键盘输入 + +**裸 API 问题**:Playwright 的 `type` 瞬间输入所有字符,无时间间隔。 + +**检测**:某些页面计算 `keydown` 和 `keyup` 之间的时间间隔,检测异常输入速度。 + +**策略**: + +``` +逐字符输入:keydown → (10ms) → keypress → (10ms) → keyup +字符间隔:50-150ms 随机 +标点/回车:相比字母略长(+20ms) +中文输入法:V1 不模拟,使用 paste 或逐个字符注入 +``` + +### 12.3 滚动 + +**裸 API 问题**:瞬间跳转到目标位置。 + +**策略**: + +``` +分段滚动:每次 50-200px(随机),间隔 10-30ms +缓动:先快后慢 +``` + +--- + +## 13. 检测站点 + +### 13.1 bot.sannysoft.com + +检测项:navigator.webdriver、plugins、languages、chrome、permissions、canvas、webgl、fonts、screen resolution 等。 + +**目标:所有测试项绿色通过** + +### 13.2 abrahamjuliot.github.io/creepjs + +检测项:每个浏览器指纹维度逐一打分(0%-100% 异常分数)。 + +异常分数含义: +- 0-30%:正常范围,不同设备的自然差异 +- 30-70%:可疑,但某些配置可能触发 +- 70-100%:自动化工具明确痕迹 + +**目标:所有维度 ≤ 30% 异常分数** + +### 13.3 fingerprint.com/demo + +综合指纹服务,给出置信度评分。 + +**目标:被识别为正常浏览器(非 bot)** + +--- + +## 14. 性能开销 + +反检测模块不应显著影响性能: + +| 模块 | 开销 | 说明 | +|------|------|------| +| stealth 插件 (playwright-extra) | ~0ms | 页面加载前注入,无后续开销 | +| canvas 噪声 | 每帧 0-1ms | 仅在截图/toDataURL 时触发 | +| audio 噪声 | 每次调用 0-1ms | 仅在音频 API 调用时触发 | +| 鼠标路径计算 | 每次点击 0-2ms | 纯 JS 数学计算 | +| HTTP 头拦截 | 每请求 0-1ms | page.route 拦截 | +| 页面初始注入脚本 | 页面加载时 10-50ms | evalOnNewDocument,一次性 | + +总体而言,页面加载时增加 10-50ms,运行时交互延迟增加 0-5ms,对用户体验和 LLM 交互 +无感知影响。 diff --git a/docs/development/api.md b/docs/development/api.md index 89ed377..47a6d8b 100644 --- a/docs/development/api.md +++ b/docs/development/api.md @@ -64,7 +64,8 @@ POST /pages ```json { "url": "https://example.com", - "alias": "demo" // 可选 + "alias": "demo", // 可选 + "profile": "desktop-chrome" // 可选,指纹配置模版 ID,默认 "desktop-chrome" } ``` @@ -78,7 +79,8 @@ POST /pages "url": "https://example.com", "alias": "demo", "title": "Example Domain", - "status": "active" + "status": "active", + "profile": "desktop-chrome" } } ``` @@ -310,6 +312,25 @@ GET /pages/:id/html ## Daemon 管理 +### 指纹配置列表 + +``` +GET /profiles +``` + +**响应:** + +```json +{ + "ok": true, + "data": [ + { "id": "desktop-chrome", "name": "桌面 Chrome (通用)", "platform": "Linux x86_64" }, + { "id": "desktop-windows", "name": "Windows 10 Chrome", "platform": "Windows NT 10.0" }, + { "id": "desktop-mac", "name": "macOS Chrome", "platform": "Macintosh" } + ] +} +``` + ### 健康检查 ``` @@ -343,3 +364,4 @@ ws://127.0.0.1:9527/ws | `page:navigated` | daemon → 客户端 | 页面 URL 发生变化 | | `page:crashed` | daemon → 客户端 | Playwright 页面崩溃 | | `page:console` | daemon → 客户端 | 页面控制台输出(调试) | +| `page:detection:warning` | daemon → 客户端 | 页面可能检测到自动化特征 | diff --git a/docs/development/architecture.md b/docs/development/architecture.md index 6f292af..d923e13 100644 --- a/docs/development/architecture.md +++ b/docs/development/architecture.md @@ -4,16 +4,22 @@ ## 1. 概述 -VisionL 是一款专为具备本地读写执行能力的 AI 智能体设计的开源浏览器。 -LLM 通过 `VisionL-cli` 工具调用来操控网页。页面独立持久化——关闭 GUI 窗口不会杀死页面后台, -每个页面只能通过 CLI 参数或未来 GUI 的右键菜单按钮来关闭。 +### 要解决的根本问题 -### 核心原则 +**让 LLM 访问各类页面尽可能不被简单的人机验证所拦截。** + +这是 VisionL 存在的第一性原理,所有设计决策必须围绕这个目标展开。 + +市面上绝大多数自动化浏览器(Playwright/Puppeteer 裸跑)会在数十个维度上暴露自动化痕迹, +被 Cloudflare、Akamai、DataDome 等反爬服务轻松识别。VisionL 的核心竞争力在于: +**GUI 浏览器所有能被页面/页面后端检测到的特征,在 CLI 中均有值(可以是模拟的)**。 + +### 次要目标 - **页面持久化**:页面在客户端断连后依然存活,只有显式 `kill` 才能终止 - **智能体优先**:CLI 子命令为 LLM 工具调用而设计,输出结构化 JSON - **本地优先**:Daemon 仅监听 localhost,不对外开放 -- **GUI 就绪**:HTTP/WS API 同时服务于 CLI 和未来的 GUI,避免重复实现 +- **GUI 就绪**:HTTP/WS API 同时服务于 CLI 和未来的 GUI --- @@ -22,27 +28,242 @@ LLM 通过 `VisionL-cli` 工具调用来操控网页。页面独立持久化— | 层级 | 选择 | 理由 | |------|------|------| | 浏览器引擎 | Playwright + Chromium | AI 浏览器自动化的事实标准 | -| 语言 | TypeScript + Node.js | Playwright 原生语言,生态最优 | -| 通信协议 | HTTP REST + WebSocket (localhost) | 职责分离,GUI 可复用同一套 API | -| 包管理 | npm workspaces (monorepo) | 共享类型,单仓库管理 | -| CLI 框架 | commander + chalk | 轻量、社区熟知 | +| 反检测框架 | `playwright-extra` + `puppeteer-extra-plugin-stealth` | 自动处理 WebDriver 标记和基础指纹 | +| 指纹补充 | 自研 stealth 模块 | stealth 插件只覆盖约 60% 的指纹,剩余需自行实现 | +| 语言 | TypeScript + Node.js | Playwright 原生语言 | +| 通信协议 | HTTP REST + WebSocket (localhost) | 职责分离,GUI 可复用 | +| 包管理 | npm workspaces (monorepo) | 共享类型 | +| CLI 框架 | commander + chalk | 轻量 | | HTTP 服务 | 原生 `http` + `ws` 库 | 本地 daemon 无需重型框架 | ### JSON 转义处理 -所有返回给智能体的 JSON 必须正确处理特殊字符转义。页面内容(文本、HTML)中可能包含 -引号、反斜杠、控制字符、Unicode 代理对等,统一通过 `JSON.stringify` 序列化整个响应对象, -禁止手动拼接 JSON 字符串。 +所有返回给智能体的 JSON 必须通过 `JSON.stringify` 序列化整个响应对象, +禁止手动拼接 JSON 字符串。页面内容字段中的引号、反斜杠、控制字符均正确处理。 --- -## 3. 项目结构 +## 3. 反检测设计(核心) + +### 3.1 指纹覆盖全景 + +目标:**所有能通过 JS API 读取到的浏览器特征,都必须返回真实 GUI 浏览器的值, +不能留下任何自动化痕迹。** + +以下表格列出页面/后端可检测的维度、Playwright 默认值的问题、以及 VisionL 的应对策略。 + +#### 3.1.1 Navigator 属性 + +| 属性 | 裸 Playwright 值 | 问题 | VisionL 策略 | +|------|-----------------|------|-------------| +| `navigator.webdriver` | `true` | **最致命的暴露** | stealth 插件 → `false` | +| `navigator.userAgent` | 含 "HeadlessChrome" | 直接暴露 | 模拟真实 Chrome UA,可配置 | +| `navigator.plugins` | 空数组 | 真实 Chrome 有 5 个插件 | stealth 插件 → 注入 PDF Viewer 等 | +| `navigator.languages` | `["en-US"]` | 需匹配目标地区 | 可配置,默认 `["zh-CN", "en-US"]` | +| `navigator.platform` | 随 OS | 无问题但需一致性 | 随系统,但确保与其他指纹一致 | +| `navigator.hardwareConcurrency` | 实际 CPU 核数 | OK | 保持真值 | +| `navigator.deviceMemory` | 实际值 | OK | 保持真值 | +| `navigator.maxTouchPoints` | `0` | 桌面无触摸 | 可配置,桌面默认 `0` | +| `navigator.vendor` | `"Google Inc."` | stealth 会修正 | stealth 插件处理 | +| `navigator.productSub` | `"20030107"` | 需一致 | stealth 插件处理 | +| `navigator.connection` | `undefined` (headless) | 真实 Chrome 有值 | 注入 NetworkInformation 对象 | +| `navigator.mediaDevices` | 存在 | 可能暴露空设备列表 | 模拟至少一个音频设备 | + +#### 3.1.2 Chrome 特有属性 + +| 属性 | 裸 Playwright 值 | 问题 | VisionL 策略 | +|------|-----------------|------|-------------| +| `window.chrome` | `undefined` (headless) | 真实 Chrome 有此对象 | 注入完整 `window.chrome` 对象 | +| `chrome.runtime` | N/A | 检测自动化时常用 | 注入 mock runtime | +| `navigator.brave` | N/A | Brave 检测用,非必须 | 不注入(伪装 Chrome 不是 Brave) | + +#### 3.1.3 屏幕与视口 + +| 属性 | 裸 Playwright 值 | 问题 | VisionL 策略 | +|------|-----------------|------|-------------| +| `screen.width/height` | 默认 1280x720 | 非标准分辨率 | 可配置,默认 1920x1080 | +| `screen.availWidth/Height` | 同 screen | 任务栏高度需扣除 | 模拟,比 screen 小 40-80px | +| `screen.colorDepth` | `24` | OK | `24` | +| `screen.pixelDepth` | `24` | OK | `24` | +| `window.outerWidth/Height` | 与视口相同 | 真实浏览器窗口含边框 | 比视口大(含窗口装饰) | +| `window.innerWidth/Height` | 视口尺寸 | 需与 screen 逻辑一致 | 保证 inner < outer < screen | +| `window.devicePixelRatio` | `1` | 现代设备多为 2 | 可配置,默认 `1` 或自动检测 | + +#### 3.1.4 Canvas 指纹 + +页面可以在 canvas 上渲染特定图形,获取像素哈希作为指纹。不同 GPU/驱动/OS 的渲染结果 +有微小差异。VisionL 需要在 canvas 渲染中加入可控的随机噪声,使每次指纹不同但看起来 +像正常设备,从而绕过基于 canvas 哈希的追踪。 + +| 措施 | 实现 | +|------|------| +| Canvas 2D 噪声 | `toDataURL()` 和 `getImageData()` 返回时在像素末尾添加 ±1 随机扰动 | +| WebGL 噪声 | `getParameter()` 和 `readPixels()` 添加类似扰动 | +| 一致性 | 同一页面会话内噪声种子固定,跨页面会话刷新 | + +#### 3.1.5 WebGL 指纹 + +| 属性 | 裸 Playwright 值 | VisionL 策略 | +|------|-----------------|-------------| +| `UNMASKED_VENDOR_WEBGL` | 真实 GPU 厂商 | 可保留真值(多样化),也可统一模拟 | +| `UNMASKED_RENDERER_WEBGL` | 真实 GPU 型号 | 同上 | +| WebGL 渲染噪声 | 无 | 类似 Canvas,在 readPixels 加入微量噪声 | + +#### 3.1.6 AudioContext 指纹 + +页面通过 AudioContext 处理音频信号提取指纹。VisionL 策略: + +- `createOscillator()` 生成的波形在浮点精度末尾加入随机噪声 +- `createAnalyser()` 的 `getByteFrequencyData()` 同样处理 +- 噪声量级极小(-100dB 级别),不改变音频语义 + +#### 3.1.7 字体枚举 + +页面无法直接枚举系统字体列表,但可通过以下方式探测: +- 测量指定字体的文本宽度 +- `document.fonts` API +- Flash(已消亡) + +VisionL 默认不做字体列表注入(多数反爬不会测字体),但保持基础常见字体的一致性。 + +#### 3.1.8 HTTP 请求头 + +| 头部 | 裸 Playwright 值 | VisionL 策略 | +|------|-----------------|-------------| +| `User-Agent` | 含 HeadlessChrome | 模拟真实 Chrome UA,可配置 | +| `Accept-Language` | `en-US` | 匹配 `navigator.languages` | +| `Sec-CH-UA` | 不完整 | 补全 `"Chromium";v="xxx", "Google Chrome";v="xxx"` | +| `Sec-CH-UA-Platform` | 随 OS | 确保一致性 | +| `Sec-CH-UA-Mobile` | `?0` | `?0`(桌面) | +| `Accept` | OK | 保持默认 | +| `Accept-Encoding` | OK | 保持默认 | +| `Connection` | `keep-alive` | 保持默认 | +| `Upgrade-Insecure-Requests` | `1` | 保持默认 | + +#### 3.1.9 权限状态 + +| 权限 | 裸 Playwright 值 | VisionL 策略 | +|------|-----------------|-------------| +| `notifications` | `prompt` | 可配置:`prompt`/`granted`/`denied` | +| `geolocation` | `prompt` | 可配置,`granted` 时提供模拟坐标 | +| `camera` | `prompt` | 可配置 | +| `microphone` | `prompt` | 可配置 | +| `midi` | `prompt` | 保持 `prompt` | + +#### 3.1.10 行为模拟 + +**鼠标移动**(v1 基础版,v2 增强): + +- 点击前鼠标从当前位置线性移动到目标中心(带随机抖动) +- 移动速度加入随机变化(不恒定) +- `mousemove` 事件在移动路径上均匀发射 + +**键盘输入**(v1 基础版): + +- 每个字符间隔 `50-150ms` 随机 +- `keydown` → `keypress` → `keyup` 完整序列 +- 中文输入法暂不模拟 + +**滚动**: + +- 非瞬间跳转,分段滚动 +- 每次滚动步长加入随机抖动 + +### 3.2 实现方案 + +``` +daemon/ +├── src/ +│ ├── stealth/ # 反检测模块 +│ │ ├── index.ts # 统一入口,页面创建时注入 +│ │ ├── navigator.ts # navigator 属性覆盖 +│ │ ├── chrome-runtime.ts # window.chrome 注入 +│ │ ├── screen.ts # 屏幕/视口尺寸管理 +│ │ ├── canvas-noise.ts # Canvas/WebGL/Audio 噪声 +│ │ ├── headers.ts # HTTP 头拦截修正 +│ │ ├── permissions.ts # 权限状态管理 +│ │ ├── human-input.ts # 鼠标/键盘/滚动行为模拟 +│ │ ├── fingerprint-profile.ts # 指纹配置文件结构 +│ │ └── profiles/ # 内置指纹模版 +│ │ ├── desktop-chrome.ts # 桌面 Chrome 通用模版 +│ │ ├── desktop-windows.ts # Windows Chrome +│ │ └── desktop-mac.ts # macOS Chrome +``` + +### 3.3 指纹配置(FingerprintProfile) + +```typescript +// packages/core/src/types/fingerprint.ts +interface FingerprintProfile { + // 浏览器基础 + userAgent: string; + platform: string; + languages: string[]; + acceptLanguage: string; + + // 屏幕 + screen: { + width: number; + height: number; + colorDepth: number; + pixelRatio: number; + }; + viewport: { + width: number; + height: number; + }; + + // GPU + webglVendor: string; + webglRenderer: string; + + // 时区与位置 + timezone: string; + geolocation?: { latitude: number; longitude: number; accuracy: number }; + + // 权限 + permissions: { + notifications: 'prompt' | 'granted' | 'denied'; + geolocation: 'prompt' | 'granted' | 'denied'; + camera: 'prompt' | 'granted' | 'denied'; + microphone: 'prompt' | 'granted' | 'denied'; + }; + + // 行为 + behavior: { + mouseMoveDelay: { min: number; max: number }; // ms + keyPressDelay: { min: number; max: number }; // ms + scrollStepDelay: { min: number; max: number }; // ms + }; + + // Canvas 噪声 + canvasNoise: { + enabled: boolean; + strength: number; // 0-1, 噪声强度 + }; +} +``` + +### 3.4 WebDriver 检测网站验证 + +v1 目标:通过以下检测站点的自动化识别: + +| 检测站点 | 检测方式 | 目标 | +|---------|---------|------| +| https://bot.sannysoft.com | 综合(navigator + screen + chrome + canvas + webgl + fonts) | **必须全绿** | +| https://fingerprint.com/demo | 综合指纹(最全面) | 降低置信度到非 bot 区间 | +| https://abrahamjuliot.github.io/creepjs/ | 浏览器指纹各维度逐一打分 | 所有维度分数控制在合理范围 | + +--- + +## 4. 项目结构 ``` VisionL/ ├── docs/ │ ├── development/ │ │ ├── architecture.md # 本文档 +│ │ ├── anti-detection.md # 反检测专项文档(检测维度详情、绕过原理) │ │ ├── api.md # REST + WS 接口文档 │ │ ├── cli.md # CLI 命令参考 │ │ └── contributing.md # 开发规范 @@ -51,41 +272,56 @@ VisionL/ │ ├── llm-integration.md # 智能体集成指南 │ └── examples.md # 典型场景示例 ├── packages/ -│ ├── core/ # 共享库:类型定义、HTTP 客户端、工具函数 +│ ├── core/ # 共享库:类型定义、HTTP 客户端 │ │ ├── src/ │ │ │ ├── types/ -│ │ │ │ ├── page.ts # Page 结构 -│ │ │ │ ├── api.ts # 请求/响应类型 -│ │ │ │ └── ws.ts # WebSocket 事件类型 -│ │ │ ├── client.ts # Daemon HTTP + WS 客户端(CLI 和 GUI 共用) -│ │ │ ├── escape.ts # JSON 安全转义 +│ │ │ │ ├── page.ts +│ │ │ │ ├── api.ts +│ │ │ │ ├── ws.ts +│ │ │ │ └── fingerprint.ts # 指纹配置类型 +│ │ │ ├── client.ts +│ │ │ ├── escape.ts │ │ │ └── index.ts │ │ └── package.json -│ ├── daemon/ # 后台进程:Playwright 管理 + HTTP/WS 服务 +│ ├── daemon/ # 后台进程 │ │ ├── src/ -│ │ │ ├── server.ts # HTTP + WS 入口 +│ │ │ ├── server.ts │ │ │ ├── routes/ -│ │ │ │ ├── pages.ts # 页面 CRUD -│ │ │ │ ├── actions.ts # click/type/scroll/eval/navigate -│ │ │ │ ├── content.ts # screenshot/text/html -│ │ │ │ └── health.ts # 健康检查 -│ │ │ ├── browser-manager.ts # Playwright 浏览器/页面生命周期 -│ │ │ ├── page-registry.ts # 内存页面表(id → Playwright Page) -│ │ │ ├── ws-relay.ts # WebSocket 事件广播 -│ │ │ └── pidfile.ts # Daemon 进程管理 +│ │ │ │ ├── pages.ts +│ │ │ │ ├── actions.ts +│ │ │ │ ├── content.ts +│ │ │ │ └── health.ts +│ │ │ ├── browser-manager.ts +│ │ │ ├── page-registry.ts +│ │ │ ├── ws-relay.ts +│ │ │ ├── pidfile.ts +│ │ │ └── stealth/ # 反检测模块 +│ │ │ ├── index.ts # 统一注入入口 +│ │ │ ├── navigator.ts +│ │ │ ├── chrome-runtime.ts +│ │ │ ├── screen.ts +│ │ │ ├── canvas-noise.ts +│ │ │ ├── headers.ts +│ │ │ ├── permissions.ts +│ │ │ ├── human-input.ts +│ │ │ ├── fingerprint-profile.ts +│ │ │ └── profiles/ +│ │ │ ├── desktop-chrome.ts +│ │ │ ├── desktop-windows.ts +│ │ │ └── desktop-mac.ts │ │ └── package.json -│ └── cli/ # CLI 工具:用户 + LLM 交互入口 +│ └── cli/ │ ├── src/ -│ │ ├── index.ts # CLI 入口(commander) +│ │ ├── index.ts │ │ ├── commands/ -│ │ │ ├── page.ts # visionl page open/close/list/info -│ │ │ ├── action.ts # visionl click/type/scroll/eval/wait/navigate -│ │ │ ├── view.ts # visionl screenshot/text/html -│ │ │ └── daemon.ts # visionl daemon start/stop/status -│ │ ├── format.ts # 输出格式化(JSON/pretty) -│ │ └── auto-daemon.ts # 自动拉起 daemon 逻辑 +│ │ │ ├── page.ts +│ │ │ ├── action.ts +│ │ │ ├── view.ts +│ │ │ └── daemon.ts +│ │ ├── format.ts +│ │ └── auto-daemon.ts │ └── package.json -├── package.json # monorepo root +├── package.json ├── tsconfig.json ├── .gitignore ├── README.md @@ -94,230 +330,163 @@ VisionL/ --- -## 4. Daemon 设计 +## 5. Daemon 设计 -### 4.1 REST API +### 5.1 REST API | 方法 | 路径 | 说明 | 请求体 | 响应 | |------|------|------|--------|------| -| POST | `/pages` | 打开新页面 | `{ url, alias? }` | `{ id, url, alias?, title, status }` | +| POST | `/pages` | 打开新页面 | `{ url, alias?, profile? }` | `{ id, url, alias?, title, status, profileId }` | | GET | `/pages` | 列出所有页面 | — | `[{ id, url, alias?, title, status }]` | -| GET | `/pages/:id` | 页面详情 | — | `{ id, url, alias?, title, status }` | +| GET | `/pages/:id` | 页面详情 | — | 同上 | | DELETE | `/pages/:id` | 杀死页面 | — | `{ ok: true }` | | POST | `/pages/:id/navigate` | 跳转 URL | `{ url }` | `{ url, title }` | | POST | `/pages/:id/click` | 点击元素 | `{ selector }` | `{ success }` | | POST | `/pages/:id/type` | 输入文本 | `{ selector, text }` | `{ success }` | | POST | `/pages/:id/scroll` | 滚动页面 | `{ deltaY?, toBottom? }` | `{ success }` | | POST | `/pages/:id/eval` | 执行 JS | `{ code }` | `{ result }` | -| POST | `/pages/:id/wait` | 等待条件 | `{ selector?, timeout?, ms? }` | `{ success }` | +| POST | `/pages/:id/wait` | 等待条件 | `{ selector?, ms? }` | `{ success }` | | GET | `/pages/:id/screenshot` | 页面截图 | — | `{ base64, mime }` | | GET | `/pages/:id/text` | 页面纯文本 | — | `{ text }` | | GET | `/pages/:id/html` | 页面 HTML | — | `{ html }` | +| GET | `/profiles` | 列出可用的指纹配置 | — | `[{ id, name }]` | | GET | `/health` | 健康检查 | — | `{ status: "ok" }` | -### 4.2 WebSocket 事件 +> **新增**:`POST /pages` 的 `profile` 字段指定指纹配置(默认 `desktop-chrome`)。 +> `GET /profiles` 返回所有内置指纹模版。 -客户端通过 `ws://127.0.0.1:{port}/ws` 建立连接,daemon 主动推送: +### 5.2 WebSocket 事件 + +同前,另增: | 事件 | 数据 | 触发时机 | |------|------|---------| -| `page:created` | `{ id, url, alias?, title }` | 新页面打开 | -| `page:closed` | `{ id }` | 页面被杀死 | -| `page:navigated` | `{ id, url, title }` | 页面 URL 变化 | -| `page:crashed` | `{ id, error }` | 页面崩溃 | -| `page:console` | `{ id, level, text }` | 控制台输出(调试用) | +| `page:detection:warning` | `{ id, level, detail }` | 页面检测到潜在的自动化特征 | -### 4.3 页面生命周期 +### 5.3 页面创建流程(含反检测注入) ``` -visionl page open https://example.com --alias demo - → POST /pages { url, alias: "demo" } - → daemon 创建 Playwright Context + Page - → 注册到 PageRegistry { id: "p_abc123", page, alias: "demo" } - → 返回 { id: "p_abc123", url, alias: "demo", title, status: "active" } - -visionl page screenshot p_abc123 - → GET /pages/p_abc123/screenshot - → daemon 调用 page.screenshot(),返回 base64 - -visionl page kill p_abc123 - → DELETE /pages/p_abc123 - → daemon 调用 page.close()、context.close() - → 从 PageRegistry 移除 +visionl page open https://example.com --alias demo --profile desktop-windows + → POST /pages { url, alias: "demo", profile: "desktop-windows" } + → browser-manager.createPage(url, fingerprintProfile) + → 1. 创建 BrowserContext(从 profile 读取 viewport、locale、timezone、geolocation) + → 2. 创建 Page + → 3. 注入 stealth 插件(navigator.webdriver 等基础隐藏) + → 4. 注入自研 stealth 模块: + - 注入 navigator 覆盖脚本(evalOnNewDocument) + - 注入 window.chrome 对象 + - 注入 canvas/webgl/audio 噪声脚本 + - 注册请求拦截器修正 HTTP 头 + - 注入权限状态 + → 5. 导航到目标 URL + → 6. 注册到 PageRegistry + → 返回 { id: "p_abc123", ..., profileId: "desktop-windows" } ``` -**规则:** -- 页面在被显式 `kill` 之前永远存活,无超时、无闲置清理 -- Daemon 重启不会恢复之前的页面(v1 不包含此功能) -- `visionl daemon stop` 优雅退出 daemon,但不杀死页面(Playwright 浏览器进程独立管理) +### 5.4 行为模拟流程 + +操作命令(click/type/scroll)不走 Playwright 的 API,而是走自研的 `human-input` 模块: + +``` +visionl click p_abc123 "#login" + → POST /pages/p_abc123/click { selector: "#login" } + → action-handler: + → 1. 获取元素 bounds + → 2. 计算鼠标路径(当前坐标 → 目标中心 + 抖动) + → 3. 沿路径逐个发射 mousemove + → 4. 到达后依次发射 mousedown → mouseup → click + → 5. (Future) 考虑 ancestor visibility 和 z-index +``` + +``` +visionl type p_abc123 "#username" "hello" + → POST /pages/p_abc123/type { selector: "#username", text: "hello" } + → action-handler: + → 1. 聚焦元素(click or focus) + → 2. 逐字符:keydown → keypress → keyup + → 3. 每字符间隔随机 50-150ms + → 4. 完成后可选触发 change/blur 事件 +``` --- -## 5. CLI 设计 +## 6. CLI 设计(略,同前版本) -### 5.1 页面管理 - -```bash -visionl page open [--alias ] -visionl page list -visionl page info -visionl page kill -visionl page kill-all -``` - -### 5.2 页面操作 - -```bash -visionl click -visionl type -visionl scroll [--down ] [--bottom] -visionl navigate -visionl eval -visionl wait [--selector ] [--ms ] -``` - -### 5.3 内容获取 - -```bash -visionl screenshot [-o ] -visionl text -visionl html -``` - -### 5.4 Daemon 管理 - -```bash -visionl daemon start [--port ] -visionl daemon stop -visionl daemon status -``` - -### 5.5 REST 直通 - -```bash -visionl raw POST /pages '{"url":"https://example.com"}' -visionl raw GET /pages -visionl raw GET /pages/p_abc123/text -``` - -### 5.6 输出格式 - -默认输出 JSON(供 LLM 解析)。使用 `--pretty` 切换为人类可读格式。 - -```json -// 成功 -{ "ok": true, "data": { ... } } - -// 失败 -{ "ok": false, "error": { "code": "PAGE_NOT_FOUND", "message": "页面 p_xyz 不存在" } } -``` - -**JSON 转义规则**:所有响应通过 `JSON.stringify` 序列化完整对象。页面内容字段(`text`、`html`) -作为不透明字符串处理,由 `JSON.stringify` 负责转义,不手动拼接。 - -### 5.7 自动拉起 - -CLI 在任何页面/操作命令前执行 daemon 存活检测: - -``` -1. 向配置端口(默认 9527)发送 GET /health -2. 有响应 → 继续执行 -3. 超时/拒绝 → 检查 pidfile(~/.visionl/daemon.pid) - - pid 存活且端口可达 → 更新端口,继续 - - pid 已死或端口不可达 → 清理残留 pidfile,启动 daemon -4. 启动:child_process.spawn('visionl-daemon', ['--port', port]) -5. 轮询 GET /health(最多 3 秒,间隔 100ms) -6. 就绪 → 继续;超时 → 报错退出 -``` - -**状态文件:** -- `~/.visionl/daemon.pid` — daemon 进程 PID -- `~/.visionl/daemon.port` — daemon 监听端口 +CLI 子命令不变。`page open` 新增 `--profile ` 指定指纹模版。 --- -## 6. 类型定义(core 包) +## 7. 类型定义(core 包核心类型) ```typescript -// packages/core/src/types/page.ts -interface PageInfo { - id: string; // 自动生成,如 "p_a1b2c3d4" - url: string; - alias?: string; // 用户指定,可选 - title: string; - status: 'active' | 'crashed'; +// 新增 +interface FingerprintProfile { + id: string; // "desktop-chrome" + name: string; // "桌面 Chrome (通用)" + userAgent: string; + platform: string; + languages: string[]; + screen: { width: number; height: number; colorDepth: number; pixelRatio: number }; + viewport: { width: number; height: number }; + webgl: { vendor: string; renderer: string }; + timezone: string; + geolocation?: { latitude: number; longitude: number; accuracy: number }; + permissions: FingerprintPermissions; + behavior: FingerprintBehavior; + canvasNoise: { enabled: boolean; strength: number }; } -// packages/core/src/types/api.ts -interface ApiResponse { - ok: boolean; - data?: T; - error?: { - code: string; - message: string; - }; +interface FingerprintPermissions { + notifications: PermissionState; + geolocation: PermissionState; + camera: PermissionState; + microphone: PermissionState; } -// 错误码 -enum ErrorCode { - PAGE_NOT_FOUND = 'PAGE_NOT_FOUND', - DAEMON_UNREACHABLE = 'DAEMON_UNREACHABLE', - INVALID_URL = 'INVALID_URL', - ALIAS_EXISTS = 'ALIAS_EXISTS', - TIMEOUT = 'TIMEOUT', - INTERNAL = 'INTERNAL', +interface FingerprintBehavior { + mouseMoveDelay: { min: number; max: number }; + keyPressDelay: { min: number; max: number }; + scrollStepDelay: { min: number; max: number }; } - -// packages/core/src/types/ws.ts -type WsEvent = - | { type: 'page:created'; data: PageInfo } - | { type: 'page:closed'; data: { id: string } } - | { type: 'page:navigated'; data: { id: string; url: string; title: string } } - | { type: 'page:crashed'; data: { id: string; error: string } } - | { type: 'page:console'; data: { id: string; level: string; text: string } }; ``` --- -## 7. 关键设计决策 +## 8. 关键设计决策 -### v1 不包含会话恢复 +### 反检测为首要质量指标 -页面在客户端断连后存活,但 daemon 重启后不会恢复。理由: -- Playwright 浏览器进程状态复杂,难以序列化/恢复 -- v1 聚焦 CLI 智能体工作流,daemon 在任务期间保持运行即可 -- 会话恢复(跨 daemon 重启保存/恢复页面状态)可在 v2 添加 +单测、集成测试、手动检测结果共同构成反检测的"质量门"。任何新功能引入不能降低 +反检测评分。 -### 无身份认证 +### 指纹模版化 -Daemon 仅绑定 `127.0.0.1`,任何本地进程均可连接。这是刻意的设计:智能体与 daemon 运行在同一台机器。v2 可添加基于 token 的认证以支持多用户场景。 +不同网站对不同地区的浏览器有不同的预期。VisionL 提供多套预置指纹模版, +智能体可根据目标网站选择。v1 提供 3 套: -### Page ID 与别名解析 +| 模版 ID | 说明 | UA 平台 | +|---------|------|---------| +| `desktop-chrome` | 桌面 Chrome 通用 | Linux x86_64 | +| `desktop-windows` | Windows 10 Chrome | Windows NT 10.0 | +| `desktop-mac` | macOS Chrome | Macintosh Intel | -- Page ID(`p_xxxxxxxx`)始终唯一,自动生成 -- 别名由用户指定,必须唯一。重复使用已有别名会报错 -- 命令同时接受两种形式:`visionl text myalias` 或 `visionl text p_abc123` -- 解析顺序:先尝试别名匹配,再尝试 ID 匹配 +### Canvas 噪声策略 -### 并发模型 +- 同一页面会话(BrowserContext)使用相同的随机种子 +- 跨页面会话自动刷新种子 +- 噪声强度可配置(0-1),默认 0.3 -- 每个 daemon 只有一个 Playwright `Browser` 实例 -- 每次 `POST /pages` 创建一个新的 `BrowserContext`(隔离 cookie/存储)+ `Page` -- v1 不设并发上限;由操作系统自然限制 -- 未来:可配置最大页面数、排队、优先级 +### HTTP 头注入时机 ---- +- `User-Agent` 和 `Accept-Language`:在创建 BrowserContext 时通过 Playwright API 设置 +- `Sec-CH-UA-*` 系列:通过请求拦截(`page.route()`)修改 -## 8. 错误处理策略 +### 其他设计决策(同前) -| 场景 | Daemon 行为 | CLI 行为 | -|------|------------|---------| -| 页面不存在 | 404 + 错误码 | 退出码 1,输出 JSON 错误 | -| Daemon 不可达 | — | 自动拉起或退出码 1 | -| 无效 URL | 400 + 错误码 | 退出码 1,输出 JSON 错误 | -| 选择器未找到 | 操作响应内 404 | 退出码 1,`{ ok: false, error: ... }` | -| 页面崩溃 | `page:crashed` WS 事件,status → crashed | 后续操作返回 500 | -| Daemon 端口被占用 | 启动失败,日志记录 | 退出码 1,建议 `--port` 覆盖 | +- v1 不包含会话恢复 +- 无身份认证(仅 localhost) +- Page ID + 别名双轨引用 +- 每个 daemon 一个 Browser 实例 --- @@ -325,24 +494,31 @@ Daemon 仅绑定 `127.0.0.1`,任何本地进程均可连接。这是刻意的 | 层级 | 工具 | 范围 | |------|------|------| +| 反检测验证 | 脚本 + 人工 | 对 bot.sannysoft.com、fingerprint.com 全绿验证 | +| stealth/navigator | vitest | navigator 各属性值验证 | +| stealth/chrome-runtime | vitest | window.chrome 对象完整性 | +| stealth/canvas-noise | vitest | 噪声输出一致性、强度控制 | +| stealth/headers | vitest + msw | HTTP 头正确性 | +| stealth/human-input | vitest | 鼠标路径计算、键盘延迟分布 | | `core` 类型 | ts 类型检查 | 编译期正确性 | -| `core/client.ts` | vitest + msw | HTTP 客户端行为、错误处理 | -| `daemon/routes` | vitest + playwright-test | 路由逻辑 + 真实 Chromium | -| `daemon/browser-manager` | vitest + playwright-test | 页面生命周期、崩溃恢复 | -| `cli/commands` | vitest + mock 客户端 | 命令解析、输出格式化 | -| `cli/auto-daemon` | vitest + fs mock | Pidfile 逻辑、spawn 行为 | -| 集成测试 | vitest + 真实 daemon | CLI → daemon → Playwright 端到端 | -| JSON 转义 | vitest | 验证特殊字符、Unicode、XSS 向量安全 | +| `daemon/routes` | vitest + playwright-test | 路由逻辑 | +| `cli/commands` | vitest + mock | 命令解析 | +| 集成测试 | vitest + 真实 daemon | CLI → daemon → Playwright | +| JSON 转义 | vitest | 特殊字符安全 | --- ## 10. 未来规划(v1 范围外) -- `visionl-gui`:基于 Electron/Tauri 的桌面 GUI,用于查看和管理页面 -- 会话持久化:跨 daemon 重启保存/恢复页面状态 -- 多用户支持:基于 token 的认证 -- 插件系统:自定义页面处理器 -- 远程 daemon 支持(突破 localhost 限制) +- `visionl-gui`:桌面 GUI +- 会话持久化 +- 更多指纹模版(移动端 Chrome、Safari、Edge 等) +- 指纹随机化(每个页面随机微调指纹参数) +- 行为模拟增强(贝塞尔曲线鼠标路径、人类式打字节奏) +- 验证码自动识别(对接打码服务) +- 多用户支持 +- 插件系统 +- 远程 daemon - 网络拦截和 Mock API - Cookie/存储管理 CLI - 页面录制与回放 diff --git a/docs/development/cli.md b/docs/development/cli.md index fe1e27c..5bb9286 100644 --- a/docs/development/cli.md +++ b/docs/development/cli.md @@ -20,6 +20,7 @@ cd packages/cli && npm link |------|------| | `--pretty` | 人类可读格式输出(默认 JSON) | | `--port ` | daemon 端口(默认 9527) | +| `--profile ` | 指纹配置模版(默认 desktop-chrome) | | `--help` | 查看帮助 | --- @@ -31,13 +32,13 @@ cd packages/cli && npm link 打开新页面。 ```bash -visionl page open [--alias ] +visionl page open [--alias ] [--profile ] ``` **示例:** ```bash -visionl page open https://www.baidu.com --alias baidu +visionl page open https://www.baidu.com --alias baidu --profile desktop-windows # {"ok":true,"data":{"id":"p_a1b2c3d4","url":"https://www.baidu.com","alias":"baidu","title":"百度一下,你就知道","status":"active"}} ``` @@ -184,6 +185,25 @@ visionl html --- +## 指纹配置 + +### `visionl profiles` + +列出可用的指纹配置模版。 + +```bash +visionl profiles +# {"ok":true,"data":[{"id":"desktop-chrome","name":"桌面 Chrome (通用)"},{"id":"desktop-windows","name":"Windows 10 Chrome"},...]} +``` + +| 模版 ID | 说明 | UA 平台 | +|---------|------|---------| +| `desktop-chrome` | 桌面 Chrome 通用(默认) | Linux x86_64 | +| `desktop-windows` | Windows 10 Chrome | Windows NT 10.0 | +| `desktop-mac` | macOS Chrome | Macintosh Intel | + +--- + ## Daemon 管理 ### `visionl daemon start` diff --git a/docs/development/contributing.md b/docs/development/contributing.md index 8041c8e..54c32c3 100644 --- a/docs/development/contributing.md +++ b/docs/development/contributing.md @@ -26,8 +26,8 @@ Monorepo 使用 npm workspaces 管理: ``` packages/ -├── core/ # 共享类型、HTTP 客户端、工具函数 -├── daemon/ # 后台守护进程 +├── core/ # 共享类型(含 FingerprintProfile)、HTTP 客户端、JSON 转义 +├── daemon/ # 后台守护进程 + stealth 反检测模块 └── cli/ # 命令行工具 ``` @@ -112,6 +112,34 @@ test: 添加 CLI 自动拉起单元测试 refactor: 重构 page-registry 为 Map 实现 ``` +## 反检测开发规范 + +这是 VisionL 最核心的质量要求。任何新功能不能降低反检测评分。 + +### 原则 + +- GUI 浏览器所有可被页面 JS 读取的属性,VisionL 都必须有真实值(或模拟值) +- 禁止暴露任何 `navigator.webdriver`、`--headless`、Playwright/Puppeteer 痕迹 +- 每个 stealth 模块必须先写测试,验证目标属性值符合真实浏览器 + +### 检测站点验证 + +每次版本发布前,必须通过以下站点验证: + +| 站点 | 指标 | +|------|------| +| https://bot.sannysoft.com | 全部绿条 | +| https://abrahamjuliot.github.io/creepjs/ | 所有维度分数 ≤ 30% 异常 | +| https://fingerprint.com/demo | 访客置信度显示为正常浏览器(非 bot) | + +### 新增 Stealth 模块 Checklist + +1. 确认需要覆盖的 JS API / 属性 +2. 在真实 Chrome GUI 中抓取基准值 +3. 实现模块,写入 `daemon/src/stealth/` +4. 写测试验证模拟值与基准值一致 +5. 在三个检测站点验证未被降级 + ## 发布流程 1. 功能开发在 `dev` 分支