首页 > 开源 > 一个反爬工具花了一年才把 WebAssembly 装进去

一个反爬工具花了一年才把 WebAssembly 装进去

OSChina资讯 2026-09-07 15:29 3 阅读 查看原文

几百个 commit、五代 PR、几十个测试、把部分 Anubis 用 Rust 重写、职业生涯第一次遇到编译器 bug、至少三次把开发机跑爆内存。这是 Xe Iaso 给 Anubis 加上 WebAssembly 工作量证明所付出的代价。

Anubis 是 Techaro 开发的一个反爬/反机器人网关,挡在网站前面,访问者需要完成 PoW 挑战才能进入。它之前用的是纯 JavaScript 的 SHA-256 挖矿挑战,客户端和服务器各有一套实现——JavaScript 一套,Go 一套,改一个地方要同步改另一个。

Anubis WASM

新的 WASM 方案把 PoW 算法从 SHA-256 换成了 argon2id——一种内存硬哈希函数。这意味着「让 Claude 帮我写个 CUDA 求解器」这条路基本被堵死了。内存硬意味着并行化困难,GPU 的优势被削弱。同一个 WASM 二进制同时跑在浏览器和服务器上,客户端和服务器彻底锁步,不会再出现两边实现不一致的情况。

「同一个二进制跑两端」这个设计还有一个隐含的好处:Rust 编译的 WASM 二进制体积小得离谱,跑得飞快。Iaso 清楚地说了不打算把 Anubis 从 Go 重写成 Rust,但把 PoW 模块做成 WASM 之后,相当于给 Anubis 开了一个插件加载器——未来可以做到运行时动态替换挑战程序,威胁行为一变,无需重新编译整个 Anubis 就能应对。

顺带修了一个历史遗留 bug。Anubis 之前用 nibble(十六进制半字节)来算难度,难度加 1 意味着计算量暴增 16 倍。新方案改用 bit 粒度,难度调节终于变得合理了。

但真正让这篇文章值得读的,不是架构设计,是实现过程中遇到的坑。

Chrome 75。 Iaso 想降低最低支持版本,但大量 Android 手机、智能电视、老旧设备跑的就是 Chrome 75 且没有任何升级路径。所以 WASM 功能必须兼容 Chrome 75 的 WebAssembly MVP。这导致需要两套 WASM 构建——一套带 SIMD(Chrome 91+ 支持,移动端性能提升巨大),一套不带。

wasm2js。 有些客户端会因为安全策略禁用 WebAssembly(iOS Lockdown 模式、GrapheneOS 的 Vanadium 默认配置)。Iaso 想起了一个著名的演讲标题《The Birth & Death of JavaScript》,得出了一个可怕的结论——把 WASM 编译成 JavaScript。binaryen 的 wasm2js 工具能做这件事,但生成出来的 JS 体积巨大,而且不同发行版打包的 binaryen 版本不一致。Iaso 的解法非常粗暴:直接把 wasm2js 和 wasm-opt 工具本身编译成 WASM,带着可复现构建提交到仓库里。「你的构建工具没法跟我的不一样,除非我把构建工具直接发给你。」

职业生涯第一个编译器 bug。 在实现可复现构建的过程中,Iaso 发现每次构建 WASM 二进制都会偏移约 29 字节。排查了很久——包括禁用 ASLR 后发现同一台机器同一次启动内构建结果一致——最终确认是 LLVM 在迭代异常处理块时用了机器指针顺序,导致非确定性输出。这是 Iaso 职业生涯中第一次遇到货真价实的编译器 bug,一个做了很多年底层开发的人第一次怀疑「也许不是我的输入错了,是编译器有问题」。

文章里还有一个细节:Iaso 搭建了一套叫 chromesweep 的自动化浏览器测试框架,把 Chrome 75 到最新版全部拉进 Kubernetes 集群里跑,每个 Chrome 实例用 Kata Containers 微虚拟机隔离,NetworkPolicy 严格限制网络访问。然后说了一句:「我花在加固这些老旧 Chrome 上的精力,比大型 AI 公司花在加固 AI agent 测试基础设施上的精力还多,这让我感到恐惧。」

整篇文章读下来,你会看到一个维护者对付现实世界兼容性的狼狈与执着——从 2019 年的 Chrome、从可能跑在乌克兰智能电视上的浏览器、从一个用 WASM 编译 WASM 工具的套娃构建链。这不是一篇架构设计文档,这是一份战地报告。

参考来源: