Skip to content

libcaptcha/motion-attestation

Repository files navigation

motion-attestation

Human interaction verification through behavioral biometrics. Analyzes mouse movement, click patterns, keystroke dynamics, scroll behavior, touch pressure, and device sensors to distinguish humans from bots. Returns integrity score and anomaly flags. Zero dependencies.

Sophisticated bots with undetected-chromedriver can pass signal checks. They cannot replicate the involuntary biomechanical patterns of a human hand.

Install

npm install motion-attestation

Usage

Collect Signals (Browser)

import { createCollector } from 'motion-attestation';

const collector = createCollector();
collector.attach();

// Track clicks on specific elements
collector.bind(document.getElementById('submit'), 'submit-btn');
collector.bind(document.getElementById('agree'), 'agree-checkbox');

// Wait for enough data (3-15 seconds of interaction)
const interval = setInterval(() => {
    if (collector.isReady()) {
        clearInterval(interval);
        const data = collector.getData();
        collector.detach();
        // Send data to server for analysis
    }
}, 500);

Analyze Interactions (Server)

import { analyze, classifyScore } from 'motion-attestation';

const { score, penalty, reasons, categories } = analyze(data);
// score: 0.0-1.0 (1.0 = human, 0.0 = bot)
// penalty: total deductions
// reasons: ["[mouse] Low curvature entropy: 0.82 (straight-line)"]
// categories: per-category { penalty, maxPenalty, reasons }

const verdict = classifyScore(score);
// "human" | "suspicious" | "bot"

Challenge-Response Protocol

// Server
import { createServer } from 'motion-attestation';

const attestation = createServer({
    secretKey: process.env.MOTION_SECRET, // optional
    scoreThreshold: 0.5,
});

// Mount on existing HTTP server
import { createServer as createHttpServer } from 'node:http';
const httpServer = createHttpServer(attestation.handler());
httpServer.listen(3000);

// Verify tokens from downstream services
const payload = attestation.validateToken(token);
// Client (browser)
import { createCollector } from 'motion-attestation';

// 1. Init challenge
const { challengeId } = await fetch('/interactions/init', {
    method: 'POST',
}).then((r) => r.json());

// 2. Collect interactions
const collector = createCollector();
collector.attach();

// 3. Submit when ready
const data = collector.getData();
collector.detach();
const response = await fetch('/interactions/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ cid: challengeId, d: data, ts: Date.now() }),
});
const { cleared, score, token, flags } = await response.json();

Token Signing

import { signToken, verifyToken, generateKey } from 'motion-attestation';

const key = generateKey();
const token = signToken({ score: 0.95, iat: Date.now() }, key);
const payload = verifyToken(token, key); // null if invalid/expired

Protocol

Client                                    Server
  |                                          |
  |--- POST /interactions/init ------------->|  Create challenge
  |<-- { challengeId, ttl } -----------------|
  |                                          |
  |    +----------------------+              |
  |    | Collect for 3-15s:   |              |
  |    | * Mouse movement     |              |
  |    | * Click positions    |              |
  |    | * Keystroke timing   |              |
  |    | * Scroll patterns    |              |
  |    | * Touch + pressure   |              |
  |    | * Gyro/Accel sensors |              |
  |    | * Event ordering     |              |
  |    +----------------------+              |
  |                                          |
  |--- POST /interactions/verify ----------->|  Analyze biometrics
  |    { cid, d, ts }                        |
  |<-- { cleared, score, token, flags } -----|
  |                                          |
  |--- GET /protected ---------------------->|  Bearer token validation
  |    Authorization: Bearer <token>         |
  |<-- { message, score } -------------------|

Signals Collected

Category Data Desktop Mobile
Mouse position Sub-pixel x,y with timestamps *
Click landing Offset from target center + dwell time *
Keystroke timing Hold duration + inter-key gaps * *
Scroll behavior Position, delta, timestamps * *
Touch events Position, pressure, contact radius *
Accelerometer 3-axis acceleration readings *
Gyroscope 3-axis rotation rate *
Device orientation Alpha, beta, gamma angles *
Event ordering Timestamped sequence of all event types * *
Bound element hits Click offset from center per bound element * *
Engagement Time-to-first-interaction, session duration * *

Analysis Algorithms

