Skip to content

mutkuoz/007captcha

Repository files navigation

007captcha

007captcha - The Robophobic Verification Framework

Behavioral captcha that catches bots through real-time ball-tracking analysis.

npm npm license stars


Users follow a ball moving unpredictably across a canvas for 8 seconds. The trajectory is generated server-side in real-time and streamed as rendered images — future positions never exist on the client. The server runs a deep multi-layered analysis of the cursor trace: per-frame tracking enforcement, velocity-curvature power law fitting, spectral timing analysis, jerk profiling, sub-movement segmentation, reaction time modeling, drift detection, and environment fingerprinting — signals that are extremely difficult for automated agents to replicate convincingly.

All verification runs server-side. The client never holds scoring logic, detection parameters, or signing secrets. Tokens are HMAC-SHA256 signed, single-use, and expire automatically.

Zero runtime dependencies across all packages.

Features

  • Real-time ball streaming — Ball trajectories are computed tick-by-tick on the server and streamed as rendered images via SSE. Future positions don't exist until generated. No video, no DOM elements, no extractable assets.
  • Frame-level tracking enforcement — The client commits to its cursor position at the moment each frame is received. The server validates those commitments against the real ball positions it sent. Pre-computed cursor paths cannot pass.
  • Fully server-side verification — All scoring, detection, and token signing run on your server. The browser is a thin input-capture layer with no access to scoring logic or detection parameters.
  • Multi-layered behavioral analysis — 14 scoring signals including spectral timing analysis (DFT on inter-event intervals), velocity-curvature power law fitting, interval-regularity coefficient-of-variation, locally-detrended residual-noise analysis, jerk profiling, sub-movement segmentation, drift/bias detection, reaction time distribution modeling, and environment fingerprinting.
  • Hard bot flags — Eleven independent hard flags trigger an immediate bot verdict that bypasses scoring: non-monotonic or resolution-locked timestamps, navigator.webdriver, classic-textbook power-law fits (bot signature on this task), spectral periodicity in inter-event timing, mechanical timing with zero saccade pauses, near-zero post-detrend residual noise, inhumanly-tight cursor-to-ball precision, too-narrow distance distribution with full tight coverage, zero reaction time at direction changes, missing or clock-incoherent frame acknowledgments, and aggregate multi-signal suspicion.
  • Visual decoy camouflage — The rendered ball frame includes small independently-moving decoy shapes in the ball colour. Humans lock onto the larger coherent moving ball visually; naive centroid-extraction attacks (corner-background subtraction + colour-averaging) are materially biased, forcing adversaries to implement connected-component labelling + shape-specific filtering.
  • HMAC-SHA256 tokens — Single-use, signed server-side, auto-expire after 5 minutes. Verified with one function call.
  • Zero runtime dependencies — Server package uses only Node.js built-in crypto. No native modules, no C++ bindings, no external services.
  • Framework-agnostic — Vanilla JS via script tag, ES modules, or the @007captcha/react component. Works with any backend framework.
  • Light & dark themes — Built-in 'light', 'dark', and 'auto' (follows system preference) themes.
  • TypeScript-first — Full type definitions shipped with every package.

Quick Start

1. Install

pnpm add @007captcha/client @007captcha/server

2. Server (Express)

import express from 'express';
import { verify, BallChallengeManager } from '@007captcha/server';

const app = express();
const SECRET = process.env.CAPTCHA_SECRET || 'change-me';

const ball = new BallChallengeManager(SECRET);

app.use(express.json());

// Start a new session
app.post('/captcha/ball/start', (req, res) => {
  res.json(ball.createSession());
});

// Stream ball frames as SSE
app.get('/captcha/ball/:id/stream', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
  });
  let done = false;
  const ok = ball.startStreaming(
    req.params.id,
    (frame) => res.write(`event: frame\ndata: ${JSON.stringify(frame)}\n\n`),
    ()      => { done = true; res.write('event: end\ndata: {}\n\n'); res.end(); },
  );
  if (!ok) { res.end(); return; }
  req.on('close', () => { if (!done) ball.cancelSession(req.params.id); });
});

// Verify the submitted cursor trace and frame acks
app.post('/captcha/ball/:id/verify', (req, res) => {
  const { points, cursorStartT, frameAcks, origin, clientEnv } = req.body;
  const requestMeta = {
    userAgent: req.headers['user-agent'],
    acceptLanguage: req.headers['accept-language'],
  };
  res.json(ball.verify(
    req.params.id,
    points || [],
    cursorStartT || 0,
    frameAcks || [],
    origin || '',
    clientEnv,
    requestMeta,
  ));
});

// Token verification
app.post('/verify', async (req, res) => {
  res.json(await verify(req.body.token || '', SECRET));
});

app.listen(3007);

3. Client (vanilla)

<div id="captcha"></div>
<script src="https://unpkg.com/@007captcha/client/dist/umd/index.global.js"></script>
<script>
  OOSevenCaptcha.render({
    siteKey: 'change-me',
    container: '#captcha',
    serverUrl: window.location.origin,
    onSuccess(token) {
      fetch('/verify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token }),
      });
    },
  });
