Skip to content

clawplaza/ai-captcha

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AI CAPTCHA

npm license

Proof of Intelligence — verify intelligence behind API calls.

Traditional CAPTCHA: "Prove you're human." AI CAPTCHA: "Prove there's intelligence behind the call — and tell me what kind."

A zero-dependency TypeScript library that generates challenges, verifies answers, and builds behavioral profiles to distinguish scripts, humans, and AI agents.

┌────────────────────────────────────────────────────────────┐
│                    Who's calling your API?                  │
│                                                            │
│   Script          Human             AI Agent               │
│   ──────          ─────             ────────               │
│   curl loop       slow, imperfect   fast, near-perfect     │
│   fixed output    variable timing   consistent timing      │
│   no constraints  misses details    nails every constraint │
│                                                            │
│   ✗ BLOCKED       ✓ DETECTED        ✓ DETECTED             │
└────────────────────────────────────────────────────────────┘

What It Does

  1. Blocks scripts — Challenges require language comprehension; curl loops can't answer
  2. Scores AI likelihood — Behavioral analysis over multiple challenges profiles the caller
  3. Zero dependencies — Pure TypeScript, runs anywhere (Node, Deno, Bun, Cloudflare Workers)

Quick Start

npm install ai-captcha
import { generateChallenge, ruleVerify, extractSignal, analyzeProfile } from "ai-captcha";

// 1. Generate a challenge
const challenge = generateChallenge();
// → { type: "constraint", prompt: "Write exactly 8 words about the ocean.", verify_data: {...} }

// 2. Send `prompt` to caller (keep `verify_data` server-side)

// 3. Verify + extract signal
const result = ruleVerify(challenge.type, answer, challenge.verify_data);
const signal = extractSignal(challenge.type, answer, challenge.verify_data, responseTimeMs);

// 4. Accumulate signals, then analyze
const profile = analyzeProfile(signals);
// → { intelligence: 0.95, ai_likelihood: 0.82, confidence: 0.7, signals: {...} }

Four Challenge Types

Topic"Write one sentence about the ocean." Random topic from 200+ words. Tests basic language production.

Paraphrase"Say this in different words: 'The weather is beautiful today'" Must rephrase without copying (>80% similarity = fail). Tests comprehension.

Keyword"Write a sentence that includes both 'moon' and 'river'." Must include both words coherently. Random from 100+ pairs.

Constraint (the AI differentiator) — Format constraints that LLMs comply with >95% of the time, but humans frequently miss:

"Write exactly 8 words about the ocean." "Write 2 sentences about technology. Start the first with 'In' and the second with 'Yet'." "Write one sentence about the moon that ends with a question mark."

Architecture

                  ┌──────────────┐
  API Call ──────►│  Your Server │
                  └──────┬───────┘
                         │
              ┌──────────▼──────────┐
              │  Layer 1: Rules     │  Zero-cost, in-process
              │  Garbage filter     │  Blocks scripts, gibberish
              └──────────┬──────────┘
                         │
              ┌──────────▼──────────┐
              │  Layer 2: LLM       │  Optional Cloudflare Worker
              │  Semantic check     │  Judges language understanding
              └──────────┬──────────┘
                         │
              ┌──────────▼──────────┐
              │  Layer 3: Constraint│  In-process, zero-cost
              │  Format compliance  │  Measures constraint adherence
              └──────────┬──────────┘
                         │
              ┌──────────▼──────────┐
              │  Layer 4: Behavioral│  In-process, stateless
              │  Profile analysis   │  Timing + consistency → AI score
              └─────────────────────┘

Layer 1 (Rule Verification) — Built-in garbage filter. Catches empty answers, gibberish, direct copies. Intentionally lenient.

Layer 2 (LLM Verification) — Optional Cloudflare Worker that sends answers to an LLM judge. Adds repeat detection and suspicious tracking.

Layer 3 (Constraint Verification) — Built-in. Checks exact word counts, sentence starters, ending punctuation, keyword presence. Returns a compliance score (0-1).

Layer 4 (Behavioral Analysis) — Built-in, stateless. Analyzes accumulated signals to build a caller profile. You store the signals; the library does the math.

Behavioral Analysis

After 5+ challenges, analyzeProfile() returns a caller profile:

{
  intelligence: 0.95,      // P(has language ability) — filters scripts
  ai_likelihood: 0.82,     // P(is AI vs human) — behavioral signals
  confidence: 0.73,        // reliability (reaches 1.0 at 15+ samples)
  signals: {
    timing_regularity: 0.90,        // AI: consistent timing
    timing_word_correlation: 0.85,  // AI: time ∝ output length
    constraint_compliance: 0.95,    // AI: near-perfect format adherence
    quality_consistency: 0.92       // AI: stable performance
  }
}

How each signal works:

Signal What it measures AI Human Script
timing_regularity Response time variance Low CV (0.1-0.3) High CV (0.5-1.5) Near-zero CV
timing_word_correlation Time vs word count correlation Strong positive Weak/none None
constraint_compliance Format constraint adherence >95% 50-80% 0%
quality_consistency Performance stability Very stable Variable N/A

API Reference

Function Returns Description
generateChallenge(options?) Challenge Generate a random challenge. Restrict types via { types: ["constraint"] }
ruleVerify(type, answer, verifyData) VerifyResult Verify an answer. Returns { passed, reason?, constraint_score? }
extractSignal(type, answer, verifyData, responseTimeMs) ChallengeSignal Extract a behavioral signal from a challenge-response pair
analyzeProfile(signals) CallerProfile Analyze accumulated signals into a behavioral profile
stringSimilarity(a, b) number Dice coefficient on character bigrams (0-1)

Pools — extend or replace the built-in challenge pools:

import { TOPICS, SENTENCES, KEYWORD_PAIRS, SENTENCE_STARTERS, EXACT_WORD_COUNTS } from "ai-captcha";

Integration Pattern

Call 1: POST /api/work  { caller_id: "agent-1" }
  ← 403 { error: "CHALLENGE_REQUIRED", challenge: { id, prompt, expires_in } }

Call 2: POST /api/work  { caller_id: "agent-1", challenge_id, challenge_answer }
  ← 200 { success: true, profile: { intelligence: 0.9, ai_likelihood: 0.5, confidence: 0.07 }, next_challenge: {...} }

Call 10+: (profile becomes reliable)
  ← 200 { profile: { intelligence: 0.95, ai_likelihood: 0.82, confidence: 0.67 } }

See examples/express.ts for a complete working server.

Why Not Just Rate Limiting?

Rate limiting answers "how often?" — AI CAPTCHA answers "who's calling?"

Defense Blocks scripts Detects AI Detects humans
Rate limiting Partial No No
Traditional CAPTCHA Yes No No
AI CAPTCHA Yes Yes Yes

Error Codes

Code Meaning
CHALLENGE_REQUIRED No challenge provided — here's one to answer
CHALLENGE_INVALID Challenge ID not found or belongs to another caller
CHALLENGE_EXPIRED Challenge TTL exceeded (default: 5 min)
CHALLENGE_FAILED Verification failed — here's a new challenge

License

MIT — ClawPlaza


Built by ClawPlaza — the AI workforce marketplace.
Battle-tested in production protecting AI agent APIs.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors