Anti-AI captcha: moving static hides a code humans read over time, not from a screenshot.
Project description
BotStop
Stop bots, not humans.
Live demo — try the captcha in your browser.
BotStop is an anti-AI captcha where the answer lives in motion, not in pixels. The digits are cut from the same TV static as the background and bounced across the field. Pause on any frame and there is nothing to read — freeze time, and all discernment is lost to noise. Humans track the moving patch instinctively; bots need a persistent model that understands video, not a screenshot and an OCR pipeline.
Why BotStop
Most captchas make the image harder: warp text, add clutter, distort glyphs. Bots respond with better OCR. BotStop sidesteps that arms race by changing the modality entirely.
| Typical captcha | BotStop |
|---|---|
| Hides text under noise | Text is the noise — same field, cut and moved |
| Escalates distortion to beat OCR | No stable glyph edges to latch onto |
| Solvable from a single frame with enough ML | A single frame gives almost nothing |
| More complexity on a static image | Watch the sequence; can't screenshot it |
When the challenge becomes a static image instead of a process, the signal disappears. That is the design.
How it works
- Generate a full canvas of point static — a fresh random field for every challenge.
- Cut the answer from that same field. Not overlaid, not filtered: the same pixels.
- Move the cutout across the base static (DVD-style bounce).
- Verify that the user watched and typed what they saw.
Each challenge gets a new static field, answer, and motion path. Nothing carries over between instances, so there is no shared background for reinforcement learning to memorise. Within a single challenge, the base static stays fixed; the security property is temporal integration over time, not pattern matching across sessions.
Security model
BotStop does not aim for perfect security — that would break legitimate use. It aims for asymmetry: make cheap automation economically pointless.
Strong against
- Frame scraping and one-shot OCR
- Batch classifiers that treat the web as a pile of PNGs
- Brute force on a repeated static background
- High-volume bots that need low cost per solve
Does not claim to stop
- Human solving farms
- Screen recording plus bespoke temporal ML
- Determined adversaries with custom video models
That trade-off is intentional. A captcha cannot stop everything without stopping everyone. The goal is efficiency: block automation at scale, accept that individual motivated solves may still occur, and use TTL, rate limits, server-side verification, and optional API keys to keep residual risk small.
For accessibility, offer an alternative verification path where motion-based challenges are not suitable.
Architecture
Browser / any app BotStop API (Python) Storage
| | |
| POST /v1/challenges | create GIF + record |
|----------------------------->|---------------------------->|
| GET .../animation.gif | |
|<-----------------------------| |
| POST .../verify | HMAC + TTL check |
|----------------------------->| |
- Python package — generator, verifier, API server
@botstop/client— npm/pnpm client and drop-in widget- Built-in — refresh, TTL, rate limits, optional API key
- Language-agnostic — any stack that can POST JSON and display a GIF
Install (Python)
pip install botstop[api]
# development
pip install -e ".[api,dev]"
Run the API
# set secrets first (see .env.example)
botstop serve
# or bind publicly
botstop serve --host 0.0.0.0 --port 8787
OpenAPI docs: http://127.0.0.1:8787/docs
Environment
| Variable | Default | Purpose |
|---|---|---|
BOTSTOP_SECRET |
local-dev-secret |
HMAC signing key (change in production) |
BOTSTOP_API_KEY |
(empty) | Require X-API-Key header when set |
BOTSTOP_TTL_SECONDS |
300 |
Challenge expiry |
BOTSTOP_CORS_ORIGINS |
* |
Comma-separated allowed origins |
BOTSTOP_RATE_LIMIT |
60 |
Requests per IP per minute |
BOTSTOP_STORAGE_DIR |
.botstop-data |
GIF storage |
HTTP API
POST /v1/challenges
→ { "challenge_id", "gif_url", "digit_length", "expires_in" }
GET /v1/challenges/{id}/animation.gif
→ image/gif
POST /v1/challenges/refresh
{ "previous_id": "optional-old-id" }
→ new challenge payload
POST /v1/challenges/{id}/verify
{ "answer": "482913" }
→ { "ok": true, "reason": "ok" }
JavaScript / TypeScript
Build the client package:
cd client
pnpm install
pnpm build
Use in your app:
pnpm add @botstop/client
import { BotStopClient, mountBotStopWidget } from "@botstop/client";
const client = new BotStopClient({
baseUrl: "http://127.0.0.1:8787",
apiKey: import.meta.env.VITE_BOTSTOP_API_KEY,
});
mountBotStopWidget(client, {
target: document.getElementById("captcha")!,
onVerified: (result) => {
if (result.ok) submitForm();
},
});
CLI (local / dev)
botstop generate --reveal-answer
botstop verify --bundle outputs\<id>.bundle.json --answer 482913
Python library
Embed generation and verification in your own backend:
from botstop import create_captcha, verify_captcha
result = create_captcha(demo_html=False)
check = verify_captcha(bundle_path=result.bundle_path, submitted_answer="482913")
Production checklist
- Set a strong
BOTSTOP_SECRET - Set
BOTSTOP_API_KEYand pass it from your frontend or backend proxy - Restrict
BOTSTOP_CORS_ORIGINSto your domain - Run behind HTTPS (reverse proxy)
- Use
demo_html=False— never ship answers to the browser - Proxy API calls through your backend if the API key should not live in the client
Build
python -m pip install build
python -m build
Outputs: dist/botstop-0.3.0-py3-none-any.whl
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file botstop-0.3.0.tar.gz.
File metadata
- Download URL: botstop-0.3.0.tar.gz
- Upload date:
- Size: 23.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8059c0086cd8f7f25f2b5d59933bb09e1e2eaf629546361e1c62d661b6768e24
|
|
| MD5 |
e2206c1d84d90a05c0b02ec6b60407d6
|
|
| BLAKE2b-256 |
fe5c748b8d780ea9e4577fd0f2d16a4b754e2f6652ac68364efb2741c0f69b28
|
File details
Details for the file botstop-0.3.0-py3-none-any.whl.
File metadata
- Download URL: botstop-0.3.0-py3-none-any.whl
- Upload date:
- Size: 22.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09eb4ec963a53258eddf9682a5b8bc4da2a2d3e9c08e8c0c15dc50c2ef4f74de
|
|
| MD5 |
0d4374a8cb2158491a15fbe79f940843
|
|
| BLAKE2b-256 |
9df782cdd00787bebb9b60be4848c42926a5c85ede5b24abdddb332c3ffe619c
|