Skip to content

Releases

v6.4.0 — 工程债一轮:审计 / 方法语义 / 契约补全 / 前端去重

Choose a tag to compare

@lza6 lza6 released this 09 Nov 02:27

工程债一轮。9 项工程待办与 5 项 UI/UX 残留全部落地;对外契约补上此前缺失的两类响应。

对外契约

  • /detect 有审计了:这个"替调用方访问任意站点"的动作此前一行审计都不留。现在成功 / 缓存命中 / 失败各落一行(event=detect),字段与 /solve 同族,GET /audit 直接可读。
  • HTTP 方法语义补齐:HEAD = GET 的头、无 body;OPTIONS → 204 + Allow;PUT/DELETE/PATCH/TRACE/CONNECT → 405 + JSON + Allow(原先是 http.server 的 501 + text/html,与"非 2xx 一律 JSON"的契约冲突)。带 body 的 405/OPTIONS 会先读掉 body,不会把同一条 keep-alive 连接上的下一条请求顶歪。
  • 两个新错误码:not-found(404) / method-not-allowed(405),进 ErrorCode 与 STATUS_BY_CODE,docs/API.md 与 openapi.json 同步。
  • OpenAPI 补服务端错误分支:/solve 的 500/502/503/504、/detect 的 500/503/504。新增门禁规则"非 2xx 响应码 ⊆ STATUS_BY_CODE",码表从 errors.py 正则提取(提取失败即报错,不静默通过)。
  • 清掉幻影参数:413 的提示曾指向从未实现的 --max-body-bytes,照提示操作会让 argparse 直接退出。改为"上限固定 65536 字节"。
  • Go 客户端:新增 SolveOptions{Want,Route,Sitekey} 与 SolveWithOptions();Clearance 补 Token/AWSWAFToken,产物校验按 want 选字段。SolveWithOptions 刻意不碰本地缓存——缓存键的前提是产物恒为 cf_clearance,而 token 类产物是一次性的。

内部

  • do_POST 的嫁接层旁路下沉为 multicaptcha.handle_compat_bypass()(纯函数,注入 check_target/entry_for),do_POST 227 → 200 行。
  • 两页(/ 与 /console)共用 solver/_page_common.py,收敛 token/getJSON/setKV/setBanner/setState/setEmpty;顺带修掉两份副本已漂移的行为(getJSON 统一到 text+parse,非 JSON body 也能显示原文)。
  • docs/facts.json 增 module_coverage 与 service_py_lines:engines.py 57%→100%、service.py 2073→3006 行两处陈年漂移清零。

验证

项 结果
Python 1065 收集 / 1058 passed / 7 skipped
覆盖率 solver/ 89.92%(门槛 80)、captchas/ 22.21%(门槛 20)、65 模块全部达标
Go 52 个 Test*,-race 全绿
门禁自测 21/21(新增 3 条负向样例)
两页 E2E(真 Chrome) 35/35
公开站 构建通过、CSP 17 HTML/20 hash、14 路由 axe 0、content 11/11
离线压测 hot 5483 QPS / p99 3.14ms / 0 错;cold 32 并发 0 拒绝;share 4 进程每轮只解 1 次

独立审查两轮:首轮 1 P1 + 1 P2 + 6 P3 全部成立并修复(Go 产物解析、keep-alive 错位、不可达的 404 声明、204 的 Content-Length、状态页空态 class、CONNECT、detect 早退耗时、容差注释),二轮复验 Approve。

证据:evidence/v64-engineering-debt.json、plans/证据/(截图与 site-verify)。

升级提示

  • 若你的接入方把"不支持的方法 → 501"写进了分支逻辑,需要改成 405(error_code=method-not-allowed)。
  • 未知路径在通过鉴权之后才返回 404;未带 token 仍是 401(不泄露路径是否存在)。

已知限制

  • do_POST 仍有 200 行,未达项目自定的 50 行标准;剩余部分是状态机本体(P3)。
  • CHANGELOG.md 的 v5.0 历史条目仍写着 --max-body-bytes(历史记录不追改)。
  • 真机动作(captchas/ 求解器实跑、真实 CF 站复验、stage2 快照)与公开站部署仍属待授权项,不在本次发行范围内。

v6.3.2 — 独立审查修复:3D 场景、移动端布局与门禁盲区

Choose a tag to compare

@lza6 lza6 released this 07 Nov 22:14

主题

独立只读审查发现的三处真缺陷,以及它们暴露出的门禁盲区。

