AIRANKS — The Authoritative Rankings for AI Web Content

AIRANKS measures AI visibility: we ask AI models real product and service questions, capture the complete answers as immutable observations, and publish what they contain — which brands were mentioned, which domains were cited, and which exact pages were linked. Every domain gets an AIR score from 1–10 (a decile of visibility in the active dataset; 0 means insufficient data), with the methodology in the open.

Skip to main content

AIRVER. FIGHTING — 2026

Artificial Intelligence Rankings

the ledger notes

The council designs a login flow, and the review catches it phishing itself

Build LogAugust 15, 2026 by Jeremy Schoemaker

"I am not sure how they do it — please research it and find out best practices."

That sentence, from the product owner, is the healthiest possible opening for an auth design. The air CLI needed what every modern CLI has: a login that either takes a pasted API key or opens a browser, lets you approve, and ends with the terminal holding a token. Claude Code does it. gh does it. Everyone does it — differently, it turns out, and the differences are the whole story.

Round 1: five seats vote for the thing the industry is abandoning

Four research operators came back with sourced findings before the seats argued. The picture: Claude Code uses a loopback redirect with PKCE (RFC 8252) — an ephemeral local HTTP server catches the browser's redirect. GitHub's gh has been device-code flow (RFC 8628) since inception — print a short code, poll until the user enters it. Stripe built a third thing: a pairing session the CLI polls while the browser shows a confirmation code. And Vercel — this is the detail that decided everything — migrated off loopback onto polling in September 2025.

Round 1 still produced a 4-to-1 vote for loopback-primary-with-device-fallback, one seat even attributing loopback to Vercel and Stripe. The chair's challenge quoted the research back: Stripe never had a loopback server; the "fallback for SSH" that every loopback design needs is an admission. One reviewer's line stuck: "if the real flow needs a paste-code fallback every time it leaves a laptop, the fallback IS the real flow."

Round 2: four retractions, unanimous convergence on a single Stripe-style pairing flow. One mechanism for laptop and SSH box alike — the browser-open is a convenience, not a dependency. The owner's own examples helped: the operator who read Hermes' auth.py brought back its 0600 atomic token-write pattern, and opencode turned out to have no OAuth at all — a useful reminder that the simplest shipping answer is sometimes "paste a key," which stayed as the menu's option 1.

The part where the unanimous council had built a phishing kit

Two Sonnet agents implemented the verdict in parallel — server and CLI against a written contract — and both came back green (the only integration catch was a response-shape ambiguity the CLI agent had flagged itself). Then the adversarial review ran, and found that the unanimous, research-backed, twice-converged design had a hole you could drive a truck through:

The pairing link carried the nonce, and the confirmation code was decoration. An attacker starts a pairing, sends the resulting URL to any logged-in user — "click here to verify your airanks account" — and one Approve click hands the attacker a token for the victim's account. The council had specified the confirmation code as the phishing mitigation, and the implementation displayed it nicely… without ever requiring anyone to prove they'd seen it. Textbook device-code phishing, the exact attack that made AWS and Microsoft demote device flows. Second blocker: no anti-framing header anywhere in the app, so the approve page could be clickjacked, reducing the phish to one stray click.

The fix inverted the flow to gh's shape: the browser URL carries nothing; the user types the code shown in their terminal. Possession became load-bearing. FrameGuard went on sitewide. Seven more findings landed in the same pass — tokens now expire in 90 days, carry a real (enforced) cli ability now that bearer tokens can no longer drive the web app, abandoned pairing rows prune hourly, claim and approve are compare-and-swap atomic, the poll got its own rate-limit bucket after the reviewer proved in framework source that named limiters share buckets across routes, and a saved token only travels to the host it was minted for.

The fixer found one more on its own: Laravel's RateLimiter::attempt() coerces a null callback return into "attempt succeeded." The throttle around code entry would have silently passed on misses. Explicit tooManyAttempts()/hit() instead.

Scoreboard

Two rounds, four operators, four retractions, two parallel implementations, one adversarial review with two blockers, one fix pass: 16/16 server tests (125 assertions), 6/6 CLI tests, full suite byte-identical to baseline. air login now offers exactly what was asked: paste a key, or open a browser, type a short code, click Approve, and the terminal is authenticated — same flow whether the terminal is a laptop or an SSH session on a Pi.

The council was unanimous twice and still shipped two blockers to the review stage. That is not an argument against councils. It is the reason the review stage exists — and the reason it goes on the record.

← Back to blog