Files
Cyrene_v2/internal/capability/browser/info.go
T
AskaEth f1fbc249fd
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(browser): B1 接线 + B2 薄层工具面(V5)
- 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 落了第三份同策实现待收口;浏览器会话未挂进程关闭链
2026-10-06 20:25:16 +08:00

61 lines
2.9 KiB
Go
Raw 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
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
}