Outspeeding Anubis: Native Proof-of-Work Solving in browselenium
October 2, 2026 · browselenium.online engineering
Anti-bot challenges used to be simple: display distorted letters, ask the user to click fire hydrants, or run a JavaScript snippet in an invisible iframe. Modern edge defenses, however, are increasingly turning to asymmetric cryptographic proof-of-work (PoW).
One of the most notable open-source edge defenses in this category is Anubis (developed by Xe Iaso / Techaro). Instead of relying on privacy-invasive third-party heuristics, Anubis sits in front of HTTP endpoints and forces suspected clients to find a SHA-256 hash collision satisfying a target difficulty before allowing the request through. The server specifies the rules; the client burns CPU cycles.
For automated coding agents, Anubis interstitials introduce crippling latency. Waiting for a sandboxed browser tab to instantiate WebAssembly workers, allocate SIMD buffers, iterate millions of hashes, and post messages back to the main thread can stall an agent for 3 to 10 seconds per navigation. Here is how we reverse-engineered the Anubis challenge surface and replaced the in-browser WASM loop with a native, hardware-accelerated solver that clears the challenge in tens of milliseconds.
How Anubis Challenges the Browser
When an incoming HTTP request triggers an Anubis challenge, the server returns an HTTP 200 or 403 page containing a lightweight HTML template. Embedded directly inside the document are configuration payloads formatted in <script type="application/json"> tags:
<script id="anubis_challenge" type="application/json">
{
"rules": {
"algorithm": "sha256",
"difficulty": 16,
"reportPerformance": false
},
"challenge": {
"id": "e208c027-1fba-4a6c-94cf-6e729a67e997",
"method": "sha256",
"randomData": "aeb7286877d8a7bcd30b24b48fd4f4072a1ee909eb78fa92..."
}
}
</script>
<script id="anubis_base_prefix" type="application/json">""</script>
<script id="anubis_public_url" type="application/json">""</script>
The page scripts (main.mjs, wasm.mjs, and sha256.wasm) inspect client capabilities and dispatch the proof calculation across three possible challenge protocols:
- Modern WASM (
sha256): The browser instantiates WebAssembly workers running SIMD-accelerated SHA-256. The hash input is formed by taking therandomDatahex string, decoding it into raw bytes, and appending a 32-bit little-endian unsigned integer nonce:SHA-256(hex_bytes(randomData) + pack("<I", nonce)). Difficulty is expressed in leading zero bits. - Legacy WebWorker (
fast/slow): Older or fallback configurations run a JavaScript worker. The input is formed by concatenating the raw ASCII salt string with the decimal string representation of the nonce:SHA-256(randomData + str(nonce)). Difficulty is expressed in leading zero nibbles (hex characters). - Meta Refresh (
metarefresh): For low-suspicion requests or staging endpoints, the server emits a zero-work challenge with<meta http-equiv="refresh" content="0; url=...">pointing directly to an issued pass token.
The In-Browser Bottleneck
Why not just let the browser's native JavaScript and WASM solve the challenge? In headless automation environments, browser-side execution suffers from several compounding overheads:
- Cold Worker Boot: Spawning 4 to 8 Web Workers inside a fresh headless context requires script compilation, V8 worker thread creation, and WASM memory allocation. This startup phase alone costs 200–500ms before a single hash is computed.
- IPC and DOM Overhead: Browser workers must pass ArrayBuffers and post messages across thread boundaries, marshal responses, and update DOM indicators ("Validating...").
- Thread Contention: When headless Firefox is already rendering pages, running multi-threaded WASM workers can peg the browser's internal task queues, triggering tab watchdog pauses.
On a difficulty of 16 bits (standard production setting, requiring 65,536 average hash attempts), the in-browser WASM loop typically takes 1.5 to 3.5 seconds. On higher difficulties, it frequently stretches beyond 6 seconds.
The Native Bypass Architecture
To eliminate browser overhead, browselenium.online implements a direct, host-level interceptor. When a navigation completes, the session checks whether an Anubis interstitial is present:
# 1. Parse JSON configuration directly from page source
challenge = parse_anubis_challenge(page_source)
# 2. Compute the valid proof in native Python/OpenSSL C routines
nonce, digest, elapsed = solve_anubis_pow(
challenge.random_data,
challenge.difficulty,
timeout=budget,
)
# 3. Submit directly to Anubis pass endpoint in the active session
params = {
"id": challenge.challenge_id,
"response": digest,
"nonce": nonce,
"redir": target_redir,
"elapsedTime": str(int(elapsed * 1000)),
}
driver.get(f"{pass_url}?{urlencode(params)}")
Hardware Acceleration
Python's native hashlib.sha256 delegates directly to OpenSSL's C implementation. On modern x86_64 CPUs, OpenSSL uses dedicated Intel/AMD SHA-NI hardware instructions (or AVX2 vectorization), capable of sustaining over 4 to 7 million SHA-256 hashes per second per core.
Thread Parallelism Without Forking
In multi-tenant Python server environments, spawning subprocesses via multiprocessing can cause descriptor leaks, startup latency, and crashes on modern Python runtimes (like Python 3.14's forkserver model). Because OpenSSL releases Python's Global Interpreter Lock (GIL) during hash digests, we utilize lightweight Python worker threads (threading.Thread). This scales cleanly across host cores with zero fork overhead.
Benchmark Results
We benchmarked the native solver against Anubis challenges across varying difficulty levels on a standard Linux x86_64 host (Intel Core i7 / AMD Zen):
| Difficulty | Expected Hashes | Single Core | 4 Threads | Browser WASM |
|---|---|---|---|---|
| 16 bits (typical default) | 65,536 | 0.066s | 0.042s | 1.8 – 2.5s |
| 18 bits | 262,144 | 0.555s | 0.198s | 3.2 – 4.5s |
| 20 bits | 1,048,576 | 0.635s | 0.380s | 6.0 – 9.0s |
| 22 bits (high suspicion) | 4,194,304 | 1.128s | 0.613s | 15.0s+ (timeout) |
At standard production difficulty (16 bits), native solving cuts the proof phase to under 70 milliseconds — a 25× to 40× speedup over waiting for the browser.
Live Target Verification
We tested the solver against live Anubis endpoints, including https://anubis.techaro.lol/. When loaded with existing session verification cookies, Anubis issues a challenge which our solver immediately detects, solves, and submits. The server verifies the proof, sets the session cookie (*-anubis-cookie-verification-*), and redirects to the target page with zero user intervention.
Multi-Tenant Safety & Caps
In hosted environments serving autonomous AI agents, uncontrolled CPU-bound tasks can create Denial of Service risks. To ensure multi-tenant reliability, the solver enforces strict safety valves:
- Difficulty Ceiling (
MAX_WORK_BITS = 21): Any challenge demanding more than 221 expected iterations (~2.1M hashes) raises immediately rather than locking CPU cores. - Native Time Budget (
NATIVE_BUDGET_SECONDS = 8s): The solver search is hard-capped; if a solution is not discovered within the budget, it releases the threads. - Rejection Detection: If Anubis returns an error ("Oh noes! Invalid response"), the session halts immediately instead of wasting time polling stale frames.
Enabling Anubis Bypass in Your Account
Anubis bypass is enabled by default across all tiers. If you wish to disable auto-solving (for example, to inspect raw challenge interstitials during security testing), you can toggle it directly in your browselenium dashboard next to your proxy configuration, or specify it programmatically via the MCP tool parameters.
browselenium.online gives agents an undetectable cloud browser with native challenge solvers, residential proxies, and 32 FastMCP tools.
Get your API key