feat(browser): fixed Windows identity and per-site egress in umt-cookies plugin (BR-4)

- browser:launching hook generates a Windows fingerprint once (camoufox-js,
  os=windows), persists it to /root/.camofox/umt-identity.json and reuses it
  on every launch; Firefox/Camoufox upgrades only bump UA/version fields
- Windows fontconfig with absolute font dir (upstream path was broken)
- GET /umt/identity (public UA/OS/version) and PUT /umt/sessions/:id/proxy
  (per-context proxy; Firefox no longer uses container HTTP_PROXY env)
- adapter identity()/setEgress(), splitBrowserOnlyCookies for cf_clearance/__cf_bm

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 03:17:06 +08:00
1 parent 6663afce7e
commit ebd07c9230
8 files changed
+746 -5

No files matched your search

+23 -1
View File
@@ -1,4 +1,4 @@
import type { BrowserCookie } from "@umt/contracts";
import { BROWSER_ONLY_COOKIES, type BrowserCookie } from "@umt/contracts";
// BR-2 凭据统一:站点账号里存的是 HTTP `Cookie:` 请求头串("a=1; b=2"),浏览器 profile 里是带域名/路径/期限的 Cookie 对象。
// 这里只做两者互转与比较;值是凭据,不打印、不进错误信息。
@@ -34,6 +34,28 @@ export function mergeCookieHeaders(...headers: (string | null | undefined)[]): s
return jar.size ? formatCookieHeader(jar) : null;
}
/** 仅浏览器用的 Cookie(Cloudflare 人机验证):名字精确匹配,或 cf_chl_ 前缀(挑战过程中的临时 Cookie) */
export function isBrowserOnlyCookie(name: string): boolean {
return (BROWSER_ONLY_COOKIES as readonly string[]).includes(name) || name.startsWith("cf_chl_");
}
/**
* 浏览器 → HTTP 方向:剔除仅浏览器用的 Cookie(BR-4)。返回剩下的头串(没有剩余为 null)与被剔除的名字(不含值)。
*/
export function splitBrowserOnlyCookies(header: string | null | undefined): {
header: string | null;
browserOnly: string[];
} {
const jar = parseCookieHeader(header);
const browserOnly: string[] = [];
for (const name of [...jar.keys()])
if (isBrowserOnlyCookie(name)) {
jar.delete(name);
browserOnly.push(name);
}
return { header: jar.size ? formatCookieHeader(jar) : null, browserOnly: browserOnly.sort() };
}
/** 两个 Cookie 头串是否等价(忽略顺序与空白) */
export function sameCookieHeader(a: string | null | undefined, b: string | null | undefined): boolean {
const x = parseCookieHeader(a);