</script>

Or with ES modules:

import { render } from '@007captcha/client';

const widget = render({
  siteKey: 'change-me',
  container: '#captcha',
  serverUrl: window.location.origin,
  onSuccess: (token) => { /* send to server */ },
});

4. React

pnpm add @007captcha/client @007captcha/react
import { OOSevenCaptcha } from '@007captcha/react';

function App() {
  return (
    <OOSevenCaptcha
      siteKey="change-me"
      serverUrl={window.location.origin}
      onSuccess={(token) => { /* send to server */ }}
    />
  );
}

Server-Side Verification

After the challenge completes, the client receives a signed token. Send it to your backend and verify:

import { verify } from '@007captcha/server';

const result = await verify(token, SECRET);

if (result.success) {
  // result.score    — 0.0 (bot) to 1.0 (human)
  // result.verdict  — 'human', 'uncertain', or 'bot'
  // result.method   — 'ball'
}

Tokens are single-use and expire after 5 minutes.

Configuration

Option Type Default Description
siteKey string required Shared secret for HMAC token signing
container string | HTMLElement required CSS selector or DOM element to mount the widget
serverUrl string required Base URL for challenge endpoints
theme 'light' | 'dark' | 'auto' 'light' Color theme
timeLimit number 14000 Time limit in ms
onSuccess (token: string) => void Called when challenge passes
onFailure (error: Error) => void Called when challenge fails
onExpired () => void Called when token expires

Widget Instance

const widget = render({ ... });

widget.getToken()   // Current verification token
widget.reset()      // Reset for a new challenge
widget.destroy()    // Remove widget from DOM

Packages

Package Description
@007captcha/client Browser widget — renders the ball, captures cursor input and frame acks, communicates with server
@007captcha/server Node.js backend — session management, analysis, token signing & verification
@007captcha/react React component wrapper

Security Model

  • Server-side analysis — All scoring, detection, and token signing happen on the server. The client is a thin rendering layer that captures cursor input and sends it back along with per-frame commitments.
  • Frame-level temporal binding — For every streamed ball frame, the client sends a frameAck with its cursor position at the moment the frame was received. The server checks that these commitments align with the real ball positions it sent, that the latency distribution looks like network jitter (not a constant replay offset), and that the committed positions match the main cursor trace. A pre-computed cursor path cannot satisfy all three constraints.
  • No client secrets — The browser never holds detection logic, scoring thresholds, or signing keys.
  • Multi-signal behavioral analysis — Each challenge evaluates 14 independent signals: spectral timing analysis, velocity-curvature power law, interval-regularity CV, locally-detrended residual-noise analysis, jerk profiling, sub-movement segmentation, drift/bias detection, and more.
  • Hard bot flags — Eleven hard flags bypass scoring and return an immediate bot verdict: spectral peak ratios above 8.0, non-monotonic/duplicate/resolution-locked timestamps, navigator.webdriver === true, headless browser signatures, textbook 1/3 power-law fits (bots mimic the literature value; real humans on this task show β ≈ 0.65–1.05), mechanical inter-event timing with zero saccade pauses, near-zero residual noise after local detrending, frame-level tracking below 55%, inhumanly precise average distance, too-narrow cursor-to-ball distance distribution, missing or clock-incoherent frame acknowledgments, zero reaction time on ball-direction changes, and aggregate multi-signal suspicion.
  • Visual decoy camouflage — Each rendered frame includes small moving decoys in the ball colour. Naive centroid extraction (corner-background subtraction + all-non-bg-pixel averaging) becomes biased away from the real ball, forcing attackers to implement connected-component labelling and shape-specific filtering.
  • Environment fingerprinting — Client-collected browser signals (webdriver, plugins, screen dimensions, touch support) combined with server-side HTTP header analysis (User-Agent, Accept-Language).
  • Real-time streaming — Ball positions are computed tick-by-tick on the server and streamed as rendered images. Future positions don't exist until each frame is generated.
  • HMAC-SHA256 tokens — Single-use, signed server-side, 5-minute expiry.
  • Canvas rendering — No <video>, no extractable DOM assets, no readable coordinates in the markup.

Examples

Example Description
examples/express-server/ Express.js with the ball challenge, SSE streaming, and full verification
examples/react-app/ Vite + React with server-side verification
examples/vanilla-html/ Minimal HTML page with script tag

Run the Express Demo

pnpm install
pnpm demo
# → http://localhost:3007

Run the React Demo

pnpm build
cd examples/react-app
pnpm install
pnpm dev
# → Vite on http://localhost:5173, API on http://localhost:3007

Development

pnpm install          # Install dependencies
pnpm build            # Build all packages
pnpm test             # Run tests
pnpm test:watch       # Watch mode
pnpm demo             # Build + start demo server

Star History

Star History Chart

License

MIT

About

The robophophic verification framework that defends your website against AI agents.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors