feat(browser): B1 接线 + B2 薄层工具面(V5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- browsercfg 接进组合根:配置读 $DATA_DIR/config/plugins/browser.json(零 env)→ 探测(本机
  Chromium → 已在跑的 CDP)→ 不可用就不注册(原因码入日志)
- 自研最小出站 CDP 客户端(不 Enabled Runtime、隔离世界求值、不发 --enable-automation)+
  导航 + 截图 0600 落盘 + 指纹自检;工具 browser(open/screenshot/info,owner-only)
- 3 个变异自检(启动恢复 --enable-automation/无视可用性/空闲回收失效)
- 真机对照(两站各 1 次,**如实结论**):两侧都 pass ⇒ 「headless 被拦 / headful 通过」**未被证明**;
  但测到两处硬差异:无头 UA 带 HeadlessChrome、screen 800 vs 2632;两模式 webdriver 均 false
  (把 L6 的推断变成实测)。跑完 0 残留 Chrome、无对外监听端口
⚠️ 未做:MCP 自动接入(口径委派未落地)/自动注入上下文/面板页/搜索降级/docker 配方/Windows 用
  pipe(Go ExtraFiles 不支持);SSRF 落了第三份同策实现待收口;浏览器会话未挂进程关闭链
This commit is contained in:
AskaEth committed 2026-10-06 20:25:16 +08:00
1 parent ea150ceea9
commit f1fbc249fd
21 files changed
+3579

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
package browser
import "context"
// Fingerprint 是一次「这台浏览器到底长什么样」的自检读数,对应
// `DevDocs/reference/browser-stealth/10 §4.1` 的落地自检项 L2~L5。它存在的意义是
// **可证伪**:有头到底有没有生效、webdriver 有没有被点亮、屏幕/时区/语言与设定是否
// 一致 —— 全靠这组**页面自报**的数字,而不是我们以为的启动参数。
type Fingerprint struct {
// Headless 来自本次会话的启动参数(我们有权威来源:参数是我们自己拼的),
// 不靠嗅探 UA —— `--headless=new` 的 UA **不含** Headless 字样(reference/05)。
Headless bool `json:"headless"`
// Webdriver 是 `navigator.webdriver`:false 才正常(我们不传 --enable-automation)。
Webdriver bool `json:"webdriver"`
// UserAgent / Platform 是页面自报的 UA 与平台串(原样,不做任何改写)。
UserAgent string `json:"userAgent"`
Platform string `json:"platform"`
// Language / Languages 是页面自报的语言列表(应与启动的 --lang 一致)。
Language string `json:"language"`
Languages []string `json:"languages"`
// Timezone 是 `Intl.DateTimeFormat().resolvedOptions().timeZone`(应与出口 IP 同源)。
Timezone string `json:"timezone"`
// ScreenWidth / ScreenHeight / ColorDepth 是 `screen.*`(应与 Xvfb 参数或真实桌面一致)。
ScreenWidth int `json:"screenWidth"`
ScreenHeight int `json:"screenHeight"`
ColorDepth int `json:"colorDepth"`
// OuterWidth / OuterHeight 是窗口外框尺寸:有头下应 > 0(无头常见 0)。
OuterWidth int `json:"outerWidth"`
OuterHeight int `json:"outerHeight"`
// DevicePixelRatio / HardwareConcurrency 是两条常见的指纹量。
DevicePixelRatio float64 `json:"devicePixelRatio"`
HardwareConcurrency int `json:"hardwareConcurrency"`
}
// fingerprintExpr 一次求值取回整组读数:分多次往返会让页面中途重定向后的读数互相
// 矛盾(一半是旧页、一半是新页)。它只在**隔离世界**里跑(session.evaluate)。
const fingerprintExpr = `(() => ({
webdriver: navigator.webdriver === true,
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
languages: navigator.languages,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
screenWidth: screen.width,
screenHeight: screen.height,
colorDepth: screen.colorDepth,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight,
devicePixelRatio: window.devicePixelRatio,
hardwareConcurrency: navigator.hardwareConcurrency
}))()`
// readFingerprint 读一次页面指纹。Headless 取自会话的启动参数,其余取自页面自报。
func readFingerprint(ctx context.Context, s *session) (Fingerprint, error) {
fp := Fingerprint{Headless: s.headless}
if err := s.evaluate(ctx, fingerprintExpr, &fp); err != nil {
return Fingerprint{}, err
}
return fp, nil
}