libcaptcha/motion-attestation

GitHub: libcaptcha/motion-attestation

基于行为生物特征的人机验证库,通过分析鼠标移动、击键动态、触摸压力等多维度交互信号,识别并阻断自动化工具和机器人。

Stars: 1 | Forks: 0

# motion-attestation 通过行为生物识别进行人机交互验证。分析鼠标移动、点击模式、击键动态、滚动行为、触摸压力和设备传感器,以区分人类与机器人。返回完整性评分和异常标记。零依赖。 使用 undetected-chromedriver 的高级机器人可以通过信号检测。它们无法复制人手的不自主生物力学模式。 ## 安装 ``` npm install motion-attestation ``` ## 用法 ### 收集信号 (浏览器) ``` 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); ``` ### 分析交互 (服务器) ``` 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" ``` ### 挑战-响应协议 ``` // 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 签名 ``` 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 ``` ## 协议 ``` 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 | |<-- { message, score } -------------------| ``` ## 收集的信号 | 类别 | 数据 | 桌面 | 移动 | | -------------- | -------------------------------------- | :--: | :--: | | 鼠标位置 | 带时间戳的亚像素 x,y | \* | | | 点击落点 | 距目标中心的偏移量 + 停留时间 | \* | | | 击键时序 | 按键持续时间 + 键间间隔 | \* | \* | | 滚动行为 | 位置、增量、时间戳 | \* | \* | | 触摸事件 | 位置、压力、接触半径 | | \* | | 加速度计 | 三轴加速度读数 | | \* | | 陀螺仪 | 三轴旋转速率 | | \* | | 设备方向 | Alpha, beta, gamma 角度 | | \* | | 事件顺序 | 所有事件类型的时间戳序列 | \* | \* | | 绑定元素命中 | 每个绑定元素距中心的点击偏移 | \* | \* | | 参与度 | 首次交互时间、会话持续时间 | \* | \* | ## 分析算法 ### 鼠标移动 — _权重:0.30_ 核心信号。人类运动控制遵循生物力学限制,这些限制极难伪造。 | 检查项 | 人类 | 机器人 | 惩罚值 | | -------------------- | -------------------------------- | ---------------------------------- | --------- | | **曲率熵** | 曲率可变,高熵 | 近零曲率,低熵 | 0.05-0.12 | | **微颤动** | 0.3-8px 抖动 (8-12 Hz 颤动) | <0.05px 平滑或 >20px 噪声 | 0.06-0.10 | | **速度方差** | CV >0.4 (Fitts' Law 曲线) | CV <0.15 (恒定速度) | 0.05-0.12 | | **加加速度分析** | 高方差 (修正动作) | 低方差 (平滑函数) | 0.06 | | **平直度指数** | 1.02-1.5 (自然曲线) | ~1.000 (尺子般笔直) | 0.04-0.10 | | **方向熵** | >2.5 (正态分布) | <1.2 (离散角度) | 0.08 | | **时序规律性** | 间隔可变 | >70% 相同间隔 | 0.08-0.10 | | **瞬移** | 从不 | <10ms 内 >300px | 0.08-0.15 | | **原点聚类** | 从不 | 点位于 (0,0) | 0.08 | | **Bezier 检测** | 不规则加速 | >85% 恒定加速 | 0.10 | | **时间戳格式** | 整数毫秒 | 非整数 (合成生成) | 0.10 | | **亚像素精度** | 0-2 位小数 | >6 位小数 (Math.random) | 0.08-0.15 | | **自相关** | 非周期性运动 | 周期性模式 (正弦波) | 0.10 | | **运动连续性** | 暂停 >150ms (思考) | 无暂停 (连续自动化) | 0.06 | | **速度极小值** | 修正性子运动 | 无轨迹中间修正 | 0.06 | ### 点击落点 — _权重:0.15_ 人类几乎从不点击元素的精确几何中心。 | 检查项 | 人类 | 机器人 | 惩罚值 | | ------------------ | ---------------------- | ---------------------------- | --------- | | **中心偏移** | >5% 偏移,可变 | >70% 在中心 5% 范围内 | 0.06-0.12 | | **偏移方差** | std >0.02 | std <0.02 (完全相同) | 0.08 | | **点击停留时间** | 60-250ms,可变 | <10ms 或完全均匀 | 0.06-0.08 | | **零持续时间** | 从不 | 没有 mousedown 即分发 | 0.08 | ### 点击前减速 — _权重:0.10_ Fitts' Law:人类在接近点击目标时会减速。 | 检查项 | 人类 | 机器人 | | -------------------------- | -------------------------- | ---------------------------------------- | | 点击前最后 500ms 的速度 | 递减 (接近阶段) | 恒定或增加 (无目标定位行为) | ### 击键动态 — _权重:0.15_ 每个人都有独特的打字节奏,无法完美复制。 | 检查项 | 人类 | 机器人 | 惩罚值 | | -------------------- | ------------------------ | ---------------------------- | --------- | | **停留时间** | 50-200ms,可变 | <5ms 或跨键完全相同 | 0.08-0.10 | | **飞行时间** | 30-500ms,可变 | <15ms (不可能) 或均匀 | 0.08-0.10 | | **节奏熵** | >2.0 (自然方差) | <1.5 (机械精度) | 0.06 | | **均匀检测** | CV >0.15 | CV <0.08 (机器人般的均匀性) | 0.08 | ### 滚动行为 — _权重:0.10_ | 检查项 | 人类 | 机器人 | | ------------------ | ------------------------ | ---------------------------- | | **速度方差** | 爆发 + 暂停可变 | 恒定速度 (CV <0.15) | | **方向反转** | 频繁 (重读) | 无 (单向) | | **暂停** | >300ms 间隔 (阅读) | 连续滚动 | | **增量均匀性** | 滚动量可变 | 固定增量 (CV <0.05) | ### 触摸生物识别 — _权重:0.10_ 真实的手指会产生压力分布和团块状的接触面积。 | 检查项 | 人类 | 机器人 | | -------------------- | ------------------------------------ | -------------------------------------- | | **压力变化** | CV >0.01 (手势过程中变化) | CV ~0 (恒定或缺失) | | **接触面积** | radiusX/Y 随手指滚动而变化 | 恒定或为零 | | **轨迹抖动** | 亚像素偏差 | RMS <0.3px (几何上完美) | | **末端减速** | 滑动结束时的自然减速 | 恒定速度或突然停止 | ### 传感器数据 — _权重:0.10_ 手持设备的人类会引入不自主的微运动。 | 检查项 | 人类 | 机器人 | | ---------------------- | --------------------------------------- | ------------------------ | | **加速度计噪声** | 每轴 stddev 0.01-0.5 (微振动) | 零或完全静止 | | **陀螺仪震颤** | 可检测到 8-12 Hz 震荡 | 零旋转速率 | | **方向漂移** | 随时间缓慢的自然漂移 | 完全固定的角度 | | **跨轴相关性** | 轴之间的相关噪声 | 独立或为零 | ### 事件顺序 — _权重:0.05_ 浏览器事件按特定顺序触发。违规表明是合成分发。 ``` Expected: mousemove -> mousedown -> mouseup -> click keydown -> keyup touchstart -> touchmove -> touchend ``` | 异常 | 惩罚值 | | -------------------------------------- | --------- | | 点击前没有 mousedown/mouseup | 每次 0.02 | | mousedown/mouseup 时间戳相同 | 0.03 | | touchmove 前没有 touchstart | 0.02 | | keyup 前没有 keydown | 0.02 | ### 合成事件检测 — _权重:0.15_ 跨信号分析,即使单个信号看起来合理,也能捕捉到自动化框架。 | 检查项 | 人类 | 机器人 | 惩罚值 | | ---------------------------- | ------------------ | --------------------------------- | ------ | | **跨信号快速分发** | 正常停留时间 | 点击和按键均 <5ms | 0.10 | | **单通道快速** | 正常停留时间 | 点击或按键 <5ms | 0.04 | | **零时间点击对** | mousedown/mouseup 间隔 >0 | 时间戳相同 (CDP 分发) | 0.05 | ### 参与度 — _权重:0.05_ | 检查项 | 人类 | 机器人 | | ---------------------- | ---------------------------- | --------------------- | | **首次交互时间** | >200ms (视觉处理) | <50ms (预脚本) | | **事件密度** | 会话持续时间内的合理值 | <500ms 内 >50 个事件 | ## 评分 ``` 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 ``` 阈值可通过 `scoreThreshold` 选项配置。 ## 配置 | 选项 | 默认值 | 描述 | | ---------------- | -------------- | ---------------------------- | | `secretKey` | 随机 32 字节 | Token 签名密钥 | | `scoreThreshold` | `0.5` | 通过的最低分数 (0.0-1.0) | | `debug` | `false` | 在响应中包含完整分析 | | `challengeTtl` | `60000` | 挑战过期时间 (毫秒) | ## 为什么有效 | 攻击方式 | 防御手段 | | ------------------------------- | ---------------------------------------------------------------------- | | Selenium/Puppeteer `moveTo()` | 产生速度均匀且曲率熵为零的直线 | | Bezier 曲线鼠标库 | 通过恒定的二阶导数检测 (过于平滑的加速) | | 录制的人类回放 | HMAC nonce 防止回放;时间戳不匹配 | | 合成 `dispatchEvent()` | 缺少 mousedown/mouseup 序列;点击停留时间为零 | | 无头触摸模拟 | 零压力,零接触半径,无抖动 | | 模拟传感器 | 零噪声底,无跨轴相关性 | | 快速 `sendKeys()` | 飞行时间 <15ms (物理上不可能),零停留方差 | | WindMouse / ghost cursor | 自相关,缺少速度极小值,无运动暂停 | | Perlin/Catmull-Rom 路径 | 亚像素精度异常,周期性自相关 | | CDP 鼠标分发 | 零时间 mousedown/mouseup 对,跨信号快速分发 | 基本洞察:**人类运动控制受生物力学限制支配** (Fitts' Law、生理性震颤、速度-准确性权衡),这些限制在运动数据中产生特征性的统计特征。这些特征是不自主的,并且在分布层面上极难复制,即使单个数据点可以被伪造。 ### 元素绑定 追踪特定交互元素上的点击精度: ``` 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); ``` 绑定元素的点击会针对中心偏移精度进行分析 —— 机器人点击精确中心,人类则不会。 ## 示例 ``` npm run example # http://localhost:3002 ``` ## 测试 ``` npm test # unit tests npm run test:e2e # playwright e2e (10 bot algorithms) npm run test:all # both ``` ### E2E 机器人算法 E2E 测试套件通过真实浏览器运行 10 种拟人化算法,并验证全部被检测到: | 算法 | 技术 | | --------------- | ---------------------------------------- | | Linear | 带有随机抖动的直线 | Bezier | 三次 Bezier 曲线插值 | | Sinusoidal | 具有可变振幅的正弦波路径 | | WindMouse | Ghost Mouse 算法 (风 + 重力模型) | | Overshoot | 目标超调并修正 | | Perlin | 类 Perlin 噪声位移 | | Spring-Damper | 物理弹簧模拟 | | Gaussian Jitter | 线性路径上的高斯噪声 | | Catmull-Rom | 穿过随机控制点的样条曲线 | | Bell Velocity | 钟形曲线速度分布 | 在单元测试和真实浏览器 E2E 测试中,所有 10 种算法的得分均低于 0.5 (被阻止)。 ## 格式化 ``` npx prtfm ``` ## 许可证 [MIT](LICENSE)
标签:CAPTCHA替代, GNU通用公共许可证, JavaScript库, MITM代理, Node.js, Web安全, 人机验证, 传感器数据, 击键动力学, 反欺诈, 完整性评分, 异常检测, 挑战-响应, 数据可视化, 无感验证, 机器人检测, 浏览器指纹, 生物特征识别, 自定义脚本, 蓝队分析, 行为生物识别, 零依赖, 风控系统, 鼠标轨迹分析