Bot and Fraud Defense skill for Claude Code
Stop credential stuffing without taxing real users. Claude names the attack and what it costs, baselines login, signup, SMS and checkout metrics, then layers controls: velocity limits keyed on account, IP, ASN and device, Pwned Passwords checks, server-verified Turnstile challenges and step-up MFA for risky traffic only. Rules launch in shadow mode with friction metrics, so you block bots without silently losing real customers.
- name
bot-fraud-defense - size185 lines · ≈2,600 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Defends signup, login, checkout, and content endpoints against automated abuse - credential stuffing, fake account creation, card testing, SMS pumping, promo abuse, and aggressive scraping - with layered signals (velocity per account, IP, ASN, device), breached-password checks, invisible challenges such as Cloudflare Turnstile, progressive friction, and metrics to tune false positives. Use when login failures spike, fake signups or disposable emails flood in, SMS or card-testing bills jump, bots scrape pricing or content, or when choosing a CAPTCHA. Try asking
- Our failed logins went from 2k to 400k a day. Help me stop this credential stuffing attack
- We are paying for thousands of OTP texts to numbers that never verify. How do we stop SMS pumping?
- Should we add a CAPTCHA to signup, and which one? Fake accounts are flooding our free tier
Use it when
- Failed login ratio or signups spike without a marketing reason
- SMS, card authorization, or chargeback costs jump suddenly
- Disposable emails or duplicate accounts abuse a free trial or promo
- Bots scrape pricing or listings faster than your infra can serve
Skip it when
- You only need rate limit algorithms and headers — use rate-limiting instead
- You are building the login and session system itself — use auth-sessions instead
How to check the result
Run new rules in shadow mode first, then confirm attack metrics fall while challenge pass rate, verified conversion, and 'cannot sign in' support tickets stay within baseline.
The common mistake it prevents
Keying every limit on IP address alone, which residential proxy pools walk straight through while shared mobile and office NATs lock out legitimate users.
Done well means: each abused flow has a named attack, a cost you are protecting (accounts, SMS spend, chargebacks, infra), layered controls that escalate friction only for risky traffic, decisions logged with their signals, and a dashboard that shows both attack volume and legitimate-user friction. A CAPTCHA on every form is not a defense plan; it taxes real users and is solved by paid farms for fractions of a cent.
Ground rules
- Defensive only: build detection and friction for systems the user operates. Do not build or test against third-party sites, and do not write tooling that evades other sites’ bot protections.
- Measure before you block. Ship new rules in log-only (shadow) mode for at least a few days, then compare flagged traffic with known-good cohorts.
- Never reveal which signal triggered a block in the response; return generic errors and log the reason server-side.
- Device fingerprinting and IP reputation involve personal data in the EU and elsewhere; document purpose (fraud prevention), retention, and disclose it in the privacy notice (see gdpr-privacy).
- Accessibility: every challenge needs a non-visual path and a human fallback (support contact, email verification) for users who fail it.
Workflow
- [ ] 1. Name the attack and its cost from logs and bills
- [ ] 2. Map the flow: endpoints, identifiers, what the attacker gains per request
- [ ] 3. Baseline metrics (success/failure ratios, velocity, geography, ASN mix)
- [ ] 4. Choose layered controls from the decision table
- [ ] 5. Ship in shadow mode, tune, then enforce with progressive friction
- [ ] 6. Monitor attack and friction metrics; review weekly while active
1–3. Identify and baseline
Questions to answer from logs (see security-logging for the event schema):
- Login: failed-login ratio per hour, distinct usernames per IP and per ASN, share of attempts on non-existent accounts (high share means a combo list).
- Signup: signups per hour, email domain distribution, verification completion rate, time from page load to submit, activation within 7 days.
- SMS/OTP: sends per destination country and prefix, OTP completion rate (pumping shows many sends with almost no completions to premium-rate ranges).
- Checkout: authorization attempts per card BIN and per customer, decline rate by
decline code (card testing shows many small auths with
card_declined/incorrect_cvc). - Scraping: requests per session for listing/detail pages, ratio of HTML to asset loads, pagination depth, user agents claiming browsers without executing JS.
4. Decision table
| Attack | First controls | Escalation | Avoid |
|---|---|---|---|
| Credential stuffing | Limits per account and per IP/ASN on failures; breached-password check at signup, login, reset; identical error and timing for bad user vs bad password | Invisible challenge for risky logins; step-up MFA on new device; passkeys | Hard account lockout (attackers use it to lock real users out) |
| Fake signups | Email verification before privileges; disposable-domain blocklist; normalized-email uniqueness; honeypot field and minimum fill time | Turnstile on signup; phone verification only for high-value tiers | Phone verification for everyone (cost, SMS pumping surface) |
| SMS pumping / toll fraud | Allowlist SMS destination countries you serve; per-number, per-IP, per-prefix send caps; require a challenge before the first SMS | Provider fraud guard features; switch to email, TOTP, or passkeys | Unlimited “resend code” buttons |
| Card testing | Require login or a challenge before payment; velocity per customer, IP, and card fingerprint; processor risk rules (e.g. Stripe Radar) | Block BIN ranges under attack temporarily; 3DS | Exposing a public “$1 donation” form without limits |
| Promo / trial abuse | One redemption per normalized email, payment fingerprint, and device; delay reward until activation | Manual review queue above a threshold | Relying on IP alone (shared NAT, mobile carriers) |
| Scraping | Auth or signed tokens for bulk data endpoints; per-session and per-key quotas; cache and paginate with cursors; robots.txt for honest crawlers | Edge bot management (managed challenge, JS detection); serve stale or partial data to flagged sessions | Blocking by user agent string alone |
Rate-limit mechanics (algorithms, Redis, headers) belong to rate-limiting; this skill decides what to key on and when to escalate.
Signals worth collecting
| Signal | Cheap? | Notes |
|---|---|---|
| Velocity per account, IP, /24 or /64, ASN, email domain, device | Yes | Combine keys; residential proxy pools defeat per-IP limits alone |
| ASN type (hosting vs residential vs mobile) | Yes, with an IP data feed | Datacenter ASNs on consumer login pages are suspicious, but VPN users are real customers |
| Account age, verified email, prior successful logins from this device | Yes | Best “known good” signals; use them to reduce friction |
| Breached password (Pwned Passwords k-anonymity) | Yes | Block at signup/reset, force reset at login |
| Form timing, honeypot, JS execution | Yes | Catches unsophisticated bots only |
| Challenge result (Turnstile, hCaptcha, reCAPTCHA) | Low cost | A risk signal, not proof of humanity |
| TLS/HTTP fingerprint (e.g. JA4) from your edge | Depends on CDN | Useful for clustering automation; mismatched UA and fingerprint is a strong signal |
| Device attestation (App Attest, Play Integrity) | Mobile | Server-verified only |
Score, do not single-gate: risk = weighted sum of signals, mapped to allow / challenge
/ step-up / block / review. Keep weights in config so they change without deploys.
Breached password check (k-anonymity)
import hashlib, urllib.request
def pwned_count(password: str) -> int:
sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
req = urllib.request.Request(
f"https://api.pwnedpasswords.com/range/{prefix}",
headers={"Add-Padding": "true", "User-Agent": "example-app-auth"},
)
with urllib.request.urlopen(req, timeout=3) as resp:
for line in resp.read().decode().splitlines():
s, _, count = line.partition(":")
if s == suffix:
return int(count)
return 0
Only the 5-character hash prefix leaves your server. Fail open with a logged warning if the API is unreachable at login; fail closed (ask for another password) at signup only if your UX tolerates it.
Server-side challenge verification (Turnstile)
// The widget token is single-use and expires after about 5 minutes.
export async function verifyTurnstile(token: string, ip: string, expectedAction: string) {
const body = new URLSearchParams({
secret: process.env.TURNSTILE_SECRET_KEY!, // never ship the secret to the client
response: token,
remoteip: ip,
});
const res = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method: "POST",
body,
signal: AbortSignal.timeout(3000),
});
const data = (await res.json()) as { success: boolean; hostname?: string; action?: string; "error-codes"?: string[] };
return data.success && data.hostname === "app.example.com" && data.action === expectedAction;
}
Always verify on the server; a client-side “solved” flag proves nothing. Check hostname and action so tokens minted on another page cannot be replayed here.
CAPTCHA choice
| Option | Choose when | Tradeoff |
|---|---|---|
| Cloudflare Turnstile | Default for most apps; invisible or managed mode; no image puzzles | Requires calling Cloudflare; works without using Cloudflare as CDN |
| hCaptcha | You want an alternative vendor or its enterprise risk scores | Visible puzzles more often in free tier |
| reCAPTCHA (v3 / Enterprise on Google Cloud) | Already on Google Cloud and want score-based assessment | Scores need threshold tuning; check current pricing and key migration to Cloud projects |
| Proof-of-work challenge (self-hosted) | Cannot send data to third parties | Raises cost for bots modestly; burdens low-end devices |
| None, rely on signals | Low-value forms with good rate limits | Fine until an attacker targets you |
5. Progressive friction and enforcement
- Shadow: compute risk, log decision, take no action.
- Challenge high-risk only (invisible first, interactive second).
- Step-up for account actions: MFA or email confirmation on new device plus risk.
- Block or tarpit clearly malicious clusters (slow responses, generic errors).
- Review queue for money-moving actions where false positives are expensive.
Credential stuffing specifics: notify users of successful logins from new devices; force password reset for accounts where a stuffing attempt succeeded; invalidate their sessions. Offer and promote passkeys (see passkeys-webauthn), which remove the password reuse problem entirely.
6. Metrics and review
Track per flow, daily: attempts, blocked, challenged, challenge pass rate, false-positive reports (support tickets tagged “can’t sign in”), verified conversion rate for challenged versus unchallenged users, cost (SMS spend, chargebacks). Alert on failed login ratio and SMS sends per country exceeding baseline by a set multiple.
Output contract
# Abuse defense plan: <flow>
**Attack:** <what, since when, volume> **Cost:** <$ / accounts / infra>
**Signals in use:** <list with weights>
**Controls:** <limit keys and thresholds> | <challenge placement> | <step-up>
**Rollout:** shadow <dates> -> enforce <date>; kill switch: <flag name>
**Success metrics:** <attack metric target> while <friction metric stays under X>
Gotchas
- Per-IP limits alone fail against residential proxies and hurt carrier-grade NAT users; always pair with per-account and per-identifier keys.
- Normalizing emails (lowercase, strip
+tag, Gmail dots) stops trivial duplicates; apply it for uniqueness checks, but still send mail to the address the user typed. - Distinct error messages for “no such user” turn login and reset into enumeration oracles; keep responses and timing identical.
- Challenge tokens verified only on the first step of a multi-step flow let bots replay the later steps directly; bind the verification to the session and the action.
- Blocking whole countries or hosting ASNs blocks real customers on VPNs and corporate egress; prefer challenge over block for those segments.
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.