
Anubis ships WebAssembly proof-of-work after a year of work
After a year of development, hundreds of commits and an LLVM bug hunt, Anubis moves to memory-hard proof-of-work written in WebAssembly, making CUDA-based solver farms far more expensive.
Anubis, the anti-bot gate that makes visitors solve a small puzzle before they reach a site, is about to start handing out a different kind of puzzle. According to its author, the change took a year: hundreds of commits, five generations of pull requests, dozens of tests, and rewriting part of Anubis in Rust. The result ships as a WebAssembly-based proof-of-work check that administrators can enable in their own thresholds and bot rules.
It first appears, switched off by default, in Anubis v1.28.0. Turning it on by default is planned for v1.29.0 and will depend on feedback from the people running it.
A memory-hard puzzle instead of a CPU-hard one
The new challenge uses argon2id, a memory-hard function, rather than the old CPU-bound hashing. The practical point is cost: solving these challenges at scale needs memory bandwidth as well as compute, which raises the price of the "port the solver to CUDA" route that scrapers have been exploring. Administrators attach the challenge to a specific rule, for example a "moderate suspicion" threshold at difficulty 6.
WebAssembly also lets the client and the server run the same binary instead of two separate implementations of one algorithm; until now the "fast" challenge existed twice, once in JavaScript and once in Go. The author compares it to the lockout chip in the Nintendo Entertainment System, where both sides run identical logic. The WebAssembly code is written in Rust (no-std, wasm32-unknown-unknown) because it produces small, fast binaries.
The details that ate a year: an LLVM bug and Chrome 75
Most of the delay came from details. Anubis still supports Chrome 75, because many smart TVs and cheap Android phones have no upgrade path. Rust's precompiled standard library turned out to use reference types, which older Chrome builds reject with "expected table index 0, found 128". The fix was to strip WebAssembly features with wasm-opt, starting from an MVP baseline and re-enabling only what Chrome 75 understands.
Building a deterministic WebAssembly-to-JavaScript tool uncovered an LLVM bug as well: the compiler iterated over exception-handling blocks in machine pointer order, so every build drifted by about 29 bytes. Disabling address-space randomisation made the drift consistent and confirmed the diagnosis; the bug was fixed upstream. To test old browsers, the author built a harness called "chromesweep" that launches many Chrome versions, published as the TecharoHQ/gubal library.
What is still unfinished
An escape hatch compiles the WebAssembly back to JavaScript for browsers that disable WebAssembly by policy, such as iOS Lockdown Mode or GrapheneOS's Vanadium; that path is slow and its progress bar is not wired up yet. Because the WebAssembly solver is very fast, difficulty settings may need retuning, and a classifier that uses IP reputation and TLS fingerprinting to give phones more grace is in progress.
SiTech — AI-powered web development
We build fast, modern websites and bring AI into real business workflows. Have a project or a question? We'd love to help.