Back to blog
·9 min read

I don't feel Anubis is a good solution

SecurityWebAnubisGPUsCryptography

I built a GPU solver on my MacBook Pro to crack Anubis challenges. It was able to solve these challenges in about 5 milliseconds (at challenge level 6). Fetching the challenge, solving it through a local HTTP service, submitting the result, and loading the protected page took about 19 milliseconds end-to-end.

Loading the exact same challenge page on the same MacBook Pro in Chrome took about 22s on average to solve. An iPhone 17 Pro Max took nearly 3 minutes. My solver is roughly 30,000x faster than a modern iPhone.

Who is supposed to struggle here?

Anubis explicitly positions itself as a response to scraping by AI companies. Its README describes protecting smaller sites from their traffic. That is the threat model I wanted to examine.

Everyone claims that the bots plaguing the internet are from AI companies scraping for training data. I'm not writing this article to debate this, but let's run under the assumption that this is the case. If the problem is companies building large AI models, those companies have access to extraordinary amounts of compute. Meta, for example, described two clusters containing 24,576 H100 GPUs each back in 2024.

I'm not saying that these GPUs are sat idle or that they're even the best choice for cracking proof-of-work challenges, but it does make me question if asking an ordinary visitor to win a computing contest against a determined organisation with that kind of engineering budget is a good idea?

How does Anubis measure proof-of-work?

For this experiment I used Anubis v1.28.2, protecting a static HTML page served by Nginx. I configured its fast proof-of-work challenge at difficulty 6 for every visitor.

Difficulty 6 is not the default challenge for every Anubis visitor. The bundled policy uses different responses depending on its suspicion score, including difficulty 2 and 4, with 6 in its highest configured tier. I deliberately selected 6 to see what a more expensive challenge would entail. Bundled policy

The task is to find a number, called a nonce, for which:

SHA256(challenge_data + decimal_nonce)

starts with the required number of hexadecimal zeroes. Six zeroes means 24 zero bits, so a fresh hash has approximately a 1 in 16,777,216 chance of working. Each extra difficulty level multiplies the expected work by 16. Validation implementation

There is no requirement that the browser itself performs that work. The server receives a solution and checks it. A client can obtain the challenge, send the computation elsewhere, and submit the answer while keeping the appropriate cookies and connection identity.

I didn't break SHA-256, predict a clever nonce, or exploit a validation bug. The solver did the work Anubis requested, using an implementation much better suited to doing it quickly.

Hashcat wasn't enough

I started with Hashcat. Its normal SHA-256 mode looks for an exact target hash, so I made an isolated copy with a small kernel modification to accept six leading zeroes instead.

On the Apple M5 Max's 40-core GPU, that version reported about 382–393 million hashes per second. It produced solutions which Anubis was very happy to accept. This is already 400x faster than the same laptop through Chrome in terms of pure hashing speed.

However, launching Hashcat for each challenge still took about 1.72 seconds on repeat runs, this is due to initialisation and autotuning.

As a proof-of-concept, I built a persistent native Metal worker, with a local HTTP API and a bounded queue in front of it. It keeps the compiled GPU pipelines and buffers ready between jobs.

There was another useful optimisation. These challenges contain 128 fixed ASCII bytes, followed by the nonce. SHA-256 processes 64-byte blocks, so the first two blocks are identical for each challenge. The worker computes these starting blocks once, then lets the GPU search by processing just the final block containing the nonce and padding. This optimisation was able to reduce how long each hash took by around 66%.

The measurements

Here are the final results on the same laptop:

Test Average time 95th percentile # of Challenges
Custom GPU solver, hashing the whole message 15.97 ms 44.03 ms 100
Custom GPU solver, reusing the fixed prefix 5.19 ms 14.24 ms 100
Warm GPU solver over local HTTP, one client 6.03 ms 15.19 ms 200
Complete local Anubis flow 19.02 ms 26.85 ms 20

A longer test solved 3,000 distinct challenges in 17.03 seconds, or 176 challenges per second, with eight clients feeding the queue. Each solution was checked against Anubis which accepted them all. These load-test challenges had the same format and difficulty, but were generated locally rather than fetched from Anubis.

Comparing these numbers to how long the browser takes to solve the challenge is stark. As aforementioned, it took around 22 seconds to solve the same challenge difficulty on the same laptop in Chrome. The challenge page reports 750-760 thousand hashes per second on my MBP on Chrome, and when testing on an iPhone 17 Pro Max, the same challenge page reported 100-110 thousand hashes per second (which took nearly 3 minutes to solve). My solution reaches 3.1~ billion hashes per second and is more than 30,000x faster than a modern iPhone at solving Anubis challenges.

I am not sharing the source code for this for obvious reason.

Turning it up hurts the wrong people

