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:
1 parent
e10ebf3df4
commit
e006ad1f0f
1 file changed
+26
-6
@@ -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) |
|
| 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 |
|
| 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-*,
|
Policy content: an `ALLOW` rule for Anubis's own endpoints (the
|
||||||
Perplexity*, CCBot, Bytespider, Amazonbot, meta-externalagent, and friends),
|
`/.within.website/x/cmd/anubis/*` API and static namespace — the challenge
|
||||||
CHALLENGE for everything else (`path_regex: "."` catch-all), DNSBL off.
|
JS calls `make-challenge`/`pass-challenge` under that prefix, and challenging
|
||||||
Policy schema at this tag is JSON-only — no `(data)/` imports and no CEL;
|
those endpoints makes the challenge challenge itself), then DENY for
|
||||||
first match wins. Upgrading the pin = stop the unit, pull + re-tag the
|
AI-scraper user-agent regexes (GPTBot, Claude-*, Perplexity*, CCBot,
|
||||||
`localhost/` alias, restart; re-verify the policy schema against the new tag first.
|
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
|
## 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
|
5. **Deploy handler pull guard.** `deploy_anubis` guards the pull with
|
||||||
`podman image exists`; a cold anonymous ghcr token request can 403 on the
|
`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.
|
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
|
## Verify live
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user