Files
AskaEth 6fe68ecfac
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(browser): 方案 A 落地 —— 显式端口 + 去掉 stealth flag(主人 2026-10-07 拍板)
依据 `DevDocs/reference/chromium-cdp/`(9 份实测+官方源码):旧姿势传 `--remote-debugging-port=0`
会被 Chromium 当成 ChromeDriver 式自动化启动 ⇒ 主动打开 `AutomationControlled`
⇒ `navigator.webdriver=true`;那个 bad-flag 是承重覆盖,但代价是**每次有头弹横幅**。
改用显式端口后 `webdriver` **本来就是 false** ⇒ flag 变纯冗余 ⇒ 去掉它,**两者兼得**。

- `launch.go`:`--remote-debugging-port=<我们选的端口>` 替掉 `=0`;**删掉**
  `--disable-blink-features=AutomationControlled`(注释写明理由)。
- 新增 `portrecord.go`:端口来源与落点的**唯一出处** —— 取空闲端口(`net.Listen`
  取号后立刻关闭,**本进程不常驻监听**)、写/读/清**我们自己的记录**
  `<profile>/cyrene-debug-port`(选它与 Chrome 的 `DevToolsActivePort` 同目录:
  天然按 profile 隔离、profile 换掉记录自动消失、内容只有端口号不含凭据);
  **兼容老记录**(换二进制时上一版起的浏览器可能还在跑 ⇒ 认不出会把"自己的浏览器"
  误报成"profile 被别人占了");就绪判据换成「我们选的那个端口答得上 `/json/version`」;
  TOCTOU 处理 = 端口被占**换号重试一次**,重试仍失败则如实报错且不留记录。
- 三条认领路径(复用/清陈记录/如实报错保留现场)与三条失败分类(进程立刻退出 /
  等端口超时 / 预算耗尽)**全部保留**;`ErrLaunch` + `errors.Is` 口径未变;
  **绝不杀进程**红线未变。

真机自证(子代理跑,默认 Skip 的 `realchrome_test.go`,`CYRENE_BROWSER_CDP_E2E=1` 开闸):
`webdriver=false` · 命令行**无** flag/`--enable-automation`/`--headless` · UA 无
`HeadlessChrome`、`plugins=5`、`languages=zh-CN,zh`、`window.chrome` 在 ·
**UI Automation 控件树 17 个命名节点里没有「信息栏容器/信息栏」⇒ 无横幅** ·
第二次 Dial **复用同端口、浏览器主进程仍是同一个 PID** · 收尾按 profile 路径筛残留 = 0。

验收(主会话复跑 + 亲自变异):整树 `go build ./...` + 两包 vet 绿;browser 包测试绿;
我用 -overlay 把该 flag 加回参数表 ⇒ `TestLaunchArgs_HeadfulCarriesNoAutomationTraces`
变红(逐字报"不许传的 flag")。

⚠️ 未验(如实):无头档的横幅(无窗口判不了)、`chrome-headless-shell`、风控侧是否把
"端口写法"本身当信号、生产 profile 整链路(未碰)。
2026-10-07 11:18:25 +08:00

43 lines
3.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package browser 是 design/28(浏览器与页面截图)B2 的自研**薄层**:把「本机真
// Chromium 有头」跑起来,对它做**出站 CDP** 调用,并把页面截图按 0600 落到
// `$DATA_DIR/browser/shots/`,供既有 image/vision 管线读取。
//
// 为什么这么薄(AGENTS §9.5):
//
// - **工具面优先复用 MCP**:`goto` / `click` / `type` / `snapshot` / `extract`
// 这一整套成熟动作由 Playwright/Puppeteer 的官方 MCP server 提供(design/28 §0
// 「Playwright MCP / Puppeteer MCP」行、§2 结论);本包**不重造**它们。
// - **本项目独有的价值只有两处**:① 宿主真 Chromium 的**默认有头 + 持久 profile**
// 启动(design/28 §2 的过风控三件套);② 截图 → 落盘(0600)→ 进上下文/面板的
// 闭环入口。本包只做这两处,外加够它们用的**最小 CDP 客户端**。
//
// 运行形态(design/28 §2「选型修正」的主路径):
//
// 1. **本机真 Chromium 有头**(默认档,`browsercfg.DefaultHeadless=false`):
// 真显示 + 真 GPU/字体/时区,指纹最像真人;
// 2. **次选:已跑的 CDP 端点**(容器 + Xvfb + 真 Chromium,配置显式给出):
// 本包只**出站连接**它。
//
// 红线(design/28 §3/§4、AGENTS §9/§10):
//
// - **不新开对外监听端口**:调试端口是**我们选号、Chromium 自己绑在 `127.0.0.1`** 的
// 显式端口(`--remote-debugging-port=<port>`,端口号由 `listen(:0)` 借内核挑一个空闲号
// 后立刻放掉,并记在 `<profile>/cyrene-debug-port` 里供下次复用,见 portrecord.go)——
// 本包**不持有任何监听端口**,始终是**出站连接方**;容器路径同理(我们连它)。
// 为什么不传 `--remote-debugging-port=0`:Chromium 会把「端口=0」当作 ChromeDriver 式
// 自动化启动、主动点亮 `navigator.webdriver`(reference/chromium-cdp/00 §2)。
// - **profile 含凭据**(cookie / 登录态):目录 0700、不得位于 git 工作树内
// (`browsercfg.EnsureProfileDir`),内容绝不进上下文与长期记忆。
// - **截图可能含凭据/二维码**:文件 0600、默认不进长期记忆(design/28 §4)。
// - **CDP 层不自曝**:本包**不发** `Runtime.enable`(主流反爬用它判定「被自动化
// 控制」,见 `DevDocs/reference/browser-stealth/06`);`Runtime.evaluate` 一律走
// `Page.createIsolatedWorld` 出来的执行上下文。
// - **失败如实报**:探测不到就不注册工具;跑不起来 / 被拦 / 超限都返回**分类原因**,
// 绝不假装成功(design/28 §6 坑 5)。
//
// 已知未做(本片如实登记,见交付报告):
// - 面板「看一眼」入口(B4)、搜索降级档(B5)未做;
// - 除「导航 + 截图 + 页面指纹自检」之外的动作(click/type/extract/wait)**交给 MCP**,
// 本包不实现。
package browser