XEY LABS — MYTHIC / 003 EN/ ID/ 中文/ 日本語/ 한국어 ← BUILD LOG PROJECT REPO ↗ GITHUB ↗

M1 完成:网络信号、Worker 求解器、源站 SDK。

M1 是"自适应"不再是 Mythic README 里的形容词、而成为机制的里程碑。此前服务端只有一件给滥用 定价的工具 —— 工作量证明难度。M1 加入了让它能对谁在发问 定价的信号:JA4 TLS 指纹和 ASN 信誉输入一个带上限的风险引擎, PoW 求解器搬离主线程,源站现在可以在 Node.js 和 Python 里原生 验证裁决。提交 bc87ed9 至 c7e4fc3。

本文在一次内部审计发现了原始"M1 完成"声明中的七处缺口后修订。 六处已在提交 067e9f7 中修复;一处仍诚实地敞开,下文会写明。 修正记录在此公开,而不是被埋起来。

为什么要信号 —— 以及为什么只做参考

反机器人的成败取决于信号,而每个孤立的信号都很弱。任何愿意费事的 客户端都能逐字节鹦鹉学舌一个 JA4 指纹。ASN 只是启发式 —— 今天是 数据中心,明天是住宅代理。把任何一个当作真理,你就是在用额外的 步骤建一个黑名单。

所以 M1 把每个网络信号都当作参考: 小而硬上限的贡献,可以提高一个请求的代价,但永远不能单方面放行 或定罪。流程是这样的:

JA4 指纹:有折扣、有上限、不被信任

ADR-0010。 服务端从 X-Mythic-JA4 读取 JA4 指纹,该头由终止代理 设置 —— 绝不直接来自客户端,因为客户端给出的任何值都受攻击者 控制。指纹与已知浏览器 JA4 模式匹配;命中后应用一个小的风险折扣, 上限让它无法单凭自己清掉一个挑战。格式错误的值和只有前缀匹配的 值会被完全排除在折扣路径之外(a8ce95a)。所有上限在 启动时校验;敌对的调参值拒绝服务(8f560bd)。

写明的限制:JA4 只是参考。逐字节鹦鹉学舌真实浏览器指纹的客户端 在这一层无法区分。折扣很小,正因为信号可伪造 —— 它只提高了朴素 脚本的成本,仅此而已。

ASN 评分:失败时关闭的启发式

同一个 ADR。客户端 IP 经 MaxMind GeoLite2-ASN 数据库 (MYTHIC_ASN_DB)解析为自治系统号。数据中心、托管和 VPN 出口 ASN 贡献风险;住宅 ISP ASN 不贡献。查找处处 nil 安全: 空的 MYTHIC_ASN_DB 会干净地禁用该信号,而已配置却 打不开的路径会在启动时致命。配置错误时关闭失败;缺席时按设计 静默降级 —— 因为那是运营者主动选择不启用。

审计修正:原始测试只覆盖了失败模式(nil、空路径、 坏路径、非法 IP)—— 从未真正跑过一次正向查找。 067e9f7 增加了 TestASNLookupPositive, 用仓库内生成的合成 .mmdb 夹具(非授权 MaxMind 数据) 钉住正向路径:数据中心网段得分,住宅网段不得分。

Web Worker 求解器:PoW 搬离主线程

SHA-256 暴力求解以前跑在主线程 —— 难度 24 以上页面会冻结数秒。 SDK 现在提供 solveInWorker(),把自包含的求解器 序列化成 Blob URL 在子线程跑。不需要单独部署 worker 文件; 不需要打包插件。进度回调每 2048 次迭代触发一次; AbortSignal 终止 worker 并回收 URL:

export async function solveInWorker(
  challenge: Challenge,
  opts: SolveInWorkerOptions = {},
): Promise<Solution> {
  // No Worker API (Node, SSR) → fall back to main thread.
  if (typeof Worker === "undefined") {
    const { solve } = await import("./pow");
    return solve(challenge, opts);
  }
  const workerCode = `(${workerMain.toString()})()`;
  const blob = new Blob([workerCode], { type: "application/javascript" });
  const url = URL.createObjectURL(blob);
  // …postMessage challenge, await solution, revoke URL
}

