Skip to main content

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

  1. Generate a full canvas of point static — a fresh random field for every challenge.
  2. Cut the answer from that same field. Not overlaid, not filtered: the same pixels.
  3. Move the cutout across the base static (DVD-style bounce).
  4. 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

  1. Set a strong BOTSTOP_SECRET
  2. Set BOTSTOP_API_KEY and pass it from your frontend or backend proxy
  3. Restrict BOTSTOP_CORS_ORIGINS to your domain
  4. Run behind HTTPS (reverse proxy)
  5. Use demo_html=False — never ship answers to the browser
  6. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

botstop-0.3.0.tar.gz (23.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

botstop-0.3.0-py3-none-any.whl (22.2 kB view details)

Uploaded Python 3

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

Hashes for botstop-0.3.0.tar.gz
Algorithm Hash digest
SHA256 8059c0086cd8f7f25f2b5d59933bb09e1e2eaf629546361e1c62d661b6768e24
MD5 e2206c1d84d90a05c0b02ec6b60407d6
BLAKE2b-256 fe5c748b8d780ea9e4577fd0f2d16a4b754e2f6652ac68364efb2741c0f69b28

See more details on using hashes here.

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

Hashes for botstop-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 09eb4ec963a53258eddf9682a5b8bc4da2a2d3e9c08e8c0c15dc50c2ef4f74de
MD5 0d4374a8cb2158491a15fbe79f940843
BLAKE2b-256 9df782cdd00787bebb9b60be4848c42926a5c85ede5b24abdddb332c3ffe619c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page