Mouse Movement — weight: 0.30

The core signal. Human motor control follows biomechanical constraints that are extremely hard to fake.

Check Human Bot Penalty
Curvature entropy Variable curvature, high entropy Near-zero curvature, low entropy 0.05-0.12
Micro-tremor 0.3-8px wobble (8-12 Hz tremor) <0.05px smooth or >20px noise 0.06-0.10
Velocity variance CV >0.4 (Fitts' Law profile) CV <0.15 (constant speed) 0.05-0.12
Jerk analysis High variance (corrections) Low variance (smooth function) 0.06
Straightness index 1.02-1.5 (natural curves) ~1.000 (ruler-straight) 0.04-0.10
Direction entropy >2.5 (normal distribution) <1.2 (discrete angles) 0.08
Timing regularity Variable intervals >70% identical intervals 0.08-0.10
Teleportation Never >300px in <10ms 0.08-0.15
Origin clustering Never Points at (0,0) 0.08
Bezier detection Irregular acceleration >85% constant acceleration 0.10
Timestamp format Integer milliseconds Non-integer (synthetic generation) 0.10
Sub-pixel precision 0-2 decimal places >6 decimal places (Math.random) 0.08-0.15
Autocorrelation Aperiodic movement Periodic patterns (sinusoidal) 0.10
Movement continuity Pauses >150ms (thinking) No pauses (continuous automation) 0.06
Velocity minima Corrective sub-movements No mid-trajectory corrections 0.06

Click Landing — weight: 0.15

Humans almost never click the exact geometric center of an element.

Check Human Bot Penalty
Center offset >5% offset, variable >70% within 5% of center 0.06-0.12
Offset variance std >0.02 std <0.02 (identical) 0.08
Click dwell time 60-250ms, variable <10ms or perfectly uniform 0.06-0.08
Zero-duration Never Dispatched without mousedown 0.08

Pre-click Deceleration — weight: 0.10

Fitts' Law: humans decelerate as they approach a click target.

Check Human Bot
Velocity in last 500ms before click Decreasing (approach phase) Constant or increasing (no targeting behavior)

Keystroke Dynamics — weight: 0.15

Every human has a unique typing rhythm that is impossible to perfectly replicate.

Check Human Bot Penalty
Dwell time 50-200ms, variable <5ms or identical across keys 0.08-0.10
Flight time 30-500ms, variable <15ms (impossible) or uniform 0.08-0.10
Rhythm entropy >2.0 (natural variance) <1.5 (mechanical precision) 0.06
Uniform detection CV >0.15 CV <0.08 (robotic uniformity) 0.08

Scroll Behavior — weight: 0.10

Check Human Bot
Velocity variance Variable bursts + pauses Constant velocity (CV <0.15)
Direction reversals Frequent (re-reading) None (one-directional)
Pauses >300ms gaps (reading) Continuous scrolling
Delta uniformity Variable scroll amounts Fixed increments (CV <0.05)

Touch Biometrics — weight: 0.10

Real fingers produce a pressure distribution and blob-shaped contact area.

Check Human Bot
Pressure variation CV >0.01 (changing during gesture) CV ~0 (constant or absent)
Contact area radiusX/Y vary as finger rolls Constant or zero
Trajectory wobble Sub-pixel deviations RMS <0.3px (geometrically perfect)
End deceleration Natural slowdown at swipe end Constant velocity or abrupt stop

Sensor Data — weight: 0.10

A human holding a device introduces involuntary micro-movement.

Check Human Bot
Accelerometer noise stddev 0.01-0.5 per axis (micro-vibrations) Zero or perfectly static
Gyroscope tremor Detectable 8-12 Hz oscillation Zero rotation rate
Orientation drift Slow natural drift over time Perfectly fixed angles
Cross-axis correlation Correlated noise between axes Independent or zero

Event Ordering — weight: 0.05

Browser events fire in specific sequences. Violations indicate synthetic dispatch.

Expected:  mousemove -> mousedown -> mouseup -> click
           keydown -> keyup
           touchstart -> touchmove -> touchend
Anomaly Penalty
Click without preceding mousedown/mouseup 0.02 each
mousedown/mouseup at identical timestamp 0.03
touchmove without preceding touchstart 0.02
keyup without preceding keydown 0.02

Synthetic Event Detection — weight: 0.15

Cross-signal analysis that catches automation frameworks even when individual signals look plausible.

Check Human Bot Penalty
Cross-signal fast dispatch Normal dwell times Both clicks AND keys <5ms 0.10
Single-channel fast Normal dwell times Clicks OR keys <5ms 0.04
Zero-time click pairs mousedown/mouseup gap >0 Identical timestamps (CDP dispatch) 0.05

Engagement — weight: 0.05

Check Human Bot
Time to first interaction >200ms (visual processing) <50ms (pre-scripted)
Event density Reasonable for session duration >50 events in <500ms

Scoring

Final Score = 1.0 - sum(category penalties)

Each category has a maximum penalty cap:
  Mouse:       0.30    Click:        0.15
  Pre-click:   0.10    Keystrokes:   0.15
  Scroll:      0.10    Touch:        0.10
  Sensors:     0.10    Event order:  0.05
  Synthetic:   0.15    Engagement:   0.05
                       -----------------
  Maximum total:       1.15 (capped at 1.00)

Score >= 0.5 -> CLEARED (human)     Token issued
Score <  0.5 -> BLOCKED (bot)       No token

The threshold is configurable via scoreThreshold option.

Configuration

Option Default Description
secretKey Random 32 bytes Token signing secret
scoreThreshold 0.5 Minimum score to clear (0.0-1.0)
debug false Include full analysis in response
challengeTtl 60000 Challenge expiry in milliseconds

Why This Works

Attack Defense
Selenium/Puppeteer moveTo() Produces straight lines with uniform velocity and zero curvature entropy
Bezier curve mouse libraries Detectable by constant second derivative (too-smooth acceleration)
Recorded human replay HMAC nonce prevents replay; timestamps won't match
Synthetic dispatchEvent() Missing mousedown/mouseup sequence; zero click dwell
Headless touch simulation Zero pressure, zero contact radius, no wobble
Emulated sensors Zero noise floor, no cross-axis correlation
Fast sendKeys() Flight times <15ms (physically impossible), zero dwell variance
WindMouse / ghost cursor Autocorrelation, missing velocity minima, no movement pauses
Perlin/Catmull-Rom paths Sub-pixel precision anomaly, periodic autocorrelation
CDP mouse dispatch Zero-time mousedown/mouseup pairs, cross-signal fast dispatch

The fundamental insight: human motor control is governed by biomechanical constraints (Fitts' Law, physiological tremor, speed-accuracy tradeoff) that produce characteristic statistical signatures in movement data. These signatures are involuntary and extremely difficult to replicate at the distribution level, even when individual data points can be faked.

Element Binding

Track click accuracy on specific interactive elements:

const collector = createCollector();
collector.attach();

collector.bind(submitBtn, 'submit');
collector.bind(checkbox, 'agree');

// getData() includes:
// bc: [[offsetX, offsetY, dwell, width, height, time, index], ...]
// bl: ['submit', 'agree']

collector.unbind(submitBtn);

Bound element clicks are analyzed for center offset precision — bots click exact center, humans don't.

Example

npm run example
# http://localhost:3002

Test

npm test           # unit tests
npm run test:e2e   # playwright e2e (10 bot algorithms)
npm run test:all   # both

E2E Bot Algorithms

The e2e suite runs 10 humanization algorithms through real browsers and verifies all are detected:

Algorithm Technique
Linear Straight line with random jitter
Bezier Cubic bezier curve interpolation
Sinusoidal Sine wave path with variable amplitude
WindMouse Ghost Mouse algorithm (wind + gravity model)
Overshoot Target overshoot with correction
Perlin Perlin-like noise displacement
Spring-Damper Physics spring simulation
Gaussian Jitter Gaussian noise on linear path
Catmull-Rom Spline through random control points
Bell Velocity Bell-curve speed profile

All 10 score below 0.5 (blocked) in both unit tests and real browser e2e tests.

Formatting

npx prtfm

License

MIT

About

Human interaction verification through behavioral biometrics. Analyzes mouse movement, click patterns, keystroke dynamics, scroll behavior, touch pressure, and device sensors to distinguish humans from bots.

Topics

Resources

License

Stars

Watchers

Forks