A collection of the gates built between January and October 2026 to make automated access cost more than it returns. None of them blocks automation outright. Each one charges for it: CPU time, server-clock seconds, wasted crawl, a key that has to be minted, or statistics a fleet cannot hide. Seven gates are full pages with their own verifiers; six more run inline on this page.
1 · Threat model
Attacker tiers and what each layer does to them
Attackers are ranked by cost, cheapest first. Each gate in the Sophie Germain series was built against the tier that beat the previous one. A layer does not need to stop every tier. It needs to leave only the tier whose cost is the price of the content.
Attacker
Tiered serving
Sophie Germain Gate
Germain Ring
Scribble and Tap
Three Dots
Fleet detection
curl, wget, no JavaScriptFetches HTML and never runs a script.
StoppedNever earns the js_ok cookie. Sees the plain tier only.
not reached
not reached
not reached
not reached
—
Headless browser, scripted inputPlaywright or Puppeteer with interpolated pointer moves.
Full tierRuns the challenge and gets the cookie.
PaysEffort floor only. It draws 40 points and buys the primes.
StoppedPerfect and jittered traces fail the continuity and entropy checks.
StoppedStraight routes rejected. Polyline scribble fails onset and speed CV.
StoppedStraight routes rejected.
Stopped at scale
ReplayerReplays a recorded human trace immediately.
Full tier
PaysThe nonce blocks replaying the proof, not the stroke.
StoppedPursuit targets depend on the trace prefix. A replay chases the wrong dot.
StoppedPresses before the 150 ms floor.
StoppedDoes not know where the dots are.
Stopped at scale
Replayer that waitsReads the dot from page state, waits 250 ms, taps dead centre.
Full tier
Pays
StoppedReader behaviour fails lag, residual and reaction checks.
FrontierPasses. Three Dots was built against this attacker.
StoppedPage state holds a decoy. Two decoy hits reject.
Stopped at scale
Smooth hand modelLow-pass or mass-on-spring pointer with about 170 ms delay.
Full tier
Pays
FrontierPasses both Ring and Pursuit. The next build stopped asking for shapes.
PaysNeeds the target from page state. Passes.
StoppedStill reads page state, still hits the decoy.
Stopped at scale
Vision botScreenshots, finds the dot by CV, waits 230–290 ms, moves like a hand.
Full tier
Pays
Pays
Pays
FrontierPasses, paying CPU and real server-clock seconds per attempt.
Stopped at scaleOne generator, many sessions. They cluster.
Vision bot with modelled diversityEach session drawn from a learned human population.
Full tier
Pays
Pays
Pays
Pays
FrontierNeeds population data the defender has and the attacker does not.
Stopped rejectedPays passes, but pays the proof of work or the challengeFrontier first attacker that passes; the next layer was built for itnot reached filtered earlier
2 · Progressive access
The ladder
No gate here is meant to stand alone, and none fires on every visitor. The layers below assemble into a progressive system: an unknown client earns access through stronger evidence, and only suspicious behaviour climbs. Three capabilities stay separate throughout, because no single mechanism provides all three: human verification, client authentication, and behavioural enforcement. The rest of this page follows the ladder from the bottom up.
Layer
Mechanism
Purpose
On this site
1
Passive request analysis
Identify suspicious traffic
Tiered serving signals; TLS and HTTP/2 fingerprint against the User-Agent
Browser Certificate policy panel; fleet detection feeds the denylist
9
Session authorisation
Keep access
HMAC JWT; signed js_ok cookie
Decision ladder
A five-level policy. Each level names the build that implements it. Signals add or remove score. In production the score decays and is calibrated against traffic; here it is illustrative.
0
risk score / 10
Observe
log it, serve the full tier
3 · Layers 1 to 3 · observe, limit, trap
Before any challenge
Most scrapers never run JavaScript. The reverse proxy routes them to a plain tier: real content, no forms, no API links, no tokens in the DOM, every link pointing back into the plain tier. Real browsers solve a silent JavaScript challenge on their first visit and receive a signed, self-expiring js_ok cookie. The tarpit is an optional annex of the plain tier that serves generated pages, one slow byte at a time, to anything that keeps following links. Honeypots and decoy URLs produce signals for the ladder; they do not ban on their own.
Routing decision
Describe the incoming request. Signals are checked in order of reliability.
Visitor
Gets
mTLS certificate present
Full app, no challenge
OIDC token present
Full app
No token, passes the JS challenge
Full app, prompted to enroll
No token, never runs JS
Plain tier
Known crawler UA
Plain tier directly, 200 OK (keeps SEO, no redirect loop)
The cookie is HMAC-signed with an embedded expiry, so there is no session store. A spoofed X-Client-Cert-DN header is stripped at the proxy. The ?next= redirect rejects absolute URLs. Design: registrar.earth, March 2026.
Tarpit, from the crawler's side
A crawler following links into the tarpit. Every page is generated from its own number, so it is stable on revisit and looks like content. Headers arrive at once; the body drips.
0
pages fetched
0
bytes drained
0.0 s
crawler time spent
0
real documents reached
Each slow connection holds a worker and a file descriptor, so production caps concurrent slow drains. Infinite pagination is enabled only after basic routing is confirmed in production.
June 2026 · linux.farm · live
Honeypot and time-to-submit
The silent proof-of-humanity layer for a site that cannot show a visual or audio challenge. A field no person can see, hidden from assistive technology as well (aria-hidden, tabindex="-1", autocomplete="off"), and a clock on the form. Bots fill every field and submit at once.
form opened; the clock is running
Stops:form fillersanything that reads the CSS
2026 · design
Decoy URLs and scanner traps
Decoy URLs that no link points to, scanner-specific endpoints such as /.env and /wp-login.php that exist only to be logged, and classification of what hits them. Three operations are kept separate: detecting a behaviour, classifying the client, and enforcing a restriction. Conflating them is how a defence turns into an attack (A Bomb Is Not a Control).
A honeypot hit is a signal for the ladder, never a ban on its own. Autofill and assistive tools cause false positives.
Decoy URLs catch enumeration. Scanner endpoints fingerprint tools by what they ask for.
Resource-consumption traps and decompression hazards stay out of production. They harm shared infrastructure and third parties, and they impose a consequence before classification.
Stops:enumeratorsscannersanything that reads the page first
4 · Layer 4 · proof of work
Charging for the request
Proof of work does not distinguish anything. It taxes. It is the only layer that still holds against a perfect human emulator, and it is accessible by construction, since it asks the person for nothing. The early gates used SHA-256 hashcash, leading zeros on a hash. The Sophie Germain series in section 5 replaced that with a prime search whose by-product is a safe prime. Both verify in constant time.
January 2026 · design and demo
mTLS, security key, DPoP, hashcash
A three-layer scheme for an authenticated application: client certificate plus WebAuthn as identity, DPoP (RFC 9449) binding every request to the certificate key, and hashcash proof of work on sensitive endpoints only. The proof of work was the least important layer by design. Device fingerprinting, behavioural scoring and risk engines were left out.
Challenge: 16 random bytes, 120 s TTL, single use, stored server-side
Base difficulty 4 leading hex zeros, capped at 8; fingerprinted clients get 2 fewer
Express middleware: verifyPoW then verifyDPoP on POST /api/transfer
Stops:token theftreplayautomation
February 2026 · Estafette protocol · live
Hashcash by sender class
Every message on the store-and-forward network carries a SHA-256 hashcash token. Difficulty depends on who is sending, not what: a local user pays little, an unknown node pays a lot. A single post costs milliseconds; a million cost days.
Token 1:bits:YYYYMMDD:resource::rand:counter; resource names the target (group:, direct:, timeline:)
Local user 16 bits, peered node 20, unknown node 26
Date within 2 days of the receiver's clock; 48-hour seen-token cache stops double spend
idle
Stops:bulk spamone determined sender
March 2026 · itys.net · live
Click to Reveal
Contact and security details hidden behind one button. Hovering the button records pointer positions; the button only works once the last 20 samples span more than 15 px in x or y. A SHA-256 search for a hash starting 00 then runs before the lines appear. Address harvesters that never move a pointer never see the address.
movement: 0 px · not yet human
email: contact@example.invalid
security.txt: /.well-known/security.txt
gpg: /key.asc
Stops:harvestersno-JS fetchersheadless with synthetic mousemove
2026 · browser proof-of-work gate · live
Nonce, hashcash, and a sum
A server-issued nonce, SHA-256 with adjustable difficulty, and a simple arithmetic question, so the person does one thing while the browser does the other. The server recomputes the hash, checks the difficulty and the sum, and consumes the nonce. The same design proposed charging proof of work for favicon requests, which are a cheap enumeration channel.
idle
Stops:no-JSevery browser, once per fetcha script that reads the sum
Price the scrape
Adaptive proof of work scales with risk. Waiting is free for a bot, since a thousand sessions wait in parallel; only the work costs. A gate stops scraping that is worth less than its per-fetch price and nothing that is worth more. Expected trials follow the Hardy–Littlewood density for Sophie Germain primes, times 6 for the residue restriction, with a geometric tail.
—
expected candidates per proof
—
CPU-days per day of scraping
—
attacker's daily bill
—
per fetch
Measured for the Sophie Germain Gate: 512-bit hunts, four at once, median 541 ms and p95 1.1 s on a laptop, p95 about 4.4 s on a phone. 512 was chosen over 576 for phones. The Three Dots default of 128 bits is near-free for a person who just tapped three dots and still charges a fleet per session.
5 · Layer 5 · human interaction
Requiring a person
Every gate in this section runs on a client the attacker controls, so none of them is a proof. They are liveness heuristics that raise the cost of automation, and each one states which attacker passes it. The Sophie Germain series combines the interaction with the proof of work from section 4: the search is seeded from what the person did, so the work cannot start before the person finishes.
The Sophie Germain series
Find p such that p and 2p+1 are both prime. The verifier needs two Miller–Rabin tests. Each build exists to close the hole the previous one left open, so they are numbered.
01SEP 2026
Sophie Germain Gate hold-gate · 54 tests · POW.md
Scribble until the meter latches green, lift the pointer, and four independent hunts for 512-bit Sophie Germain primes start from four points of the stroke.
Liveness
At least 40 points, 400 px of path and 800 ms of ink (Limits::default() in scribble.rs)
Pressure captured per sample
Redraw cancels the running hunts
Work
Seed = HMAC-SHA256(key, point ‖ stroke tag), masked to K−1 bits, odd, capped so the window cannot cross 2K
Segmented sieve on p and 2p+1 to 216, then 12-base Miller–Rabin
512-bit: median 541 ms, p95 1.1 s on a laptop; p95 about 4.4 s on a phone
User parallelism caps near 2× (measured 1.97×); scrapers scale linearly
About half a CPU-second per download
What it leaves open: it is an effort floor, not a behaviour test. Any client that draws 40 points buys its way through. It prices CPU, not elapsed time; a VDF would be the tool if wall-clock were the goal. Designed as a third layer behind Anubis and a liveness check.
Trace a circle (Ring), or chase a dot that jumps four times to positions derived from the trace so far (Pursuit). The challenge nonce and the trace seed the search.
Ring thresholds
At least 0.98 revolutions, 64 samples, 1 to 120 s
Radial RMS 0.4 to 30 % of R; speed CV at least 0.08
Residual entropy at least 1.2 bits/step; lag-1 ρ(error) at least 0.70, ρ(speed) at least 0.50
Pursuit thresholds
Jump k lands at SHA-256(challenge ‖ "jump" ‖ k ‖ trace prefix). A replay chases the wrong dot
Pursuit lag 60 to 600 ms by velocity cross-correlation; lag-aligned residual ρ at least 0.5 and RMS at least 1.5 px
Reaction at least 100 ms minimum, median at most 900 ms; median tracking error at most 70 px
Work and risk
Candidate i = SHA-256(seed ‖ i), top bit set, p ≡ 5 (mod 6); trial division to 2000, 12 fixed MR bases
Frontier: a low-pass filtered hand model passes Ring; a mass-on-spring tracker (ω = 11, 170 ms delay) passes Pursuit. Reference-tracking tasks reward a good motor model, so the next build stopped asking for shapes.
Scribble (or in tap-only mode, tap once), then tap two dots that appear in sequence at transcript-derived positions. Errors count in the person's favour: a miss followed by a correction is evidence, not a penalty.
Tap thresholds
Dot k = SHA-256(challenge ‖ "tap k" ‖ scribble ‖ previous taps), 25 to 45 % of the field away
First press at least 150 ms after appearance; hit within 12 s; landing within 30 px plus half the finger's contact width
Up to 5 attempts per dot
Route and contact
Straight route: chord at least 40 px, max deviation under 1.0 px, path/chord under 1.005. Two straight routes reject
Teleport: chord at least 60 px with fewer than 3 approach samples
Evidence (never gates): corrections, path at least 1.08 × chord, RT CV at least 0.10, mean miss at least 1 px
Suspicious: dead-centre under 0.75 px, metronome RT CV under 0.03, press under 12 ms
Risk picks 112 / 192 / 256 bits; default 128
Frontier: the replayer that waits. The dot's position and appearance time both live in page state, so a bot can read the target, wait 250 ms, and tap it dead centre. The 150 ms floor is not a number a server can trust when the client reports both ends of it.
Tap anywhere, then three dots. A server (a Web Worker in the demo; the download endpoint in production) derives each dot from a secret the page never sees, draws it on an OffscreenCanvas, feeds the page a decoy, times every press on its own clock, and signs an HMAC receipt.
The page is blind
Dot k = SHA-256(secret ‖ challenge ‖ "dot k" ‖ opener ‖ taps so far). The page learns the position only after the hit
Decoy = SHA-256(secret ‖ "decoy k" ‖ h), placed 70 to 110 px from the real dot
One decoy hit is +40 risk; two reject
Server clock
Reveal, first arrival and hit are timed server-side
First press at least 150 ms after reveal; hit within 12 s
All Scribble-and-Tap route, contact, evidence and suspicion checks, over three dots
Same candidate derivation and MR bases; 64 to 384 bits, default 128; risk tiers 112 / 192 / 256
Simulated bots: reader (hits the decoy three times), straight-line, tired human, vision
Frontier: the vision bot. It locates the dot by looking, waits a human interval, and moves like a hand. Against it the remaining costs are real server-clock seconds, CPU per attempt, and the fact that a fleet of them clusters while people do not. Section 7 is built for that.
A 3×3 board of challenge-derived shapes and patterns, each cell with its own motion when picked and a Crockford base32 label. The prompt names one cell by two attributes: "find the hexagon with diagonal stripes." Pointer, touch, keypad in phone layout, or arrows and Enter. The original Rust/Axum and Flask builds asked for a three-symbol sequence; this version asks for a lookup by two attributes.
Board, prompt, decoys, labels and motions from SHA-256(challenge ‖ "grid"). Manhattan-distance scatter keeps the target away from the previous one
Two decoys share the shape, two share the pattern, so a one-attribute reader lands wrong
First pick at least 150 ms, hit within 20 s, three attempts, pixel-straight routes rejected. Misses count as evidence
Stops:no-JSreader botstraight-route readerreader with a hand model
PIN copy gate · 2026 · full page
Copy the PIN
Copy a six-digit PIN from one box to the next. The server already knows the digits, so a correct answer proves nothing by itself. The gate scores where the characters came from: every one must trace to a trusted key, paste or drop, and none may arrive faster than reading.
PIN = SHA-256(challenge ‖ "pin") reduced to six digits
keydown, beforeinput, input, paste and copy transcript. The value must equal the last input event's value. isTrusted on every event
First character at least 300 ms after reveal; typed cadence median at least 60 ms with CV at least 0.08. A clipboard round trip and backspaces are evidence
Text in, text out. The one interaction gate here that works with a screen reader
Stops:value setterssynthetic eventsinstant pastemetronome typistsdriven browser with jittered keys
March 2026 · c-collar.com · live
Three-step gate
Built to keep a personal essay off aggregators. Three steps chain into one session key: a drag-to-confirm slider that must reach 95 % or snaps back; a SHA-256 proof of work at difficulty 5 run in animation-frame batches; a browser fingerprint. HKDF-SHA-256 derives a key from all three, stored in localStorage and a 7-day SameSite=Strict cookie. Return visits skip the gate when both match.
slide to confirm
›
○Drag to confirm, at least 95 % of the track or it snaps back
○Proof of work, difficulty , capped at 250 000 attempts
○Fingerprint
The 250 000 cap was added after visitors timed out. A client that exhausts it is passed through with exhausted: true in the session metadata. At difficulty 5 that happens to about four in five browsers
Production used FingerprintJS v4 with a UA-and-screen hash as fallback. This demo uses the fallback
A referrer check ran before everything else: any Reddit domain, or utm_source=reddit, got a 403 overlay instead of the gate
May 2026: the gate stopped firing on cold visitors. A landing page explains what is behind it first, and the gate triggers on click-through
For a site built around a Bandit-style wargame and an in-browser shell, the on-theme gate is a one-line terminal prompt. Plain text is the most screen-reader-friendly challenge there is, and blind sysadmins already work in exactly this environment. It is flavour on top of proof of work, not a control: any automated client that can read text can answer it.
gate@linux.farm:~$ print the working directory
$
Stops:nothing on its ownscreen-reader clean
6 · Layers 6 to 9 · identity and session
Recognising a returning client
A browser that has paid once should not pay again. These layers give the server a handle: a key the browser holds, a certificate it can revoke, a fingerprint it can rate-limit, and a session token with its own lifetime. None of them proves a person. That is what section 5 is for, and the two are kept apart on purpose.
Browser certificate gate · 2026 · full page
Browser Certificate
Four stages: proof of work; a non-extractable ECDSA P-256 key pair made by WebCrypto, with a self-signed certificate request; a seven-day certificate signed by a private CA; and proof of possession on every later visit, verified against the certificate, the CA, the validity window and the fingerprint lists. The first implementation used RSA-2048; EC P-256 replaced it.
Key and certificate persist in IndexedDB. The CA's own state does too, so a return visit across a reload is real
Allowlist, denylist and revocation on the fingerprint (SHA-256 of the SPKI)
Session token is an HMAC-SHA-256 JWT, one hour. Proposed next step: mutual TLS at the proxy with the same key and CA
Role:client authenticationenrollment, once per identity
March 2026 · registrar.earth · design
Favicon recognition tier
The tier between "has a session" and "cold visitor" in the serving model of section 3. A returning person who lost their session should not have to pass the JS challenge again. Recognition rides on the browser's favicon cache, which survives cookie clearing.
A human-readable word cookie is the lookup key. It is JS-readable by design because it is not a credential
32 favicon URLs encode a 32-bit token. The page probes each with PerformanceObserver; transferSize of 0 means cached, which means bit 1
All favicon URLs served from one origin, id.registrar.earth, with Timing-Allow-Origin: *, to survive cache partitioning
DataMatrix in a 16 × 16 favicon was ruled out. It needs 64 × 64 to decode
Role:fast path for returning people
February 2026 · so.itys.net
Click to enter
A full-screen threshold in front of a Lemmy instance: a wordmark, a pulsing logo button, "click to enter". The click fires a comic-book POW! burst, writes a ten-year flag to a cookie on so.itys.net, a second cookie on .itys.net and localStorage, then redirects. Returning visitors skip it.
Classification: a consent threshold, not a bot defence. Any client that runs JavaScript and clicks passes. It is the ancestor of the landing-before-the-gate pattern that c-collar.com adopted in May, where the page explains what the gate is for before the gate fires.
Stops:curlanything with a DOM
7 · Layer 8 · enforcement at scale
Fleet detection
Every session that passes Three Dots sends the server its transcript: reaction times, press durations, route deviation, scatter. People are heterogeneous. One person is slow and precise, another fast and sloppy, and a tired one is both. A bot fleet is drawn from one generator. Comparing each session's feature vector with its nearest neighbours shows a cluster of too-similar "people", even when every one of them passes alone. This is the first defence where the defender's data advantage matters: the attacker has to model population diversity without the population.
—
sessions passing the gate alone
—
flagged as a cluster
—
of those are bots
—
humans flagged (false positives)
—
bots missed
—
bot kNN distance ÷ human kNN distance
Method: four features per session (median RT, mean press duration, mean route deviation, RT coefficient of variation), standardised by median and MAD, then the mean distance to the four nearest neighbours. A session is flagged when that distance is under 0.45 × the population median. At diversity 0 every bot shares one parameter set. At 1 each bot draws its own from the human ranges, which is the data the attacker does not have.
Every synthetic session here satisfies the Three Dots per-session checks (first press at least 150 ms, RT CV at least 0.10 where required). The fleet is caught by comparison, not by any single threshold. Flagged fingerprints go to the denylist in layer 8.
8 · Whole-site deployment
The accessible gate: Anubis and Cap
The gates above protect one download. A learning platform with a CTF wargame, an in-browser shell and lessons needs the whole site gated, and its audience includes blind sysadmins working through Orca and brltty. That rules out every visual and audio challenge. Proof of work is the accessible choice because it asks the person for nothing: the browser computes, the person waits. Two tools, complementary rather than competing.
PrimeAccessibility is the prime directive. No visual or audio challenge. WCAG 2.1 AA, ARIA live regions on the interstitial, tested with Orca and brltty.
AnubisGates whole-site page loads at the proxy with no application changes. Metarefresh is the default experience; proof of work is an escalation tier only, driven by botPolicies.yaml.
CapA widget plus verify API, scoped to the two endpoints where abuse would cost something: CTF registration and flag submission.
EgressNo third-party verification. Nothing to Google or Cloudflare. Cap's jsDelivr asset fetch is closed and the assets self-hosted.
RedisCap's Redis/Valkey dependency conflicts with the practice of removing Redis from self-hosted projects. If it stays a dealbreaker, ALTCHA or mCaptcha take its place.
OpenWhether Cap is needed at all before CTF signup shows real abuse. Anubis first; measure; then decide.
9 · Reference
Files and lineage
The site is plain HTML, CSS and JavaScript with no build step and no runtime dependencies beyond Google Fonts, with system fallbacks. Every gate page is self-contained and includes its own verifier. The archive layout:
Path
Scheme
Contents
index.html
—
This page, with the inline demos
gates/sophie-germain-gate/
hold-gate
Series 01. Scribble effort floor, windowed sieve, 512-bit hunts
gates/germain-ring/
germain-ring/2
Series 02. Ring and Pursuit tracking tasks, environment risk, bot simulations
gates/scribble-and-tap/
scribble-tap/3
Series 03. Scribble plus two chained dots, route and contact model
gates/three-dots/
three-dots/1
Series 04. Server-blind dots, decoys, server clock, HMAC receipt
Proof of work, P-256 key, 7-day certificate, proof of possession
reference/gate-archive-demo/
—
The six-page demo set: three-symbol Visual PIN with keys 1 to 9, nonce puzzle, ephemeral P-256 identity, honeypot form, five-level policy simulator, event log
Caddyfile, SHA256SUMS, README.md
—
Serving example with a matching CSP, checksums, this layout in text
Shared by series 02 to 04: candidate i = SHA-256(seed ‖ u32 i), hash-chained to width, masked, top bit set, p += (5 − p mod 6) mod 6; trial division by odd primes under 2000; Miller–Rabin with the 12 fixed bases 2 to 37 on both p and 2p+1, deterministic below about 3.3 × 1024; expected trials ln N · ln 2N / (12 · 0.66016). The server in every page is a simulation so the protocol can be read offline. In production the verifier, and for Three Dots the dot derivation, decoys, timing and receipt, move to the download endpoint, which is the only party holding the secret.