Design Bot Defense & Sybil-Resistant Registration Architecture
1. Problem Statement & Scope Clarification
System Mission
Design a multi-tiered, enterprise-grade bot defense and Sybil-resistant account registration architecture capable of protecting global platforms from coordinated bot attacks, automated credential stuffing, ticket scalping, fake account farm creation, and distributed scrapers. The platform must absorb volumetric registration floods ( malicious requests/sec), enforce sub-100ms edge filtering via AWS WAF Bot Control and JA4/JA3 TLS fingerprinting, impose computational economic asymmetry on adversaries via dynamic Client-Side Proof-of-Work (Hashcash) puzzles, and validate account authenticity with zero friction for legitimate human users.
The Economic Asymmetry Invariant: Adversaries automate account creation because the marginal cost per fake account is near zero (< $0.0001 USD). Traditional rate limiting by IP fails against residential proxy botnets rotating across millions of IPs. Sybil resistance succeeds by shifting the economic equation: combining passive TLS/device fingerprinting, Bloom filter disposable domain screening, and dynamic Proof-of-Work (PoW) puzzles forces the client to burn 2 to 5 seconds of dedicated CPU cycles ( hash iterations) per registration attempt, while server verification executes in time (). This collapses botnet profitability while remaining imperceptible to human users.
Synthesizing vector architecture diagram...
Functional Requirements
- Multi-Tier Edge Traffic Classification: Inspect inbound HTTP/2 and HTTP/3 TLS handshakes; compute JA4/JA3 fingerprints, analyze TCP window sizes and header ordering, and match against threat intelligence feeds to assign a real-time Risk Score ().
- Client-Side Proof-of-Work (Hashcash) Challenge: Dynamically generate cryptographic puzzles with variable difficulty ( of leading zeros) based on client risk scores. Legitimate users solve the puzzle in ; botnets attempting registrations/min face catastrophic CPU exhaustion.
- Disposable Domain & Carrier Screening: Screen registration emails against a high-performance in-memory Bloom Filter containing disposable/temporary email domains; detect and reject VoIP/virtual phone numbers via carrier lookup APIs.
- Honeypot & Canary Traps: Inject invisible form fields and fake API endpoints into registration workflows. Automated DOM scrapers and headless browsers filling honeypot fields are fingerprinted and blacklisted immediately.
- Sybil Device Fingerprint Registry: Generate deterministic hardware/browser fingerprints (combining WebGL entropy, Canvas 2D render hashes, AudioContext curves, and CPU concurrency) stored in Amazon DynamoDB to prevent one device from creating multiple accounts.
Non-Functional Requirements (SLAs & SLOs)
- Edge Decision Latency: , , processing overhead added by edge WAF and Lambda@Edge inspection.
- Server PoW Verification Latency: ( single SHA-256 hash evaluation).
- False-Positive Rate (FPR): Strict false-positive block rate for human users on clean residential IPs.
- Volumetric Attack Absorption: Sustain attack bursts at the edge without degrading origin authentication databases.
- Data Durability & Isolation: 11 Nines () for user account credentials; encrypted at rest via AWS KMS.
- CAP / PACELC Classification:
- Edge Rate Limiting & Bot Scoring: AP / PA/EL system. Fast edge admission decisions prioritize low latency over cross-region rate-limit consistency.
- Account Registration & Credential Store: CP / PC/EC system enforcing strict unique constraints on email, username, and device hashes in Amazon DynamoDB.
Out-of-Scope
- Biometric facial recognition or government identity card verification (KYC/AML).
- Continuous in-session behavioural fraud scoring during financial wire transfers (handled by dedicated Fraud Services).
- Post-registration account recovery social graph verification.
2. Capacity & Scale Estimation (Back-of-the-Envelope Math)
Traffic Scale Calculations
- Global User Registrations (Nominal): human registrations/month.
- Volumetric Adversary Attack Surge (Credential Stuffing / Scalper Bot Farm): Botnets utilizing rotating residential proxy IPs attempting simultaneous signups during a viral drop:
Proof-of-Work (Hashcash) Computational Asymmetry Math
- Target Difficulty ( of leading zeros):
- Client Computation Time (Modern JavaScript / WebAssembly client executing ):
- Server Verification Time (Single SHA-256 evaluation):
- Computational Asymmetry Ratio:
- Adversary Attack Cost (be honest about it): A botnet attempting must solve . In browser JavaScript (~2 MH/s per core) that is ~50,000 cores, but an attacker does not use a browser: native SHA-NI code reaches ~50 MH/s per core (~2,000 cores) and a single commodity GPU ~1-2 GH/s, so ~50-100 GPUs (tens of dollars per hour on a cloud) sustain the full flood. Proof-of-Work therefore raises the marginal cost of a fake account by orders of magnitude and throttles residential-proxy botnets (whose IoT/router hosts have no GPU), but it does not make farming impossible. That is why it is one rung of the escalation ladder in §6.3, feeding JA4 clustering, subnet velocity and device-registry limits, never the whole defense.
Storage & In-Memory Sizing
- Bloom Filter for Disposable Email Domains:
- Item count (): disposable domains (e.g.,
tempmail.com,guerrillamail.com). - Target false-positive probability (): ().
- Required bits (): (Fits entirely within L3 CPU cache; near-zero latency lookup).
- Item count (): disposable domains (e.g.,
- Redis Sliding Log Rate Limiting: Tracking IP subnets (/24) and device fingerprints over a 10-minute rolling window: .
- DynamoDB User & Device Fingerprint Registry (3-Year Horizon): (replicated across Multi-AZ).
3. AWS-First High-Level Architecture
Synthesizing vector architecture diagram...
Follow a sign-up from the top. In "Tier 1: Edge TLS & WAF Inspection", WAF bot rules run first, then the edge scores the request from 0 to 100 using its TLS fingerprint (JA4), whether it comes from a datacenter, Tor or a proxy network, and whether the honeypot field was filled. In "Tier 2: Proof-of-Work Challenge Engine", the score picks a path: 80 or more is blocked, 30-79 must solve a SHA-256 puzzle and resubmit, and under 30 passes straight through. In "Tier 3: Stateful Auth & Sybil Validation", the registration service checks disposable email domains with a Bloom filter, looks for many accounts from the same device fingerprint, and enforces sliding-window rate limits in Redis before writing to DynamoDB. The friction grows with suspicion, so real users usually never see a challenge.
Data Flow Walkthrough
- Edge TLS & Network Handshake Inspection (Tier 1):
- When a client establishes a TLS 1.3 connection to Amazon CloudFront, CloudFront terminates TLS and exposes the handshake fingerprint as the
CloudFront-Viewer-JA3-Fingerprint/CloudFront-Viewer-JA4-Fingerprintheaders; AWS WAF has native JA3/JA4 match statements. Lambda@Edge never sees the raw Client Hello, it consumes those headers. - The JA4 fingerprint (cipher suites, ALPN protocols, TLS extensions, elliptic curves) is the primary transport signal at the edge; HTTP/2 frame fingerprints (SETTINGS ordering, header order) are only available where the platform terminates HTTP/2 itself (see the caveat in §6.2).
- The IP address is cross-referenced against AWS WAF Bot Control and MaxMind GeoIP databases to identify datacenter hosting providers (AWS, GCP, DigitalOcean, Hetzner), known Tor exit nodes, and commercial VPNs.
- When a client establishes a TLS 1.3 connection to Amazon CloudFront, CloudFront terminates TLS and exposes the handshake fingerprint as the
- Honeypot & Canary Trapping:
- Hidden CSS/DOM form inputs (e.g.,
<input type="text" name="b_address_zip_code" style="display:none" tabindex="-1">) are delivered in the HTML. - Legitimate humans do not interact with hidden fields. Automated headless bots (Puppeteer, Playwright, Selenium) auto-fill every input field. If the honeypot field is populated, the request is flagged with Risk Score 100.
- Hidden CSS/DOM form inputs (e.g.,
- Adaptive Challenge Dispatch (Tier 2):
- Risk Score (Human): The request proceeds directly to the registration endpoint without user disruption.
- Risk Score (Suspicious / Datacenter / High Velocity): The edge rejects direct form submission and responds with an HTTP 428 Precondition Required containing a Proof-of-Work Challenge (
seed,difficulty,expiration_epoch,hmac_signature). - The client browser executes WebAssembly/WebWorker hash iterations to find the valid
nonce.
- Stateful Identity & Disposable Domain Screening (Tier 3):
- Once submitted, the
Auth & Registration Servicere-validates the PoW solution in using a single SHA-256 hash (defense in depth: the edge already verified it and consumed the seed, so a request that reaches origin with a bad nonce indicates an edge bypass and is logged as such). - The user's email domain is checked against an in-memory Bloom Filter of disposable temporary email services.
- The client device fingerprint (combining Canvas render hash, WebGL vendor, and AudioContext phase curves) is checked in Amazon DynamoDB. If the fingerprint has registered accounts in the past 30 days, registration is blocked.
- The credential record commits atomically to DynamoDB via
TransactWriteItems: theUSER#item, anEMAIL#<email>uniqueness item withattribute_not_exists(PK), and theDEVICE#/USER#association. A GSI onemailcannot enforce uniqueness, so the dedicatedEMAIL#item is what makes two concurrent signups with the same address impossible.
- Once submitted, the
Zero-Cost Edge Screening via In-Memory Bloom Filters: Storing 100,000+ known disposable domains in an in-memory 117 KB bit vector ( hash functions, false positive rate) eliminates relational SQL lookups entirely. Clean signups traverse the domain gate in under , preserving origin database CPU and connection pools for legitimate state transitions.
End-to-End Request Tracing Walkthrough
| Step # | Event / Action | Component State | Distributed Transition | Output / Response |
|---|---|---|---|---|
| Step 1 | Client opens registration page | Client browser | HTTPS GET arrives at CloudFront Edge; Lambda@Edge captures TLS Client Hello | JA4 fingerprint computed: t13d1516h2_8daaf6150882_... |
| Step 2 | Edge ASN & IP Reputation Check | WAF Bot Control | IP evaluated: Residential IP, but JA4 matches known Go-HTTP/Python Requests fingerprint | Initial Risk Score computed: (Suspicious) |
| Step 3 | Client Submits Registration Form | Form submit event | POST /v1/auth/register arrives at CloudFront Edge without Proof-of-Work token | Edge Interceptor detects Risk Score ; blocks origin |
| Step 4 | Proof-of-Work Challenge Issued | Edge Challenge Engine | Lambda@Edge generates challenge: seed = UUIDv7, diff = 20, signs with HMAC | Returns HTTP 428 Precondition Required with PoW seed |
| Step 5 | Client Computes PoW Nonce | Client WebWorker | Browser executes WebAssembly SHA-256 loop: finds nonce = 8492019 in | Hash output satisfies condition: 00000a98f12... |
| Step 6 | Client Resubmits with Solution | Client Ingress ALB | Submits form payload + {seed, nonce, difficulty, signature} headers | Request admitted past edge; forwarded to Internal ALB |
| Step 7 | Server Verifies PoW Solution | Auth Service (ECS) | Server evaluates: SHA-256(seed + nonce); verifies 20 leading zeros | PoW valid (); advances to domain validation |
| Step 8 | Disposable Email Bloom Filter | Auth Service | Checks user@tempmailo.com domain against in-memory 117 KB Bloom Filter | Bloom Filter MATCH: identified as disposable email provider |
| Step 9 | Poison Pill Quarantine | Auth Service | Domain check fails; request aborted; attempts logged to security telemetry | Returns HTTP 400: "Disposable email addresses not allowed" |
| Step 10 | Clean Retry with Legitimate Email | Client re-submits | Re-submits with user@gmail.com; Bloom filter returns CLEAN (at least one of the probed bits is unset, so the domain is definitively not in the list) | Advances to Sybil Device Fingerprint check |
| Step 11 | Device Fingerprint Collision Check | Auth Service DynamoDB | Queries PK = DEVICE#8f92a10... for USER# association items created in the last 30 days | Zero collisions found ( for this device) |
| Step 12 | Atomic Credential Commit | Auth Service DynamoDB | TransactWriteItems: USER# item + EMAIL#<email>/UNIQUE item (attribute_not_exists(PK)) + DEVICE#/USER# link | Account created; returns HTTP 201 Created with auth token |
4. API Interface Design & Wire Protocol
1. Request Proof-of-Work Challenge (POST /v1/auth/challenge/pow)
httpPOST /v1/auth/challenge/pow HTTP/2 Host: auth.platform.com Content-Type: application/json X-Device-Fingerprint: 8f92a102938471bce0918234 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36... { "action": "ACCOUNT_REGISTRATION", "client_timestamp_epoch_ms": 1773648000000 }
Response: 200 OK(Puzzle Payload)
json{ "challenge_id": "pow_01HZX894KMNPQ9283", "seed": "7f8a9b2c-3d4e-5f6a-7b8c-9d0e1f2a3b4c", "difficulty_bits": 20, "algorithm": "SHA-256", "expires_at_epoch_ms": 1773648180000, "signature": "c8f92b40192a83e...hmac_signature" }
2. Submit Registration with Proof-of-Work Solution (POST /v1/auth/register)
httpPOST /v1/auth/register HTTP/2 Host: auth.platform.com Content-Type: application/json X-PoW-Challenge-Id: pow_01HZX894KMNPQ9283 X-PoW-Nonce: 8492019 X-PoW-Signature: c8f92b40192a83e...hmac_signature X-Device-Fingerprint: 8f92a102938471bce0918234 Idempotency-Key: 9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d { "email": "sarah.connor@gmail.com", "password": "<plaintext over TLS; the server derives the Argon2id hash - a client-side hash would itself become the password>", "username": "sarah_c_2026", "honeypot_field_company": "", "device_telemetry": { "canvas_hash": "a8f9021b", "webgl_vendor": "Apple Inc.", "screen_resolution": "2560x1440", "touch_support": false } }
Response: 201 Created
json{ "user_id": "usr_981402819284", "email": "sarah.connor@gmail.com", "status": "ACTIVE", "auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "created_at_epoch_ms": 1773648001200 }
3. Status Codes & Error Contracts
| HTTP Status | Reason Code | Error Contract Payload | Client Handling / Recovery Strategy |
|---|---|---|---|
201 Created | REGISTRATION_SUCCESS | Standard user context & session tokens | Log user in; redirect to onboarding dashboard |
400 Bad Request | DISPOSABLE_EMAIL | {"error": "Temporary disposable email prohibited"} | Prompt user to input standard business/personal email |
403 Forbidden | SYBIL_DEVICE_LIMIT | {"error": "Maximum accounts reached for this device"} | Block registration; log device hash to blacklisted set |
409 Conflict | EMAIL_ALREADY_EXISTS | {"error": "Account exists with this email address"} | Prompt user to navigate to Login / Password Reset |
422 Unprocessable | POW_SOLUTION_INVALID | {"error": "Nonce hash does not meet difficulty"} | Re-compute Proof-of-Work puzzle with valid nonce |
428 Precondition | POW_REQUIRED | {"error": "Proof of Work challenge required", "challenge": {...}} | Client initiates WebAssembly worker to solve PoW |
429 Too Many Requests | SUBNET_RATE_LIMITED | {"error": "Too many signups from your IP subnet"} | Exponential backoff; prompt user to retry in 10 mins |
5. Data Models & Storage Architecture
DynamoDB Single-Table Design: Users & Sybil Device Registry
Stores user credentials, device fingerprints, and account association mappings in Amazon DynamoDB.
Synthesizing vector architecture diagram...
Three item types share one DynamoDB table. USER items (PK USER#id) hold account data; DEVICE_FINGERPRINT items (PK DEVICE#hash) hold per-device counters such as total_accounts_created and a blacklist flag. DEVICE_ASSOCIATION items link the two: stored under the device's partition with the user in the sort key, so reading DEVICE#hash returns every account ever created from that device in one query, which is the direct signal of one device making many accounts. The GSI flips the key (GSI1PK USER#id), so the reverse question, which devices has this user used, is also one query. Designing both lookups into the keys means the fraud check at sign-up costs a single read instead of a scan.
DynamoDB Single-Table Schema Layout
Partition Key (PK) | Sort Key (SK) | GSI1_PK | GSI1_SK | Entity Attributes |
|---|---|---|---|---|
USER#usr_9814 | METADATA | EMAIL#sarah@gmail.com | METADATA | username: "sarah_c", status: "ACTIVE", created_at: 1773648000 |
EMAIL#sarah@gmail.com | UNIQUE | - | - | user_id: usr_9814 (written in the same transaction with attribute_not_exists(PK); this item, not the GSI, enforces email uniqueness) |
DEVICE#8f92a10... | METADATA | METADATA | METADATA | account_count: 1, is_blacklisted: false, first_seen: 1773648000 |
DEVICE#8f92a10... | USER#usr_9814 | USER#usr_9814 | DEVICE#8f92a10... | ip_address: "73.189.42.15", linked_at: 1773648000 |
POW#pow_01HZ... | METADATA | EXPIRES#1773648180 | METADATA | seed: "7f8a...", difficulty: 20, TTL = 1773648180 |
Redis Sliding Window Rate Limiter Lua Script
Enforces strict rate limits per IP subnet (/24) and per device fingerprint over a sliding 10-minute window.
lua-- KEYS[1]: rate_limit_key = "rate:reg:subnet:{ip_subnet_24}" -- ARGV[1]: current_epoch_ms -- ARGV[2]: window_size_ms (e.g., 600000 for 10 minutes) -- ARGV[3]: max_allowed_requests (e.g., 10 signups per /24 subnet per 10m) local current_time = tonumber(ARGV[1]) local window_start = current_time - tonumber(ARGV[2]) local max_limit = tonumber(ARGV[3]) -- 1. Remove all events older than the 10-minute sliding window redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, window_start) -- 2. Count active entries remaining in the sliding window local current_count = redis.call('ZCARD', KEYS[1]) if current_count >= max_limit then return 0 -- RATE LIMITED: SUB-NET LIMIT EXCEEDED end -- 3. Add current timestamp to sorted set redis.call('ZADD', KEYS[1], current_time, current_time) redis.call('PEXPIRE', KEYS[1], ARGV[2]) return 1 -- ALLOWED: RATE UNDER LIMIT
Unlock Complete Architecture & Production Runbooks
You have explored the free architectural preview (~38%). Spend 1 Coin to unlock the remaining 6 production deep-dive sections for a full 24 hours.