修复

  • idle 3D E2E 恒真:重构时删掉了生产端 __sceneFrames 埋点,测试读到的永远是 0,改回 frameloop="always" 也能通过。恢复埋点,断言改为「指针移动必须产生帧、静止 1s 内新增帧 ≤ 1」。
  • 320/360px 下流水线 5 个按钮被说明卡遮挡:.scene__footer 改列布局,窄屏把控件排到画布下方;新增 360px 逐按钮 elementFromPoint + 点击断言。
  • frameloop="demand" 下指针视差永不运行:R3F 不会因 pointermove 调度帧,改为 canvas 上显式 invalidate(),并补 pointerleave 回正。
  • 场景标签数值错误:丢 valueMax、输出枚举单位(463bytes、72mb),改用与说明卡统一的格式化。
  • 站点版本与测试数漂移:站点 changelog 仍在 6.3.0、benchmarks 仍写 971;同步为 6.3.2 / 1030,并加 content 断言防止再漂移。
  • 门禁自测从未接进 CI:gates.selftest.mjs 自称「CI 里跑一步」,实际没有任何 workflow 调用,已接入 ci.yml。
  • importorskip 依赖不全 / 裸导入检查可绕过:补 Pillow,改用 ast.walk。
  • 覆盖率命令不一致:文档仍写 --cov=captchas(CI 已实证会被静默忽略),统一为 --cov=captchas/。
  • 占位符边界:补 localhost.、127.x.x.x、[::1]。

验证

门禁 结果
site-ci / ci 全绿
site e2e 19/19(新增移动端控件、指针触发 + 静止停止)
site content 11/11(新增版本/测试数同步断言)
verify:site 14 路由,axe 0
Lighthouse 桌面 100/100/100/100;LCP 0.3s、CLS 0、TBT 0ms
Python 1023 passed / 7 skipped
覆盖率 64 模块全过
门禁自测 18/18

已知限制

未变:真实托管头、真实低端设备、英文母语审校未做;dev 工具链 9 个 high 审计例外。

v6.3.1 — 公开站 V1.1 收口与 CI 覆盖率门禁修复

Choose a tag to compare

@lza6 lza6 released this 07 Nov 21:18

主题

公开站从"能读"变成"可核对",并把文档里声称的东西补齐;CI 的 captchas 覆盖率门禁真正跑起来。

公开站(site/)

  • 3D 场景可操作:3 个挑战面板 / 5 个流水线节点各配 44×44 DOM 按钮与 aria-pressed,
    说明卡显示实测值与条件;Backend 展开 cfsolve → pydoll → Camoufox → FlareSolverr。
  • 移除凭证自动旋转,frameloop="demand";E2E 断言静止 1s 内新增帧 ≤ 1。
  • 首页实测数据摘要 4 行 + /benchmarks/ 链接;Hero 三个数字改为引用 benchmarks.ts 的 id。
  • 导航栏滚动超过 24px 才加背景与 blur。
  • 构建期门禁:check-env.mjs 拒绝缺失/占位的 NEXT_PUBLIC_SITE_URL;
    check-out.mjs 断言 12 页 canonical/hreflang、sitemap、robots、JSON-LD、OG、占位残留。
  • OG 固化为 /og/{zh,en}.png,12 个可索引页全部带 og:image(此前只有 2 页)。
  • 本地预览构建(SITE_ALLOW_PLACEHOLDER=1)在页脚渲染提示。
  • test:e2e 18 项:运行时错误、键盘焦点环、reduced-motion、场景控件、static 保留控件、静止无自动旋转、CLS。

现有两页(/、/console)

  • 内联 base64 SVG favicon;真实 Chrome 不再因 /favicon.ico 404 报错(UI E2E 31/35 → 35/35)。
  • 单测 34 → 36。

工程与 CI

  • tests/test_detect_source.py 子进程输出改 UTF-8 解码(Windows GBK 崩溃导致 3 个测试失败)。
  • captchas 离线测试的可选依赖(opencv-headless / numpy / pillow / onnxruntime)进 requirements-dev.txt;
    缺依赖时 importorskip 而非红。
  • 覆盖率门禁改用路径形式 --cov=captchas/,避免模块名解析在 CI 上被静默忽略。

验证

门禁 结果
site-ci(Lint/Typecheck/Build/CSP/Content/Browser/Lighthouse) 全绿
ci(python 3.11 / 3.12 / go / findings) 全绿
Python 全量 1023 passed / 7 skipped
覆盖率 64 模块全过;solver 89.65%、captchas 22.21%
站点 e2e 18/18
Lighthouse 桌面 100/100/100/100;LCP 0.4s、CLS 0、TBT 0ms
包体 初始 232,745 B;3D 241,853 B