Moving from difficulty 6 to 7 multiplies expected work by 16. At the browser rate I observed, that's roughly 40+ minutes of average computation on my iPhone. At the GPU throughput I measured, it would be roughly 65 ms of computation.

Increasing difficulty preserves the hardware advantage. It doesn't make a legitimate visitor and a prepared scraper equally accountable for paying the cost.

Meanwhile, if the protected page takes tens or hundreds of milliseconds to fetch, a 5 ms computation can be a relatively small addition. A scraper can overlap network requests with other work and let a shared solver handle new challenges. Once it has an eligible cookie, that challenge cost disappears from subsequent page fetches until it needs authorization again.

So what do we do?

Everything said thus far has been running Anubis under its default settings (except for the difficulty). SHA-256 is a terrible choice for a proof-of-work challenge when your adversary literally has mountains of GPUs. There are alternative proof-of-work algorithms intended to make this kind of acceleration harder, and Anubis supports them out of the box.

Anubis supports both Argon2id and HashX. I benchmarked both on the same MacBook Pro to see how much they change the advantage I exploited in this article. Here are the average solution times:

Algorithm Difficulty Chrome Native CPU GPU
Argon2id 6 bits 233.45 ms 86.85 ms 83.39 ms
HashX 20 bits 191.06 ms 8.82 ms 9.78 ms

As you can see, the difference between Native CPU and GPU is almost entirely gone. However, running the algorithms outside of Chrome is still more performant so there is an advantage is having an external service tackle these challenges especially for HashX.

Okay, but surely everyone who uses Anubis uses Argon2id or HashX right? No one would use the default fast algorithm which uses SHA-256 right?

anakin and padme meme

On the 24th of October 2026, I sampled 15 public websites that had publicly announced using Anubis. Fourteen returned readable challenge settings, and all fourteen used SHA-256.

Site Algorithm Difficulty Anubis version
Linux kernel Git SHA-256 5 1.26.2
Linux mailing-list archive SHA-256 4 1.26.2
FFmpeg Trac SHA-256 2 1.28.1
FreeBSD SVN SHA-256 4 1.26.2
Sourceware Git SHA-256 4 1.27.0
GCC Bugzilla SHA-256 4 1.27.0
freedesktop.org GitLab SHA-256 2 1.27.0
OpenWrt SHA-256 2 1.22.0-20250926
FreeCAD Wiki SHA-256 4 1.28.1
ScummVM Wiki SHA-256 3 1.27.0
Hosted Weblate SHA-256 2 1.27.0
Valve Developer Community SHA-256 4 1.23.1
NixOS Hydra No challenge — —
Haiku development tracker SHA-256 2 1.28.2
Purism GitLab SHA-256 4 1.28.1

It has to be said, I was genuinely quite gobsmacked to see this. 14 out of the 15 sites I checked all used the SHA-256 algorithm, and the one outlier didn't challenge me at all. This particularly stunned me considering I have read articles by some of these site owners who have expressed their disbelief that an AI company would actually spend compute solving these challenges. Of-cause the bot scrapers would, it's trivial to do when the algorithm is SHA-256!

For anyone reading this that is using Anubis, change your settings!!

Can I also just take a detour to mention that it's a terrible idea to literally advertise what version of software you're using. If Anubis were to have a security vulnerbility disclosed (which it has), it would be incredibly trivial to make a bot to search for sites which run the affected version and exploit it. When I say vulnerbility I'm not talking about something insubstanial like the ability to bypass the bot protection. Anubis is a piece of infrastructure that you will need to run somewhere, and if Anubis is vulnerable, it could be used as a way in to pwn your whole server. I could not see any setting in Anubis to disable exposing the Anubis version, so I'll let these site owners off the hook for this one.

My stance on the whole thing

I actually think Anubis is a pretty cool piece of software and I reckon if used properly it could probably deter some of these bots. However, in the wild it's clearly misconfigured, and going back to my point at the very top, you're still choosing to have an AI company play a compute tug-of-war with a visitor who is likely on a second-hand mobile with a cracked screen that they've had for 8 years. I think you're likely to piss off your users before you deter the AI companies (especially if you use the SHA-256 algorithm).

The HTTP service that I had Codex help me build for this article, the one which literally cracks Anubis challenges at a very impressive speed. GPT-6 Astra built that on medium reasoning without Daybreak access. This is what someone without access to any of the models with reduced cybersecurity guardrails can do. I also was not being coy with my prompts to Astra to try and get around any guardrails. I would probably work under the assumption that the scrapers are doing exactly this to get around Anubis.

I'm also not a security researcher, I just have an interest in security, and I've been building websites for a long time. Feel free to take all of this with a pinch of salt. Remember to drink water. Have a nice day!