docs(anubis): record the challenge-flow contract and the JS-canary gotcha
The policy must ALLOW Anubis's own endpoints before the challenge-all rule or the challenge challenges itself: browsers POST-solve via make-challenge then GET-navigate pass-challenge, and a challenged answer renders as the browser's own fetch-failure error page. Non-JS clients never reach the verify step, which is why every curl probe looked green while every human hit a wall. Also records that v1.13.0's valid action verbs are ALLOW/CHALLENGE/DENY (ACCEPT crash-looped the unit on first deploy).
This commit is contained in:
@@ -28,12 +28,19 @@ graph LR
|
||||
| Policy | `dankclaw/anubis/policy.json` mounted read-only into the container; parsed at process start only (no reload signal exists — every policy change needs a unit restart) |
|
||||
| Store | memory (no signing keys needed); a restart invalidates issued challenge JWTs — users simply re-challenge once |
|
||||
|
||||
Policy content: DENY for AI-scaper user-agent regexes (GPTBot, Claude-*,
|
||||
Perplexity*, CCBot, Bytespider, Amazonbot, meta-externalagent, and friends),
|
||||
CHALLENGE for everything else (`path_regex: "."` catch-all), DNSBL off.
|
||||
Policy schema at this tag is JSON-only — no `(data)/` imports and no CEL;
|
||||
first match wins. Upgrading the pin = stop the unit, pull + re-tag the
|
||||
`localhost/` alias, restart; re-verify the policy schema against the new tag first.
|
||||
Policy content: an `ALLOW` rule for Anubis's own endpoints (the
|
||||
`/.within.website/x/cmd/anubis/*` API and static namespace — the challenge
|
||||
JS calls `make-challenge`/`pass-challenge` under that prefix, and challenging
|
||||
those endpoints makes the challenge challenge itself), then DENY for
|
||||
AI-scraper user-agent regexes (GPTBot, Claude-*, Perplexity*, CCBot,
|
||||
Bytespider, Amazonbot, meta-externalagent, and friends), then CHALLENGE for
|
||||
everything else (`path_regex: "."` catch-all). DNSBL off. Valid action verbs
|
||||
at this tag are `ALLOW`, `CHALLENGE`, `DENY` — `ACCEPT` is not one of them
|
||||
(first deploy of that rule crash-looped the unit on exactly that typo;
|
||||
verified against the image binary's string table). The policy schema at this
|
||||
tag is JSON-only — no `(data)/` imports and no CEL; first match wins.
|
||||
Upgrading the pin = stop the unit, pull + re-tag the `localhost/` alias,
|
||||
restart; re-verify the policy schema against the new tag first.
|
||||
|
||||
## Exempt by construction
|
||||
|
||||
@@ -67,6 +74,19 @@ ungated), and gitea (git smart-HTTP clients cannot solve JS challenges —
|
||||
5. **Deploy handler pull guard.** `deploy_anubis` guards the pull with
|
||||
`podman image exists`; a cold anonymous ghcr token request can 403 on the
|
||||
first fetch — retry once, the auth cache makes the second attempt local.
|
||||
6. **JS-capable clients are the canary.** Non-JS clients (curl, bots,
|
||||
scrapers) never solve challenges — they just receive the challenge page,
|
||||
so every curl probe looks green while the verification step is broken.
|
||||
The first JS-capable client to traverse a freshly deployed chain is the
|
||||
first real test of it: browsers POST-solve via `make-challenge` and
|
||||
GET-navigate `pass-challenge?response=<sha256(challenge+nonce)>&nonce=N
|
||||
&elapsedTime=T&redir=<path>`; if any own-endpoint path is challenged
|
||||
instead of allowed, the browser's fetch throws and it renders its own
|
||||
error page (iOS/macOS browsers phrase it as "the request failed and the
|
||||
interceptors did not return an alternative response") instead of the
|
||||
login flow. Verify with a JS-capable client end-to-end — mint via
|
||||
`make-challenge`, solve, submit with browser-exact query params — before
|
||||
declaring a challenge chain healthy.
|
||||
|
||||
## Verify live
|
||||
|
||||
|
||||
Reference in New Issue
Block a user