AntiScrape helps you protect contact info from bots and scrapers by generating short links that reveal an email or phone number only after a visitor completes a CAPTCHA. Values are encrypted server-side with AES-256-GCM.
- Generate protected email and phone links (plus WhatsApp and Telegram)
- Server-side AES-256-GCM encryption with rotatable keys
- Cloudflare Turnstile CAPTCHA on create and unlock, enforced fail-closed
- D1-backed rate limiting shared across Workers isolates
- One-time delete token per link, so links can be revoked
- Links never expire by default
- Cloudflare Pages + Functions
- Cloudflare D1 (SQLite)
- Static frontend (Tailwind via CDN)
public/— static site and assetspublic/config.js— public front-end config (Turnstile site key)functions/— Cloudflare Pages Functions (API)schema.sql— idempotent schema, safe to run against productionschema.reset.sql— destructive rebuild, local use onlymigrations/— upgrade scripts for existing databaseswrangler.toml— Cloudflare config
npm installCreate .dev.vars:
ENCRYPTION_KEY=your_base64_key_here
ALLOW_INSECURE_NO_CAPTCHA=trueGenerate a key:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"ALLOW_INSECURE_NO_CAPTCHA skips CAPTCHA locally. In production, a missing
TURNSTILE_SECRET_KEY makes the API fail closed rather than accept everything.
Full reference: ENV_SETUP.md.
wrangler d1 execute antiscrape-db --local --file=./schema.sqlschema.sql only creates what is missing, so the same command is also the
correct way to set up production. Upgrading a database created before the
hardening work? Run migrations/0001_hardening.sql first.
schema.reset.sqldrops every table. It permanently deletes all links. Never point it at production.
npx wrangler pages dev public --port 8788Open http://localhost:8788.
All endpoints are POST and return JSON. Responses are sent no-store.
Returns { "id", "deleteToken", "expiresAt" }. deleteToken is returned
once and never again — only its hash is stored.
Rate limit: 20/minute/IP.
{ "captchaToken": "…" }Returns { "value", "type" }. Rate limit: 10/minute/IP.
{ "deleteToken": "…" }Returns { "deleted": true }. A wrong token and a missing link both return
404, so the endpoint cannot be used to probe for valid IDs. Rate limit:
10/minute/IP.
- Link IDs are 10 characters from a CSPRNG. Older 6-character IDs still resolve.
- Rate limiting is stored in D1 because per-isolate memory gives each of
Cloudflare's many isolates its own budget. Cloudflare WAF rules are still the
recommended outer layer — see
CLOUDFLARE_HARDENING.md. - Errors returned to clients are generic; details go to the Workers log.
- Key rotation never breaks existing links, provided retired keys are kept.
Procedure and audit query:
ENV_SETUP.md.
Found a vulnerability? See CONTRIBUTING.md.
No automated tests are configured yet. Manual smoke checks are in
RUNBOOK.md.
See DEPLOYMENT.md and ENV_SETUP.md.
Licensed under the GNU Affero General Public License v3.0 or later.
AntiScrape is a hosted service, and the AGPL is what keeps it open: if you run a modified version and let others use it over a network, you must offer them the corresponding source. Self-hosting for yourself, unmodified or modified, carries no such obligation.
Releases published before this change remain available under the MIT license.
{ "value": "you@example.com", "type": "email", // email | phone | whatsapp | telegram "captchaToken": "…", "ttlDays": 30 // optional; omit so the link never expires }