已知限制

  • 真实托管头未验证(部署按用户选择停在本地)。
  • 移动端 Lighthouse 仍是 11-06 数据。
  • dev 工具链 9 个 high 审计例外(@lhci/cli 链,不进产物)。
  • 英文文案未经母语审校。

v6.3.0 — 出口实测 / 审计可看 / 真机流量录制 / 出口进 UI

Choose a tag to compare

@lza6 lza6 released this 05 Nov 05:12

主题:补上两条断链 —— "出口到底是谁"和"挑战页到底在干什么"。

这两件事一直只能靠猜:

  • cf_clearance 与 (domain + 出口 IP + UA) 三者绑定,任何一个不匹配都是静默 403。
    但出口 IP 只能从 --proxy 参数和部署记录里"认为",池里登记的地址从没被实测过。
    填错了不会报错,只会表现为"这个代理莫名过不去"。
  • 挑战页在什么时机、按什么顺序、下发哪个 cookie,是这套项目最需要的第一手材料。
    而 scripts/capture_sample.py 走的是 urllib —— 能拿到 HTML 和脚本正文,但拿不到
    真实浏览器会话的 header 顺序与真实值、JS 设的 cookie、子资源时序、状态码序列。

参考:proxypool 的出口 tester 思路(A)、logdy 的"日志要能直接看"(B)、
mitmproxy 的流量 addon 架构(C,用 Playwright page.on 等价实现,零新依赖)。

A. 出口 IP 实测校验

python run.py egress --proxy URL [--proxy-egress-ip URL=IP](= scripts/doctor.py --offline --verify-egress)。

  • ProxyPool.entries_snapshot():内部只读快照,含 raw_url(校验出口必须用真地址)。
    这个值永不进日志、永不进 HTTP 响应 —— 池对外的 snapshot() 仍然脱敏。
  • parse_echo_ip():认三种回显格式(Cloudflare /cdn-cgi/trace 的 key=value 行、
    httpbin 的 {"origin": ...}、纯 IP 文本)。拿不到合法 IPv4 就返回空串,
    绝不把原文当 IP —— 否则回显页 302 到 HTML 时会把一堆标签当成"实测 IP"。
  • verify_egress():五路判断。只有登记值 ≠ 实测值才判红(那是缓存键错位、
    凭证发给错出口的源头);未登记 + 实测成功判绿并给出登记建议;探测失败不判红
    (代理挂了是另一件事)。
  • 刻意不写回:实测结果只报告,不自动改池里的 egress_ip。自动写回会把
    "登记错了"这种配置问题变成静默自愈,从此再没人发现出口换过。
  • doctor 默认不出站。整个工具只有显式加 --verify-egress 才发请求。

B. 审计日志可看:GET /audit?lines=N

  • solver/eventlog.py::read_jsonl_tail():尾部有界读取(AUDIT_TAIL_MAX_BYTES = 512KB,
    64KB 分块反向读)。最新在前;碰到写了一半的行就跳过(并发 append 时最后一行常是半截);
    返回 total_lines_estimate 与 truncated,让人知道"看到的是尾巴,不是全部"。
  • 端点要 token,lines 有上限钳制;没有审计文件时明确回"没有审计日志"而不是空数组。
  • 控制台新增审计区块:时间/事件/结果/耗时/说明。耗时按人读的方式显示
    (16.5 s,不是 16530 ms),失败行有标记。

C. 真实浏览器会话流量录制:scripts/record_traffic.py

python scripts/record_traffic.py --url <target> --out samples/x            # dry-run(默认)
python scripts/record_traffic.py --url <target> --out samples/x --real     # 真录
  --engine camoufox|chromium   --max-requests N   --wait-after SEC   --ua UA

产物:traffic.jsonl(一行一个请求/响应配对,新的在后)+ summary.json(按资源类型/
状态码/主机计数、时序、Set-Cookie 名清单、失败原因)+ manifest.json(元数据与预算)。

三条硬约束:

  1. 默认 dry-run,不传 --real 只打印"会去哪儿、会录什么",零网络;
  2. 请求预算 --max-requests(默认 60,硬上限 200,突破需显式 --allow-over-budget)。
    真机预算不可再生,所以预算必须严格;
  3. 凭证永不落盘:header 走白名单(X-* 一律不记);Cookie/Authorization/X-Api-Key
    只记"有没有";Set-Cookie 只记名字与个数,值一律不写。body 只量大小不落盘。

