@surbee/cipher 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +341 -0
- package/dist/index.d.ts +341 -0
- package/dist/index.js +202 -0
- package/dist/index.mjs +174 -0
- package/package.json +45 -0
- package/src/checks/behavioral.ts +527 -0
- package/src/checks/content.ts +372 -0
- package/src/checks/device.ts +384 -0
- package/src/checks/index.ts +59 -0
- package/src/checks/timing.ts +256 -0
- package/src/cipher.ts +225 -0
- package/src/index.ts +75 -0
- package/src/tiers.ts +507 -0
- package/src/types.ts +366 -0
- package/test/cipher.test.ts +245 -0
- package/test/fixtures.ts +627 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavioral checks
|
|
3
|
+
*
|
|
4
|
+
* Analyzes mouse, keyboard, and interaction patterns to detect bots.
|
|
5
|
+
* All checks in this file are offline (no API required).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CheckResult, BehavioralMetrics } from '../types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check: Low Interaction
|
|
12
|
+
*
|
|
13
|
+
* Detects minimal mouse/keyboard activity relative to survey duration.
|
|
14
|
+
* Bots often have very few interaction events.
|
|
15
|
+
*/
|
|
16
|
+
export function checkLowInteraction(metrics?: BehavioralMetrics): CheckResult {
|
|
17
|
+
if (!metrics) {
|
|
18
|
+
return {
|
|
19
|
+
checkId: 'low_interaction',
|
|
20
|
+
passed: true,
|
|
21
|
+
score: 0,
|
|
22
|
+
details: 'No behavioral data available',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const durationSeconds = (metrics.duration || 1) / 1000;
|
|
27
|
+
const totalInteractions =
|
|
28
|
+
metrics.mouseMovementCount +
|
|
29
|
+
metrics.keypressCount +
|
|
30
|
+
metrics.scrollEventCount;
|
|
31
|
+
|
|
32
|
+
// Expect at least 1 interaction per second on average
|
|
33
|
+
const interactionsPerSecond = totalInteractions / durationSeconds;
|
|
34
|
+
|
|
35
|
+
if (interactionsPerSecond < 0.1) {
|
|
36
|
+
return {
|
|
37
|
+
checkId: 'low_interaction',
|
|
38
|
+
passed: false,
|
|
39
|
+
score: 1.0,
|
|
40
|
+
details: 'Almost no user interaction detected',
|
|
41
|
+
data: { interactionsPerSecond, totalInteractions, durationSeconds },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (interactionsPerSecond < 0.5) {
|
|
46
|
+
return {
|
|
47
|
+
checkId: 'low_interaction',
|
|
48
|
+
passed: true,
|
|
49
|
+
score: 0.6,
|
|
50
|
+
details: 'Below average interaction rate',
|
|
51
|
+
data: { interactionsPerSecond },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
checkId: 'low_interaction',
|
|
57
|
+
passed: true,
|
|
58
|
+
score: 0,
|
|
59
|
+
data: { interactionsPerSecond },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check: Excessive Paste
|
|
65
|
+
*
|
|
66
|
+
* Detects heavy copy-paste behavior which might indicate:
|
|
67
|
+
* - AI-generated content being pasted
|
|
68
|
+
* - Copying from other sources
|
|
69
|
+
* - Bot automation
|
|
70
|
+
*/
|
|
71
|
+
export function checkExcessivePaste(metrics?: BehavioralMetrics): CheckResult {
|
|
72
|
+
if (!metrics) {
|
|
73
|
+
return {
|
|
74
|
+
checkId: 'excessive_paste',
|
|
75
|
+
passed: true,
|
|
76
|
+
score: 0,
|
|
77
|
+
details: 'No behavioral data available',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const { pasteEvents, keypressCount } = metrics;
|
|
82
|
+
|
|
83
|
+
// If more pastes than manual keypresses, very suspicious
|
|
84
|
+
if (pasteEvents > 0 && keypressCount === 0) {
|
|
85
|
+
return {
|
|
86
|
+
checkId: 'excessive_paste',
|
|
87
|
+
passed: false,
|
|
88
|
+
score: 1.0,
|
|
89
|
+
details: 'All content was pasted, no typing detected',
|
|
90
|
+
data: { pasteEvents, keypressCount },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Ratio of paste events to keypresses
|
|
95
|
+
const pasteRatio = keypressCount > 0 ? pasteEvents / keypressCount : 0;
|
|
96
|
+
|
|
97
|
+
if (pasteRatio > 0.5) {
|
|
98
|
+
return {
|
|
99
|
+
checkId: 'excessive_paste',
|
|
100
|
+
passed: false,
|
|
101
|
+
score: 0.8,
|
|
102
|
+
details: 'High paste-to-typing ratio',
|
|
103
|
+
data: { pasteRatio, pasteEvents },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (pasteRatio > 0.2) {
|
|
108
|
+
return {
|
|
109
|
+
checkId: 'excessive_paste',
|
|
110
|
+
passed: true,
|
|
111
|
+
score: 0.4,
|
|
112
|
+
details: 'Some paste events detected',
|
|
113
|
+
data: { pasteRatio },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
checkId: 'excessive_paste',
|
|
119
|
+
passed: true,
|
|
120
|
+
score: 0,
|
|
121
|
+
data: { pasteEvents },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check: Pointer Velocity Spikes
|
|
127
|
+
*
|
|
128
|
+
* Detects unnatural mouse movement patterns:
|
|
129
|
+
* - Teleporting (instant jumps)
|
|
130
|
+
* - Perfectly linear movement
|
|
131
|
+
* - Inhuman speeds
|
|
132
|
+
*/
|
|
133
|
+
export function checkPointerSpikes(metrics?: BehavioralMetrics): CheckResult {
|
|
134
|
+
if (!metrics?.mouseMovements || metrics.mouseMovements.length < 10) {
|
|
135
|
+
return {
|
|
136
|
+
checkId: 'pointer_spikes',
|
|
137
|
+
passed: true,
|
|
138
|
+
score: 0,
|
|
139
|
+
details: 'Insufficient mouse data',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const velocities = metrics.mouseMovements
|
|
144
|
+
.map(m => m.velocity)
|
|
145
|
+
.filter(v => v > 0);
|
|
146
|
+
|
|
147
|
+
if (velocities.length === 0) {
|
|
148
|
+
return {
|
|
149
|
+
checkId: 'pointer_spikes',
|
|
150
|
+
passed: true,
|
|
151
|
+
score: 0,
|
|
152
|
+
details: 'No velocity data',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check for velocity spikes (inhuman speeds > 50 pixels/ms)
|
|
157
|
+
const spikeThreshold = 50;
|
|
158
|
+
const spikes = velocities.filter(v => v > spikeThreshold);
|
|
159
|
+
const spikeRatio = spikes.length / velocities.length;
|
|
160
|
+
|
|
161
|
+
if (spikeRatio > 0.3) {
|
|
162
|
+
return {
|
|
163
|
+
checkId: 'pointer_spikes',
|
|
164
|
+
passed: false,
|
|
165
|
+
score: 0.9,
|
|
166
|
+
details: 'Many unnatural mouse speed spikes detected',
|
|
167
|
+
data: { spikeRatio, spikeCount: spikes.length },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (spikeRatio > 0.1) {
|
|
172
|
+
return {
|
|
173
|
+
checkId: 'pointer_spikes',
|
|
174
|
+
passed: true,
|
|
175
|
+
score: 0.4,
|
|
176
|
+
details: 'Some unusual mouse movements',
|
|
177
|
+
data: { spikeRatio },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
checkId: 'pointer_spikes',
|
|
183
|
+
passed: true,
|
|
184
|
+
score: 0,
|
|
185
|
+
data: { spikeRatio },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Check: Robotic Typing
|
|
191
|
+
*
|
|
192
|
+
* Detects uniform keystroke timing that suggests automation.
|
|
193
|
+
* Humans have natural variation in typing rhythm.
|
|
194
|
+
*/
|
|
195
|
+
export function checkRoboticTyping(metrics?: BehavioralMetrics): CheckResult {
|
|
196
|
+
if (!metrics?.keystrokeDynamics || metrics.keystrokeDynamics.length < 10) {
|
|
197
|
+
return {
|
|
198
|
+
checkId: 'robotic_typing',
|
|
199
|
+
passed: true,
|
|
200
|
+
score: 0,
|
|
201
|
+
details: 'Insufficient keystroke data',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const dwellTimes = metrics.keystrokeDynamics.map(k => k.dwell).filter(d => d > 0 && d < 1000);
|
|
206
|
+
|
|
207
|
+
if (dwellTimes.length < 5) {
|
|
208
|
+
return {
|
|
209
|
+
checkId: 'robotic_typing',
|
|
210
|
+
passed: true,
|
|
211
|
+
score: 0,
|
|
212
|
+
details: 'Not enough valid keystroke data',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Calculate coefficient of variation
|
|
217
|
+
const mean = dwellTimes.reduce((a, b) => a + b, 0) / dwellTimes.length;
|
|
218
|
+
const variance = dwellTimes.reduce((sum, t) => sum + Math.pow(t - mean, 2), 0) / dwellTimes.length;
|
|
219
|
+
const stddev = Math.sqrt(variance);
|
|
220
|
+
const cv = stddev / mean;
|
|
221
|
+
|
|
222
|
+
// Humans typically have CV > 0.25 for keystroke timing
|
|
223
|
+
if (cv < 0.08) {
|
|
224
|
+
return {
|
|
225
|
+
checkId: 'robotic_typing',
|
|
226
|
+
passed: false,
|
|
227
|
+
score: 1.0,
|
|
228
|
+
details: 'Keystroke timing is machine-like uniform',
|
|
229
|
+
data: { cv, mean, stddev },
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (cv < 0.15) {
|
|
234
|
+
return {
|
|
235
|
+
checkId: 'robotic_typing',
|
|
236
|
+
passed: true,
|
|
237
|
+
score: 0.5,
|
|
238
|
+
details: 'Lower than typical keystroke variation',
|
|
239
|
+
data: { cv },
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
checkId: 'robotic_typing',
|
|
245
|
+
passed: true,
|
|
246
|
+
score: 0,
|
|
247
|
+
data: { cv },
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Check: Mouse Teleporting
|
|
253
|
+
*
|
|
254
|
+
* Detects large instant mouse jumps that are physically impossible.
|
|
255
|
+
*/
|
|
256
|
+
export function checkMouseTeleporting(metrics?: BehavioralMetrics): CheckResult {
|
|
257
|
+
if (!metrics?.mouseMovements || metrics.mouseMovements.length < 5) {
|
|
258
|
+
return {
|
|
259
|
+
checkId: 'mouse_teleporting',
|
|
260
|
+
passed: true,
|
|
261
|
+
score: 0,
|
|
262
|
+
details: 'Insufficient mouse data',
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let teleportCount = 0;
|
|
267
|
+
const movements = metrics.mouseMovements;
|
|
268
|
+
|
|
269
|
+
for (let i = 1; i < movements.length; i++) {
|
|
270
|
+
const prev = movements[i - 1];
|
|
271
|
+
const curr = movements[i];
|
|
272
|
+
const dx = curr.x - prev.x;
|
|
273
|
+
const dy = curr.y - prev.y;
|
|
274
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
275
|
+
const dt = curr.t - prev.t;
|
|
276
|
+
|
|
277
|
+
// If moved > 500px in < 10ms, it's a teleport
|
|
278
|
+
if (distance > 500 && dt < 10) {
|
|
279
|
+
teleportCount++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const teleportRatio = teleportCount / movements.length;
|
|
284
|
+
|
|
285
|
+
if (teleportRatio > 0.2) {
|
|
286
|
+
return {
|
|
287
|
+
checkId: 'mouse_teleporting',
|
|
288
|
+
passed: false,
|
|
289
|
+
score: 0.9,
|
|
290
|
+
details: 'Frequent mouse teleportation detected',
|
|
291
|
+
data: { teleportCount, teleportRatio },
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (teleportRatio > 0.05) {
|
|
296
|
+
return {
|
|
297
|
+
checkId: 'mouse_teleporting',
|
|
298
|
+
passed: true,
|
|
299
|
+
score: 0.4,
|
|
300
|
+
details: 'Some mouse teleportation detected',
|
|
301
|
+
data: { teleportCount },
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
checkId: 'mouse_teleporting',
|
|
307
|
+
passed: true,
|
|
308
|
+
score: 0,
|
|
309
|
+
data: { teleportCount },
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Check: No Corrections
|
|
315
|
+
*
|
|
316
|
+
* Detects perfect typing with no backspaces or corrections.
|
|
317
|
+
* Humans naturally make typos and correct them.
|
|
318
|
+
*/
|
|
319
|
+
export function checkNoCorrections(metrics?: BehavioralMetrics): CheckResult {
|
|
320
|
+
if (!metrics || metrics.keypressCount < 20) {
|
|
321
|
+
return {
|
|
322
|
+
checkId: 'no_corrections',
|
|
323
|
+
passed: true,
|
|
324
|
+
score: 0,
|
|
325
|
+
details: 'Insufficient typing data',
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const { backspaceCount, keypressCount } = metrics;
|
|
330
|
+
const correctionRatio = backspaceCount / keypressCount;
|
|
331
|
+
|
|
332
|
+
// Typical humans have ~5-15% backspace rate
|
|
333
|
+
if (backspaceCount === 0 && keypressCount > 50) {
|
|
334
|
+
return {
|
|
335
|
+
checkId: 'no_corrections',
|
|
336
|
+
passed: false,
|
|
337
|
+
score: 0.8,
|
|
338
|
+
details: 'No typing corrections despite significant text entry',
|
|
339
|
+
data: { backspaceCount, keypressCount },
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (correctionRatio < 0.01 && keypressCount > 30) {
|
|
344
|
+
return {
|
|
345
|
+
checkId: 'no_corrections',
|
|
346
|
+
passed: true,
|
|
347
|
+
score: 0.5,
|
|
348
|
+
details: 'Very few typing corrections',
|
|
349
|
+
data: { correctionRatio },
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
checkId: 'no_corrections',
|
|
355
|
+
passed: true,
|
|
356
|
+
score: 0,
|
|
357
|
+
data: { correctionRatio },
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Check: Hover Behavior
|
|
363
|
+
*
|
|
364
|
+
* Analyzes mouse hover patterns before clicks.
|
|
365
|
+
* Humans typically hover before clicking; bots often don't.
|
|
366
|
+
*/
|
|
367
|
+
export function checkHoverBehavior(metrics?: BehavioralMetrics): CheckResult {
|
|
368
|
+
if (!metrics?.mouseClicks || metrics.mouseClicks.length < 3) {
|
|
369
|
+
return {
|
|
370
|
+
checkId: 'hover_behavior',
|
|
371
|
+
passed: true,
|
|
372
|
+
score: 0,
|
|
373
|
+
details: 'Insufficient click data',
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const clicksWithHover = metrics.mouseClicks.filter(c => c.hadHover);
|
|
378
|
+
const hoverRatio = clicksWithHover.length / metrics.mouseClicks.length;
|
|
379
|
+
|
|
380
|
+
// Humans typically hover before ~70%+ of clicks
|
|
381
|
+
if (hoverRatio < 0.2) {
|
|
382
|
+
return {
|
|
383
|
+
checkId: 'hover_behavior',
|
|
384
|
+
passed: false,
|
|
385
|
+
score: 0.8,
|
|
386
|
+
details: 'Clicks without natural hover behavior',
|
|
387
|
+
data: { hoverRatio, totalClicks: metrics.mouseClicks.length },
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (hoverRatio < 0.4) {
|
|
392
|
+
return {
|
|
393
|
+
checkId: 'hover_behavior',
|
|
394
|
+
passed: true,
|
|
395
|
+
score: 0.4,
|
|
396
|
+
details: 'Lower than typical hover-before-click rate',
|
|
397
|
+
data: { hoverRatio },
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
checkId: 'hover_behavior',
|
|
403
|
+
passed: true,
|
|
404
|
+
score: 0,
|
|
405
|
+
data: { hoverRatio },
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Check: Scroll Patterns
|
|
411
|
+
*
|
|
412
|
+
* Analyzes scrolling behavior for signs of automation.
|
|
413
|
+
*/
|
|
414
|
+
export function checkScrollPatterns(metrics?: BehavioralMetrics): CheckResult {
|
|
415
|
+
if (!metrics?.scrollEvents || metrics.scrollEvents.length < 5) {
|
|
416
|
+
return {
|
|
417
|
+
checkId: 'scroll_patterns',
|
|
418
|
+
passed: true,
|
|
419
|
+
score: 0,
|
|
420
|
+
details: 'Insufficient scroll data',
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const velocities = metrics.scrollEvents.map(s => Math.abs(s.velocity)).filter(v => v > 0);
|
|
425
|
+
|
|
426
|
+
if (velocities.length < 3) {
|
|
427
|
+
return {
|
|
428
|
+
checkId: 'scroll_patterns',
|
|
429
|
+
passed: true,
|
|
430
|
+
score: 0,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Check for uniform scroll velocity (robotic)
|
|
435
|
+
const mean = velocities.reduce((a, b) => a + b, 0) / velocities.length;
|
|
436
|
+
const variance = velocities.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / velocities.length;
|
|
437
|
+
const cv = Math.sqrt(variance) / mean;
|
|
438
|
+
|
|
439
|
+
if (cv < 0.1) {
|
|
440
|
+
return {
|
|
441
|
+
checkId: 'scroll_patterns',
|
|
442
|
+
passed: false,
|
|
443
|
+
score: 0.7,
|
|
444
|
+
details: 'Unnaturally uniform scroll pattern',
|
|
445
|
+
data: { cv },
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
checkId: 'scroll_patterns',
|
|
451
|
+
passed: true,
|
|
452
|
+
score: 0,
|
|
453
|
+
data: { cv },
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Check: Mouse Acceleration
|
|
459
|
+
*
|
|
460
|
+
* Analyzes natural mouse acceleration patterns.
|
|
461
|
+
* Real mice have gradual acceleration/deceleration.
|
|
462
|
+
*/
|
|
463
|
+
export function checkMouseAcceleration(metrics?: BehavioralMetrics): CheckResult {
|
|
464
|
+
if (!metrics?.mouseMovements || metrics.mouseMovements.length < 20) {
|
|
465
|
+
return {
|
|
466
|
+
checkId: 'mouse_acceleration',
|
|
467
|
+
passed: true,
|
|
468
|
+
score: 0,
|
|
469
|
+
details: 'Insufficient mouse data',
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const velocities = metrics.mouseMovements.map(m => m.velocity).filter(v => v > 0);
|
|
474
|
+
|
|
475
|
+
if (velocities.length < 10) {
|
|
476
|
+
return {
|
|
477
|
+
checkId: 'mouse_acceleration',
|
|
478
|
+
passed: true,
|
|
479
|
+
score: 0,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Calculate acceleration (change in velocity)
|
|
484
|
+
const accelerations: number[] = [];
|
|
485
|
+
for (let i = 1; i < velocities.length; i++) {
|
|
486
|
+
accelerations.push(Math.abs(velocities[i] - velocities[i - 1]));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Check if acceleration is too uniform
|
|
490
|
+
const meanAcc = accelerations.reduce((a, b) => a + b, 0) / accelerations.length;
|
|
491
|
+
const varianceAcc = accelerations.reduce((sum, a) => sum + Math.pow(a - meanAcc, 2), 0) / accelerations.length;
|
|
492
|
+
const cvAcc = Math.sqrt(varianceAcc) / meanAcc;
|
|
493
|
+
|
|
494
|
+
if (cvAcc < 0.2) {
|
|
495
|
+
return {
|
|
496
|
+
checkId: 'mouse_acceleration',
|
|
497
|
+
passed: true,
|
|
498
|
+
score: 0.5,
|
|
499
|
+
details: 'Lower than typical acceleration variation',
|
|
500
|
+
data: { cvAcc },
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
checkId: 'mouse_acceleration',
|
|
506
|
+
passed: true,
|
|
507
|
+
score: 0,
|
|
508
|
+
data: { cvAcc },
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Run all behavioral checks
|
|
514
|
+
*/
|
|
515
|
+
export function runBehavioralChecks(metrics?: BehavioralMetrics): CheckResult[] {
|
|
516
|
+
return [
|
|
517
|
+
checkLowInteraction(metrics),
|
|
518
|
+
checkExcessivePaste(metrics),
|
|
519
|
+
checkPointerSpikes(metrics),
|
|
520
|
+
checkRoboticTyping(metrics),
|
|
521
|
+
checkMouseTeleporting(metrics),
|
|
522
|
+
checkNoCorrections(metrics),
|
|
523
|
+
checkHoverBehavior(metrics),
|
|
524
|
+
checkScrollPatterns(metrics),
|
|
525
|
+
checkMouseAcceleration(metrics),
|
|
526
|
+
];
|
|
527
|
+
}
|