MythicClient 接受 useWorker(默认 true),构造时自动启动行为采集器。一个客户端,一条 流程,不需要手工接线。

白纸黑字写一个已知缺口:worker 路径至今只跑在 Bun 的 Worker 实现上 —— 从未在 Chromium、Firefox 或 Safari 里跑过。这记录 在 sdk/js/README.md 里,不做其他宣称。曾尝试搭建 Playwright 套件;构建环境下载不到浏览器二进制文件。在此改变之前, 真实浏览器行为未经测试、也不宣称。

源站验证:Node.js 与 Python

决策令牌以前只能在 Go(server/xtoken)里验证。源站现在 拿到原生助手。sdk/js/src/verify.ts —— parsePublicKey(x) 把 JWKS 原始密钥包成 DER SPKI; verifyToken(pub, token) 在原始载荷字节上校验 Ed25519 签名(不预哈希),强制规范 base64url 重编码,并拒绝过期令牌:

// Canonical encodings only: re-encoding the decoded bytes
// must reproduce the input exactly. Go's base64 decoder
// silently discards unused low bits of a trailing character,
// so distinct strings can decode to the same token.
if base64.RawURLEncoding.EncodeToString(payload) != payloadPart ||
    base64.RawURLEncoding.EncodeToString(sig) != sigPart {
    return nil, fmt.Errorf("%w: non-canonical encoding", ErrInvalidToken)
}

sdk/python/mythic/verify.py —— 经 cryptography 包实现相同语义: parse_public_key、verify_token、 TokenClaims 数据类,任何拒绝都抛 TokenError。Python SDK 已进 CI (.github/workflows/sdk-python-ci.yml),并跑在 make test 下 —— 此前没有任何东西自动执行它。

已跨语言验证并提交:e2e/crosslang.sh 从实时 Go 服务器 签发令牌,在 Go、Python、JS 三端逐一验证出一致结果 (dec=allow risk=6,三端相同)。

证据

套件结果
go build ./... · go vet ./...干净
go test -race -count=17/7 个包,0 数据竞争
bun test (JS SDK)17/17
tsc --noEmit · bun run build干净
pytest (Python SDK)6/6
e2e/crosslang.shGo、Python、JS 在一个实时令牌上达成一致

评分契约本身被钉在代码里 —— 敌对调参无法把超范围的分值泄漏进 签名令牌(红队发现 H3,已回归测试):

// The score is a 0-100 contract (token claim `risk`,
// dashboards, tuning docs). Hostile tuning must clamp
// here, not leak 500 into tokens (red-team H3).
if score > 100 {
    score = 100
}
if score < 0 {
    score = 0
}

审计改变了什么

在原始"M1 完成"声明之后,一次从零开始的审计(全新克隆、每个套件 重跑、对验证路径做对抗性检查)发现了七处缺口。提交 067e9f7("fix(audit): close all 7 M1 audit gaps") 关闭了其中六处:

  • ASN 正向测试已补(以前只有失败模式)。
  • 行为提示现在在 verify() 时发送 —— 此前采集器在挑战结束后就停了,导致服务端的 verify 阶段行为解析实际成为死代码。
  • Python SDK 已加入 CI 和 make test。
  • 跨语言 E2E 已提交(e2e/crosslang.sh)。
  • docs/ROADMAP.md 已修正 —— 它还把已完成的 M1 事项标为计划中。
  • 一个误导性的 worker 测试名已修正(它声称测的是回退路径,实际测的是真实 worker 路径)。

第七处 —— Web Worker 的真实浏览器测试 —— 仍敞开,作为已知缺口 记录,而不是宣称已完成。这就是 M1 当前的诚实状态。

开放问题

真实浏览器 worker 覆盖是上面唯一敞开的事项。除此之外:IP 信誉源 仍在计划中(ROADMAP,诚实地标着),M2 行为生物特征和 M5 跨节点 验证共享密钥库仍是下一个里程碑。按项目规则,每个新信号的绕过 方式都要在信号上线前先写进文档 —— 见 ADR-0010 的"Bypasses"节。

— XEY LABS