D. 出口状态进 UI 与控制台

  • 状态页新增「出口(代理池)」卡片:出口、egress_id、评分、连续失败、冷却剩余、
    成功/失败、出口 IP 是否已登记。
  • 控制台 /health 的代理行同步展示。
  • 三种"没有 proxies"分开说,否则就是谎报:未带 token(读不到)/带 token 且确实没配/
    服务端版本太老没有这个字段。

本轮自查发现并修掉的缺陷

# 位置 缺陷
1 record_traffic.py 录制预算被并发请求打穿:--max-requests 2 实跑录到 5 条
2 record_traffic.py 改成数 len(records)+len(pending) 仍录到 3 条 —— 真因是 resp.body() 切 greenlet 时本条响应还没入账;修法是放行瞬间自增的单调计数器 + 先入账再取可能切 greenlet 的东西
3 record_traffic.py headers_array 是方法不是属性(写成属性访问 → Set-Cookie 一个都收不到)
4 record_traffic.py headers_array() 返回全部 header 不只是 set-cookie(漏过滤 → ['28','text/html','Thu, 05 Nov…'] 被当成 cookie 名)
5 console_html.py 审计区块用 innerHTML + 一个不存在的 esc() → 整块永远显示失败;改成纯 DOM API
6 console_html.py 审计表没有 CSS → 时间列独占 414px、其余挤成一坨
7 statuspage.py 没有 token 时谎报"未配置代理池"(实际只是读不到)
8 statuspage.py setEmptyEl 写死 class "chips" → 代理卡片被套上 flex 布局
9 statuspage.py 代理表列贴死(70 与 3 粘在一起)→ 补 padding: 6px 12px
10 run.py 回显的命令行带出口口令(http://u:p@h:8080 会进 CI 日志/贴进 issue)
11 run.py / verify_ui_e2e.py ui --only 写错状态名甩 KeyError 栈;egress 的回显地址参数名是 --value(与 doctor 的 --egress-echo-url 不一致)
12 eventlog.py read_audit_tail 曾是 Handler 的 @staticmethod 却按实例属性调用 → AttributeError;移到 eventlog.py 作模块函数
13 console_html.py 顶部「刷新」只刷 /health,审计表停在旧数据 —— 同一页两个「刷新」一个全量一个局部,主操作竟是局部的
14 record_traffic.py --url https://u:p@host/ 把口令逐字写进 traffic.jsonl / manifest.json / dry-run 输出 —— 违反本模块自己的「凭证永不落盘」
15 service.py::_query_lines 完全不夹取:/audit?lines=100000 吐 3506 行 / 524KB(实测),而 API.md 承诺与 /logs 同一套 [0, 200] 规则。/logs 的夹取在 Solver.logs() 里,/audit 走的 read_jsonl_tail() 只有 max(1, lines),没有上界
16 两条断言 上面这条缺陷被两条空转断言漏掉:单测 fixture 只造 6 行、真实调用用的是刚建的空审计文件,lines=99999 无论夹不夹都 <= 200。改法是 fixture 造 MAX_LOG_LINES + 300 行并断言恰好等于上限

独立复审(第 1 轮,claude -p 独立进程,范围收窄到 4 个交付文件)

详见 plans/证据/independent-review-v6.3-r1.md。4 条真缺陷,全部已修 + 回归用例 + 变异验证:

# 级别 缺陷
R1 P1 nav_error 未脱敏:Playwright 的超时消息内嵌完整 URL,而"挑战页导航超时"正是本工具最常走的分支 → manifest.json 与 stdout 带出口令,traffic.jsonl 却是干净的
R2 P2 --engine camoufox 仍强行塞 Windows/Firefox UA:求解侧不加 UA(身份由 persona 决定,缓存键取 navigator.userAgent 实测值),于是"与求解时同一套指纹"在这一维上是假的
R3 P2 doctor.py 里任一 --proxy-egress-ip 匹配不上就 return → --verify-egress 静默不执行,报告里连"未实测"都不出现,读起来像"没问题"
R4 P3 item.partition("=") 按第一个等号切:代理口令含 =(base64 填充)时必然登记失败,还报"出口不在 --proxy 列表里"(它就在)。doctor.py 与 bootstrap.py 两处同源

复审另指出 /audit?lines=0 回 1 行而 /logs?lines=0 回 0 行与 API.md 的"同一套夹取规则"
不符 —— 已修(max(1, lines) → max(0, lines))。

这四条里有三条是同一个形状:承诺与主干路不一致(脱敏漏了最常走的分支、宣称同一套指纹
却改了那个维度、宣称实测却在参数写错时静默不测)。单跑一遍 happy path 永远发现不了。

第 1/2 项的回归测试用变异验证过:把实现退回任一旧版,三条预算用例当场变红。
第 13 项同样验证过:把「主刷新」改回不碰审计,audit/顶部刷新会带上审计区块 立刻红(rows=8,期望 9)。

验证

  • 本机 Python:964 passed / 7 skipped(总 971);solver/ 89.62%,23 个模块全部达标。
  • UI 截图验收:31/31,6 种状态(新增 audit)+ 按钮交互 + 逐张看片。
  • 真实调用验收:41/41(真进程 + 真 token;新增 /audit 的 401、尾读结构、lines 钳制与非数字容错)。
  • 真实 E2E:40/40;服务契约 golden 13 端点一致;openapi 14 操作与源码一致。
  • 录制真跑:本机 chromium 打本地「1 文档 + 1 CSS + 1 JS + 2 图(1 个 404)」站点,
    预算 1/2/3/5/6 各档严格、连跑 10 轮无越界;Set-Cookie 多值解析、
    凭证不落盘(含 --url https://u:p@host/)、白名单外 header 不落盘均实测确认。
  • 两个引擎都真跑过,并且 manifest.user_agent 记的是实测值:
    chromium → HeadlessChrome/151;camoufox → Macintosh; Intel Mac OS X 10.15 … Firefox/156。
    (旧默认硬塞的是 Windows NT 10.0 … Firefox/156 —— 与实际 persona 自相矛盾。)
  • 出口校验真跑:本地模拟回显代理,一致 → ✓、不一致 → ✗ FAIL(exit=2)、未登记 → WARN + 实测成功。
  • /audit 大文件真跑(876KB / 6000 行):lines=100000 由 3506 行 / 524KB / 56.7ms
    收到 200 行 / 30KB / 3.3ms;lines=0 → 1 行;lines=abc → 回落默认 50。

v6.2.2 — 修 peak_rss 断言(内核惰性记账,不是 bug)

Choose a tag to compare

@lza6 lza6 released this 05 Nov 03:05

替换 v6.2.0 / v6.2.1。两者 CI 均为红(本机 Windows 全绿掩盖了平台相关缺陷)。按 policy.md §5 发补丁版,不移动历史 tag。

本次修的是什么

v6.2.1 的 CI 只红 python (3.11)(3.12 绿),失败断言:

assert 84922368 >= 84926464   # 差 4096 字节,正好一页

这不是 bug,是操作系统行为:Linux 的 ru_maxrss 按页记账惰性更新,会短暂滞后于 /proc/self/statm 报的当前 RSS。严格断言 peak >= rss 本身就是错的(3.12 绿、3.11 红也说明它时序敏感)。

两处修:

  1. 实现改为先读当前 RSS、再读峰值(ru_maxrss 单调,后读让窗口最小化);
  2. 断言改成"两者同一量级"(容差 1MB),注释写明不能要求严格 ≥。

三个 CI 红的共同点(比单条修复更值得记)

轮次 症状 本机为什么绿
1 monkeypatch.setattr(os, "name", "nt") → Linux 上 Path() 变 WindowsPath → pytest INTERNALERROR 本机 os.name 本来就是 nt
2 服务报告 golden 绑了本机研究产物(last_snapshot_at string vs null) 本机有快照
3 peak_rss >= rss 断言(内核惰性记账差一页) 本机走 Windows 分支,采样方式不同

三次都是"环境相关"。这也是本轮把「确定性报告 golden」做成门禁时必须补的那一课:
shape() 的 volatile 判断要排在类型判断之前,且刻意不收计数器
(它们类型恒定,标 volatile 会连"这里是数字"这条检查一起丢掉)。

v6.2 的主体内容(本版包含,详见 v6.2.0 说明)

四类验收全部脚本化,一个命令就能重来:

类型 命令 实测
UI 截图 python run.py ui 23/23(真 Chrome × 5 状态 + 按钮交互 + 逐张看片)
真实调用 python run.py realcalls 37/37
真实 E2E python run.py e2e 40/40(含真浏览器段)
服务契约 golden python run.py report 13 端点形状一致
真实代码复审 claude -p 独立进程 两轮,问题全部关闭

加上:engines.py 覆盖率 57% → 100%(撤销 waiver)、FlareSolverr 会话生命周期、
vendors.py 单一事实源(治"命中某特征 ≠ 有盾"这类反复复发的错误)。

验证

  • 本机:Python 905 passed / 7 skipped;solver/ 89.4%,23 模块达标;文档 9 处一致;openapi 13 操作一致。
  • CI:run 41956389364(commit 8214c8e)4 job 全绿。
  • Linux(WSL, 3.12):纯函数测试全绿;剩余失败经逐条核实全部源于 WSL 自身 loopback 不可用(连最小 ThreadingHTTPServer 都连不上),非代码问题。

v6.2.1 — 修两处 CI 红(本机全绿、只有 CI 抓到的平台相关缺陷)

Choose a tag to compare

@lza6 lza6 released this 05 Nov 02:29

⚠️ 已被 v6.2.2 取代(CI 红:平台相关缺陷,本机 Windows 全绿掩盖了它们)。按 policy.md §5 保留历史 tag 不移动。请使用 v6.2.2。

替换 v6.2.0。v6.2.0 的 tag 指向的 commit CI 是红的,按 policy.md §5
「同类情况默认按发补丁版处理」发本版,不移动 v6.2.0 的 tag。

修复 1:测试改全局 os.name 把 pytest 自己打崩

tests/test_observability.py 用 monkeypatch.setattr(m.os, "name", "nt") 模拟平台 —— m.os 就是全局 os 模块。pathlib 实例化时读到 name == "nt",在 Linux runner 上 Path() 变成 WindowsPath → NotImplementedError,整个 pytest 会话 INTERNALERROR。

同一类问题还有:monkeypatch.setitem(sys.modules, "threading", fake) 换掉了 pytest 自己也要 import 的模块。

修法:平台判定可注入(process_metrics(*, platform_name=None)),生产路径不变;线程探测改成 patch 真正的 threading.active_count。另修一条过强断言(datetime.max.timestamp() 在 Windows 抛 OSError、Linux 能算出巨大值)。

修复 2:服务报告 golden 绑在了本机研究产物上

~ 类型变化:...research.fields.last_snapshot_at  'string' → 'null'

last_snapshot_at 的有无取决于"这台机器是否跑过 scripts/research.py"——golden 实际上拿"有快照的机器"生成,那不是门禁,是环境指纹。

修法:shape() 的 volatile 判断提到类型判断之前(None 曾先返回 "null")。刻意不收计数器(solves/failures/assertions_*):类型恒定,标 volatile 会连"这里是数字"这条检查一起丢掉,等于自己削弱门禁。

新增

tests/test_service_report.py(13 例)把门禁的两个方向锁死:「环境差异不能进契约」与「真实漂移必须被抓」。

验证

  • 本机:Python 904 passed / 7 skipped;solver/ 89.4%,23 模块达标;文档 9 处一致。
  • CI:run 41954927353(commit 50253c6)4 job 全绿。

v6.2.0 的主体内容(本版包含)

四类验收全部脚本化:python run.py ui(23/23,真 Chrome × 5 状态 + 按钮交互)、realcalls(37/37)、e2e(40/40)、report(服务契约 golden)。详见 v6.2.0 的说明。

v6.2.0 — 四类验收全脚本化 + 两轮独立复审收口

Choose a tag to compare

@lza6 lza6 released this 05 Nov 02:04

⚠️ 已被 v6.2.2 取代(CI 红:平台相关缺陷,本机 Windows 全绿掩盖了它们)。按 policy.md §5 保留历史 tag 不移动。请使用 v6.2.2。

本 tag 指向的 commit(794fe80)CI 是红的:两处平台相关缺陷(测试改全局 os.name
让 pytest 在 Linux 上崩;服务报告 golden 绑了本机研究产物)。本机 Windows 全绿掩盖了它们。
请使用 v6.2.1。

保留本 Release 只为历史可追溯 —— 按 policy.md §5,tag 不移动。


主题

把「验收」从一次性快照变成可复跑的门禁,并修掉 v6.1/v6.2 交付后暴露的 UI 缺陷与两个公共决策函数的分叉。

四类验收:全部脚本化,一个命令就能重来

类型 命令 实测
UI 截图 python run.py ui 23/23(真 Chrome × 5 种状态 + 按钮交互 + 逐张看片)
真实调用 python run.py realcalls 37/37(真进程 + 真 token,全端点 × 鉴权两态)
真实 E2E python run.py e2e 40/40(含真浏览器段)
服务契约 golden python run.py report 13 端点形状一致
真实代码复审 claude -p 独立进程 两轮,问题全部关闭

UI:真 Chrome 看截图才发现的 6 条

state=wedged 曾渲染成默认深色徽章(服务已死却看起来正常);控制台把 wedged 当"异常"、丢掉"需重启";键值对因 class 被改而退化成上下堆叠;「上次快照」显示 Invalid Date;11 个扩展类型把卡片撑变形。

决策分叉:最严重的一条会让服务对几乎全部真实站点失效

把「命中某特征」当成「有盾/有厂商」——任何现代站点都用了 localStorage / clipboard / Intl,于是普通 CF 站被判"解不了"。这类错误在开发中被复现了三次,最终收敛成一个谓词 vendors.is_shield(category),并新增 solver/vendors.py 作为「厂商能力表」单一事实源。

工程治理

  • solver/engines.py 覆盖率 57% → 100%,撤销长期 waiver(门禁 56 → 95)。
  • FlareSolverr 后端补齐会话生命周期(sessions.create/destroy + 并发保护)。此前等于每请求泄漏一个远端 driver 进程。
  • tests/test_statuspage.py 新增 12 例:该模块此前零行为测试,100% 覆盖率是假象。

破坏性变更

  • detect.recommend() 的 unsupported_or_browser 语义收紧:命中里任何一家解不了即整页判不可解。
  • protected 只统计盾类(Anti-Bot/ANTIBOT/CAPTCHA)。
  • /health 状态词表从三态扩为四态(新增 wedged)。

v6.0.1 — 修 CI 红(Python 3.11 f-string 兼容)

Choose a tag to compare

@lza6 lza6 released this 04 Nov 08:44

ℹ️ tag 说明:v6.0.1 已从 bc1ccb3 移到 e413bbd(原 tag 指向的提交缺 run.ps1 转发改动,checkout 会得到不完整内容;按仓库 policy §5 的 tag 移动例外规则处理)。其后的 7f647b5 只是把这次发版与一次无效门禁尝试写进 workflow_status.md 的文档提交。

修 CI 红:run.py 跨行 f-string 在 Python 3.11 下是语法错误

无行为变更,纯兼容性修复。

修复

run.py::cmd_stop 里原本写成跨行 f-string:

print(f"强制终止 PID {pid}({'优雅退出未在 15s 内完成' if graceful
      else '无 token 或端点不可用'})")

f-string 内换行(PEP 701)是 Python 3.12+ 才允许的。CI 的 python (3.11) job
直接 SyntaxError: unterminated string literal → 测试与覆盖率门槛失败。

本机跑 Python 3.14,本地 652 passed 全绿,所以这个语法错误只有 CI 能抓到 ——
这正是 CI 存在的价值。已改为先算 reason 再插值。

防复发

对全仓 .py 跑 ast.parse(source, feature_version=(3, 11)):0 个不兼容文件
(本地 3.14 的 compileall 抓不到这类问题,用 feature_version 才能拦)。

验证

Python 652 passed / 7 skipped · 覆盖率 87.5% · 21 模块逐模块达标 ·
openapi 13 操作 · 文档数字 9 处一致 · findings 54 条 ·
CI run 41801357561 4 job 全绿。

v6.0.0 的功能内容不受影响,本版只是让它在 Python 3.11 上能跑。

v6.0.0 — 研究工程化(样本驱动、离线可复现的 CF 改版回归)

Choose a tag to compare

@lza6 lza6 released this 04 Nov 08:35

⚠️ 本 tag 的 CI 是红的:python (3.11) job 在 run.py 上报 SyntaxError(跨行 f-string 是 Python 3.12+ 才有,PEP 701)。功能内容不受影响,请以 v6.0.1 为准 —— 该版只修这一个兼容性问题,CI 4 job 全绿。

主题:研究工程化 —— 把「CF 改版就重新赌一把」变成「样本驱动、离线可复现的回归」

breaking change:无。 纯增量:新增 GET /console、响应头 X-Request-Id、
/health 的 research 段、/metrics 三条 cfsolve_research_* 序列。
既有字段、状态码、错误结构、鉴权语义均未改。

研究台(本版核心)

能力 命令 实测
构建前置自检(G0 门禁) research.py doctor EXIT=0,13 项全 PASS
编译 5 个离线分析 example research.py build EXIT=0 / 4m24s
结构断言矩阵 research.py assertions 真样本 pass=2 fail=1 skip=6(skip 有明确原因)
两份快照差分 research.py diff a b 报出 counter: pass → fail 并映射到补丁
VM 原语缺口 research.py gaps <id> 真样本 783 调用点 / 105 缺口 / 35 已实现
预算护栏 research.py run --max-sec 超时 2.023s 精确中止;200KB 输出不死锁
会话归档/脱敏 corpus.py add/list/verify/redact 真样本入库,redact_hits=0

其他新增

GET /console 单文件只读控制台(零依赖零构建、CSP、无 innerHTML)· X-Request-Id 贯穿
(响应头/envelope/审计/attempts,且绝不进 metrics label)· run.py 跨平台入口 ·
仓库卫生门禁 · detect 签名新鲜度 · dev 依赖 hypothesis + ruff。

Rust 侧(T-71/T-72)已落地

  • T-71 别名规则数据驱动化(src/alias_rules.json + include_str!),
    CFSOLVE_ALIAS_RULES=off 逐字恢复旧判定。cargo test --lib alias_rules 8 passed
    (含反向用例:obj[getKey()] 必须判动态)。
  • T-72 envelope.rs 拆成「候选集 → 逐个校验」,错误带候选/行号并保留原错误串为 detail。
  • 上游 cargo test --lib:245 passed / 1 failed;失败项经核实为 pre-existing 环境失败
    (src/capture/ 零改动、稳定失败 3/3),与本次改动无关。

本轮实测暴露并修复的 6 个真缺陷

反混淆产物名硬编码 5 处(真样本被拒收)· stage2 断言把「缺输入」报成 fail ·
doctor 不查 cmake/nasm/libclang · example 产物路径判断错(构建成功仍报 FAIL)·
中文 Windows 下乱码与 subprocess GBK 解码崩溃。

⚠️ 本版不含真机结论(必须读)

  • 真机 1 次(steamdb.info):反混淆成功,stage2 阶段内存吃满 24.6 GB 后按 PID 终止;
    样本仍脱敏入库。未拿到走到 stage2 的完整会话。
  • 因此 stage2 的 6 条结构断言在真样本上仍是 skip(既非 pass 也非 fail)。
  • 断言强度是字面量级(literal-substring),不是 AST 级:pass 只表示"该标记仍出现",
    不表示"结构形态未变"。此 caveat 已写进断言矩阵输出、差分报告与 /health。
  • 本版不承诺 stage2 对最新变体收敛 —— 只交付「收敛过程可视化」。

验证(全部真实执行)

Python 652 passed / 7 skipped · 覆盖率 87.50% · 21 模块逐模块门禁达标 ·
Go ok · openapi 13 操作双向一致 · 文档数字 9 处一致 · findings 54 条 ·
仓库卫生 exit 0 · 真进程 E2E 32/32 · 热更新演练 17/17 · /console 真浏览器 E2E PASS。

独立复审 3 轮(claude -p 独立进程,只喂 diff 不喂结论)

  • 第 1 轮 Request Changes:5 条 blocking(研究指标数据源 schema 错配、/metrics 三态伪造、
    脱敏闸门名不副实且 JSON 形态漏检、diff 判据回退、断言强度被夸大)——全部经复核成立并修复。
  • 第 2 轮 Request Changes:整篇正则脱敏会写坏 JSON/JS、assertions_total 语义错配致
    多变体时三态清零、run_wrapped 管道死锁、run.py stop 端口子串误杀。
  • 第 3 轮:关键项判定 FIXED,剩余 3 项(last_diff_at 取错文件、run 子命令参数被覆盖、
    rss 查询晚于 terminate)已修并实测。

v5.0.3 — 真机多站端到端确认 + 工具契约固化

Choose a tag to compare

@lza6 lza6 released this 04 Nov 02:12

主题:真机多站端到端确认 + 工具契约固化(无代码行为变更)。

真机验证(R-E2E-3,2 次目标请求,全部成功)

  • glassdoor.co.uk:求解 16.5s(12 条 cookie)→ 30s 后回放 200 / 735,957B / 真实标题 /
    前 2048 字节 0 挑战 marker
    —— 端到端成功。
  • 结合 R-E2E-2(udemy 端到端成功):『求解 → 缓存 → 回放真实内容』在两个不同站点上成立。
  • 累计预算 ≈58/80;累计失败仍 1/5。

新增

  • F-053:多站端到端结论(含"12 条 cookie 里大量是站点业务 cookie"的集成提示)。
  • F-054:工具契约 —— 「是不是挑战页」必须复用生产客户端的同一份实现
    (Go gocfsolve.LooksBlocked / Python verify_real.is_challenge),禁止另写子串匹配;
    验证脚本里的 cookie/凭证只允许存在内存。

验证

  • Python 542 / Go 46 全绿;20 模块逐模块门禁达标;12 个 openapi 操作一致;findings 54 条 exit 0。

🤖 Generated with Claude Code