@anonympins/fingerprint 0.4.6 → 0.5.1
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/CHANGELOG.md +43 -0
- package/README.md +56 -11
- package/package.json +2 -1
- package/src/js/fingerprint.client.js +360 -23
- package/src/js/fingerprint.js +508 -200
- package/src/js/fingerprint.utils.js +184 -0
- package/src/js/gpu_pow.solver.js +217 -0
- package/src/js/library.js +1 -1
- package/src/js/pow.solver.inline.js +12 -4
- package/src/js/pow.solver.js +12 -4
- package/src/js/tests/fingerprint.client.init.test.js +5 -2
- package/src/js/tests/fingerprint.isMalicious.test.js +234 -116
- package/src/js/tests/fingerprint.test.js +195 -1
- package/src/js/tests/gpu_pow.test.js +118 -0
- package/src/js/tests/ja3AnomalyDetector.test.js +1 -0
- package/src/js/tests/quicFingerprint.test.js +35 -0
- package/src/php/AutoTuner.php +71 -0
- package/src/php/Challenge/ChallengeUtils.php +344 -9
- package/src/php/Config/SecurityProfiles.php +9 -0
- package/src/php/FingerprintEngine.php +61 -2
- package/src/php/RequestContext.php +2 -0
- package/src/php/Tests/ChallengeUtilsTest.php +285 -81
- package/src/php/Tests/MaliciousPatternsTest.php +104 -0
- package/src/php/Tests/QuicFingerprintTest.php +54 -0
- package/src/php/Tests/RequestUtilsTest.php +43 -0
- package/src/php/Utils/BigInt.php +202 -144
- package/src/php/Utils/MaliciousPatterns.php +74 -58
- package/src/php/Utils/RequestUtils.php +120 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
## Version 0.5.1
|
|
2
|
+
|
|
3
|
+
### ✨ New Features
|
|
4
|
+
|
|
5
|
+
- **GPU Proof-of-Work (PoW) Challenge**: Introduced a highly parallelized chaotic logistic map float computation challenge utilizing WebGPU (with a fallback to WebGL2). This challenge is specifically designed to exhaust CPU-based headless emulators (such as SwiftShader). It includes sample-based server-side verification to prevent DoS vectors.
|
|
6
|
+
- **Biometric Keystroke Dynamics (Dwell & Flight Times)**: Introduced advanced behavioral biometric tracking by measuring key press duration (*dwell time*) and key-to-key transition intervals (*flight time*) to build a unique digraph/trigraph motor profile for the user.
|
|
7
|
+
- *Why it's a Killer Feature*: Automated text-injecting bots often simulate simple randomized delays between characters, but they fail to replicate natural human muscle memory patterns (such as ultra-fast cognitive transitions between adjacent keys on physical or virtual layouts). Server-side statistical checks (utilizing standard deviation, variance thresholds, and Benford's Law) immediately flag these robotic, uniform input patterns.
|
|
8
|
+
- **Stealthy Honeypot Traps (Shadow DOM)**: Implemented a new honeypot mechanism that conceals trap links and form fields within a closed Shadow DOM, with dynamic rendering styles calculated by nested CSS variables. This makes them invisible to legitimate users and standard browser automation tools, but highly detectable by bots that inject specific JS or use complex selectors, significantly increasing their behavioral signature.
|
|
9
|
+
- **Hot-Reloadable Security Configuration**: Enabled dynamic, in-memory updates of security configurations (weights, thresholds, patterns) without requiring a server restart. This ensures continuous adaptability to evolving threats (e.g., DDoS L7, scraping campaigns) without service interruption or latency.
|
|
10
|
+
|
|
11
|
+
### 🚀 Improvements
|
|
12
|
+
|
|
13
|
+
- **Display & Protocol Anomaly Scoring**: Fully integrated `renderingAnomalyScore` and `quicAnomalyScore` across all backend engines (Node.js, PHP, Python). This enables real-time detection of virtual software framebuffers (like `Xvfb`) lacking physical V-Sync through jitter analysis, as well as HTTP/3 stream setting inconsistencies.
|
|
14
|
+
- **Security Profile Tuning**: Integrated display and QUIC anomaly detectors into the default security profiles (`balanced`, `strict`, `blog`, `ecommerce`) with custom weights.
|
|
15
|
+
- **Layout-Agnostic Client Tracking**: Enhanced the keystroke dynamics tracker to prioritize physical key locations (`KeyboardEvent.code`) over localized characters (`KeyboardEvent.key`), ensuring robust detection across different keyboard layouts (QWERTY, AZERTY) and virtual mobile keyboards.
|
|
16
|
+
|
|
17
|
+
### 🐛 Bug Fixes
|
|
18
|
+
|
|
19
|
+
- **Node.js 24 Test Suite Compatibility**: Fixed unit test suite execution and environment configuration issues specifically encountered on Node.js 24.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Version 0.5.0
|
|
24
|
+
|
|
25
|
+
### ✨ New Features
|
|
26
|
+
|
|
27
|
+
- **Zero-Knowledge Proofs (ZKP)**: Added cryptographically secure Schnorr Zero-Knowledge Proofs (`generateZkpProof` on client and `verifyZkpProof` on server) for device fingerprints, allowing zero-disclosure fingerprint validation and making session tickets completely tamper-proof.
|
|
28
|
+
- **Optional Ed25519 Asymmetric Keys**: Implemented dynamic and optional Ed25519 asymmetric key binding with an automatic fallback to symmetric AES-256-CBC encryption.
|
|
29
|
+
- **Cooperative Proof-of-Space (Coop PoSpace)**: Introduced decentralized, cooperative Proof-of-Space challenge routing within local subnets. Highly suspicious clients must coordinate with neighboring subnet peers to fetch and aggregate cryptographic blocks. This vastly increases the cost and complexity for distributed botnets trying to cycle residential proxy IPs, as they are forced to run and maintain real, synchronized, and cooperative peer nodes within the same local IP subnet to solve challenges.
|
|
30
|
+
|
|
31
|
+
### 🚀 Improvements
|
|
32
|
+
|
|
33
|
+
- **Enhanced Malicious Injection Detection**: Upgraded the WAF and input validation subsystem to recursively inspect deeply nested NoSQL/SQL structures, significantly improving protection against complex MongoDb/SQL injection vectors.
|
|
34
|
+
- **Traffic Data Pruning**: Introduced automated traffic data pruning (`pruneTrafficData`) with time-based and size-based limiters to avoid memory leaks during long-running auto-tuning sessions.
|
|
35
|
+
|
|
36
|
+
### 🐛 Bug Fixes
|
|
37
|
+
|
|
38
|
+
- **CI/CD OpenSSL Compatibility**: Fixed test environment crashes on older systems or CI/CD pipelines where the `OPENSSL_KEYTYPE_ED25519` constant is undefined.
|
|
39
|
+
- **Autoloader savePath Resolution**: Resolved file-system path resolution bugs when saving optimized configurations inside the auto-tuner.
|
|
40
|
+
- **Repository Size Optimization**: Pruned obsolete resources and optimized package assets to significantly reduce overall repository footprint.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
1
44
|
## Version 0.4.6
|
|
2
45
|
|
|
3
46
|
### ✨ New Features
|
package/README.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
# Fingerprint anti-bot protection
|
|
2
2
|
|
|
3
|
-
NodeJS tests : [](https://github.com/anonympins/fingerprint/actions/workflows/ci-nodejs.yml) / PHP tests : [](https://github.com/anonympins/fingerprint/actions/workflows/ci-php.yml) / Python tests : [](https://github.com/anonympins/fingerprint/actions/workflows/ci-python.yml)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/anonympins/fingerprint/releases)
|
|
6
|
+
[](https://github.com/anonympins/fingerprint/blob/main/LICENSE)
|
|
7
|
+
[](https://github.com/anonympins/fingerprint/commits/main)
|
|
8
|
+
[](https://github.com/anonympins/fingerprint)
|
|
8
9
|
|
|
9
|
-
A multi-layered behavioral, cryptographic, and network analysis engine designed to identify and mitigate malicious requests (bots, scrapers, session hijacking, bot farms) in real-time. Supports **Node.js**, **Python** and **PHP** environments.
|
|
10
|
+
A multi-layered behavioral, cryptographic, and network analysis production-grade engine designed to identify and mitigate malicious requests (bots, scrapers, session hijacking, bot farms) in real-time. Supports **Node.js**, **Python** and **PHP** environments.
|
|
11
|
+
|
|
12
|
+
It leverages multi-layer hardware fingerprinting, real-time behavioral analysis, passive network/TLS tracking, and adaptive/useful proof-of-work challenges to dynamically detect and mitigate scraping, scalping, account takeover (ATO), and sophisticated automated threats.
|
|
13
|
+
|
|
14
|
+
Supported officially on **Node.js (>=20.0.0)**, **PHP (>=8.0)**, and **Python (>=3.8)**.
|
|
10
15
|
|
|
11
16
|

|
|
12
17
|
|
|
@@ -14,11 +19,42 @@ A multi-layered behavioral, cryptographic, and network analysis engine designed
|
|
|
14
19
|
|
|
15
20
|
[](https://www.youtube.com/watch?v=Ujeznl0JAl4)
|
|
16
21
|
|
|
17
|
-
## Key Features
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
## 🚀 Key Features
|
|
24
|
+
|
|
25
|
+
### 1. 🧬 Polymorphic Client-Side WASM & JS Solvers
|
|
26
|
+
* **Polymorphic WebAssembly Solver**: Dynamically generates unique, randomized C++ compiled WebAssembly binary modules per session. Prevents static analysis, bot automation, and emulator tampering.
|
|
27
|
+
* **IndexedDB WASM Caching**: Transparently caches compiled WASM modules (`wasm-cache-db`) in the browser's IndexedDB, minimizing initialization overhead and execution lag on subsequent visits.
|
|
28
|
+
* **Advanced Obfuscation**: Uses multi-layered control flow flattening and string array obfuscation for client-side libraries.
|
|
29
|
+
|
|
30
|
+
### 2. 💱 Useful Proof-of-Work (uPoW) & PoSpace
|
|
31
|
+
* **Collaborative Useful PoW**: Instead of burning CPU cycles on arbitrary mathematical hash puzzles, suspicious clients solve complex optimization problems (e.g., *Traveling Salesperson*, *Portfolio Allocation*, *Facility Location*, *Fraud Detection Parameter Tuning*).
|
|
32
|
+
* **Proof-of-Space (PoSpace) Challenge**: Forces browser clients to allocate and verify access to massive, persistent storage chunks (e.g., 100MB) inside IndexedDB, multiplying the cost of multi-threaded headless automation.
|
|
33
|
+
* **Chained CPU/Memory Challenges**: Employs client-side resource exhaustion techniques (Chained SHA-256 target seeking & Memory Hard allocation vectors up to 128MB) that are validated in $O(1)$ on the server.
|
|
34
|
+
|
|
35
|
+
### 3. 🌐 Passive TLS, HTTP/2, and TCP/IP (p0f) Tracking
|
|
36
|
+
* **Native JA3/JA4 TLS Handshake Parsing**: Inspects raw TLS client hello bytes to extract and analyze cipher suite arrangements, extensions, and elliptic curve formats.
|
|
37
|
+
* **Passive TCP/IP Stack Fingerprinting**: Emulates `p0f` rules by analyzing raw TCP SYN packets (TTL, Window Size, MSS, WS, SACK) to classify client OS and detect raw network spoofing.
|
|
38
|
+
* **Multi-Language Handshake Parser**: Built-in support for event-driven PHP runtimes (Swoole, ReactPHP, Workerman), Node.js native sockets, and Python ASGI/WSGI contexts.
|
|
39
|
+
|
|
40
|
+
### 4. 🧠 Stateful Behavioral Entropy & Click Variance
|
|
41
|
+
* **Click Coordinate Variance**: Tracks exact click relative positions on DOM elements to compute spatial entropy, flagging bots clicking targets with robotic, mathematically perfect precision (zero variance).
|
|
42
|
+
* **Mobile Touch Move Dynamics**: Captures mobile-specific touchscreen signals, analyzing tactile contact area radius, variable pressure indices, and multi-touch capabilities.
|
|
43
|
+
* **Typing Keystroke Latency**: Measures real-time keystroke interval latencies to prevent automated text insertion.
|
|
44
|
+
|
|
45
|
+
### 5. 🔍 Cross-Layer & Analog Inconsistency Scoring
|
|
46
|
+
* **Layer Cross-Referencing**: Analyzes inconsistencies between User-Agent declarations, Client-Hints (`Sec-CH-UA`), TLS Handshake capabilities, and TCP stacks (e.g., claiming Windows NT on Chrome but negotiating TLS like curl/Safari on a Linux kernel).
|
|
47
|
+
* **Viewport Aspect ratio & Screen mismatches**: Detects virtualized viewports exceeding physical dimensions or fake hardware specifications.
|
|
48
|
+
|
|
49
|
+
### 6. 🦠 Honeypot Traps & Extensible WAF
|
|
50
|
+
* **Signed Trap URLs**: Injects visually hidden, signed trap URLs into the DOM. Attempts to crawl, probe, or scrape these URLs immediately condemn the device.
|
|
51
|
+
* **Recursive Injection Filters**: Inspects deeply nested payload structures (JSON/NoSQL/GraphQL) using a robust regular expression matrix to flag SQLi, XSS, XXE, SSTI, and JNDI (Log4Shell) vulnerabilities.
|
|
52
|
+
* **ModSecurity NodeJS Extensibility**: Allows plugging in native core rule sets or custom WAF rule compilers into the honeypot pipeline.
|
|
53
|
+
|
|
54
|
+
### 🧬 Progressive Threshold Auto-Tuning
|
|
55
|
+
* **Genetic Policy Optimizer**: Dynamically updates classification parameters using a multi-objective genetic algorithm on your actual sanitized traffic data.
|
|
56
|
+
* **Inertial Parameter Sliding**: Adjusts security thresholds slowly with an adaptive learning rate to prevent configuration spikes.
|
|
57
|
+
* **Sybil Protection**: Filters out traffic logs, ensuring individual compromised bot networks cannot pollute optimization datasets.
|
|
22
58
|
|
|
23
59
|
## Quick Start
|
|
24
60
|
|
|
@@ -62,9 +98,18 @@ We welcome community contributions! Please read our **[Contributing Guidelines](
|
|
|
62
98
|
Thanks to our contributors :
|
|
63
99
|
- [anonympins](https://github.com/anonympins)
|
|
64
100
|
|
|
65
|
-
##
|
|
101
|
+
## 💖 Sponsor This Project
|
|
102
|
+
|
|
103
|
+
If this security suite helps protect your business against botnets, automated scraping, credential stuffing, or Layer 7 DDoS attacks, please consider supporting its active development!
|
|
104
|
+
|
|
105
|
+
Sponsorship helps maintain the library, fund active updates, and keep the dynamic WebAssembly engine cutting-edge.
|
|
106
|
+
|
|
107
|
+
### 🌟 Featured Sponsors
|
|
108
|
+
|
|
109
|
+
<img src="https://s6.imgcdn.dev/YJTWv9.png" width="100" alt="YJTWv9.png" border="0" valign="middle">
|
|
110
|
+
|
|
111
|
+
[https://primals.net](https://primals.net) and sub-sites
|
|
66
112
|
|
|
67
|
-
- https://primals.net and sub-sites
|
|
68
113
|
|
|
69
114
|
## License
|
|
70
115
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anonympins/fingerprint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Advanced anti-bot library for Node.js using multi-layer fingerprinting (JA3, client-side, headers), behavioral analysis, and adaptive Proof-of-Work (PoW) challenges to mitigate scraping, scalping, and automated threats.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "vitest run --reporter=verbose",
|
|
12
|
+
"pub": "npm run build && npm login && npm publish --access public",
|
|
12
13
|
"build": "node src/js/build-client.js"
|
|
13
14
|
},
|
|
14
15
|
"exports": {
|
|
@@ -82,6 +82,56 @@ const ClientLibrary = {
|
|
|
82
82
|
*/
|
|
83
83
|
_hasher: (str, seed) => activeCyrb53(str, seed),
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Génère une preuve de connaissance à divulgation nulle (ZKP) de Schnorr pour l'empreinte de l'appareil.
|
|
87
|
+
* Rend le tout stable et compatible avec les vérifications JS, PHP et Python.
|
|
88
|
+
* @param {string} fingerprint - L'empreinte de l'appareil.
|
|
89
|
+
* @returns {Promise<string>} La preuve sous format "y:t:s" en hexadécimal.
|
|
90
|
+
*/
|
|
91
|
+
async generateZkpProof(fingerprint) {
|
|
92
|
+
const ZKP_P = 115792089237316195423570985008687907853269984665640564039457584007908834671663n;
|
|
93
|
+
const ZKP_G = 2n;
|
|
94
|
+
const cryptoObj = window.crypto || window.msCrypto;
|
|
95
|
+
|
|
96
|
+
const sha256Hex = async (str) => {
|
|
97
|
+
const encoder = new TextEncoder();
|
|
98
|
+
const data = encoder.encode(str);
|
|
99
|
+
const hashBuffer = await cryptoObj.subtle.digest('SHA-256', data);
|
|
100
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
101
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const modPow = (base, exponent, modulus) => {
|
|
105
|
+
if (modulus === 1n) return 0n;
|
|
106
|
+
let result = 1n;
|
|
107
|
+
base = base % modulus;
|
|
108
|
+
while (exponent > 0n) {
|
|
109
|
+
if (exponent % 2n === 1n) {
|
|
110
|
+
result = (result * base) % modulus;
|
|
111
|
+
}
|
|
112
|
+
exponent = exponent >> 1n;
|
|
113
|
+
base = (base * base) % modulus;
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const xHex = await sha256Hex(fingerprint);
|
|
119
|
+
const x = BigInt('0x' + xHex) % ZKP_P;
|
|
120
|
+
const y = modPow(ZKP_G, x, ZKP_P);
|
|
121
|
+
const randomBytes = new Uint8Array(32);
|
|
122
|
+
cryptoObj.getRandomValues(randomBytes);
|
|
123
|
+
let vHex = Array.from(randomBytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
124
|
+
let v = BigInt('0x' + vHex) % (ZKP_P - 1n);
|
|
125
|
+
if (v === 0n) v = 1n;
|
|
126
|
+
const t = modPow(ZKP_G, v, ZKP_P);
|
|
127
|
+
const cStr = ZKP_G.toString() + y.toString() + t.toString();
|
|
128
|
+
const cHex = await sha256Hex(cStr);
|
|
129
|
+
const c = BigInt('0x' + cHex) % ZKP_P;
|
|
130
|
+
const s = (v + c * x) % (ZKP_P - 1n);
|
|
131
|
+
|
|
132
|
+
return `${y.toString(16)}:${t.toString(16)}:${s.toString(16)}`;
|
|
133
|
+
},
|
|
134
|
+
|
|
85
135
|
/**
|
|
86
136
|
* Génère l'empreinte de l'appareil actuel.
|
|
87
137
|
*/
|
|
@@ -290,6 +340,137 @@ const ClientLibrary = {
|
|
|
290
340
|
document.addEventListener('touchend', handleTouch, { passive: true });
|
|
291
341
|
},
|
|
292
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Démarre le suivi de la régularité d'affichage (V-Sync/rAF) pour détecter les framebuffers logiciels sans V-Sync.
|
|
345
|
+
*/
|
|
346
|
+
startRenderingTracker() {
|
|
347
|
+
if (this._renderingTrackerAttached) return;
|
|
348
|
+
this._renderingTrackerAttached = true;
|
|
349
|
+
|
|
350
|
+
if (typeof window === 'undefined' || !window.requestAnimationFrame) return;
|
|
351
|
+
|
|
352
|
+
const rAfTimestamps = [];
|
|
353
|
+
let lastTime = performance.now();
|
|
354
|
+
const maxSamples = 15;
|
|
355
|
+
|
|
356
|
+
const checkOffscreenAnom = () => {
|
|
357
|
+
try {
|
|
358
|
+
if ('OffscreenCanvas' in window && HTMLCanvasElement.prototype.transferControlToOffscreen) {
|
|
359
|
+
const nativeToString = Function.prototype.toString.call(HTMLCanvasElement.prototype.transferControlToOffscreen);
|
|
360
|
+
return !nativeToString.includes('[native code]');
|
|
361
|
+
}
|
|
362
|
+
} catch (e) {}
|
|
363
|
+
return false;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const loop = (time) => {
|
|
367
|
+
const delta = time - lastTime;
|
|
368
|
+
lastTime = time;
|
|
369
|
+
if (rAfTimestamps.length < maxSamples) {
|
|
370
|
+
if (rAfTimestamps.length > 0) { // Skip first delta
|
|
371
|
+
rAfTimestamps.push(delta);
|
|
372
|
+
}
|
|
373
|
+
window.requestAnimationFrame(loop);
|
|
374
|
+
} else {
|
|
375
|
+
const avg = rAfTimestamps.reduce((a, b) => a + b, 0) / rAfTimestamps.length;
|
|
376
|
+
const sqDiffs = rAfTimestamps.map(v => Math.pow(v - avg, 2));
|
|
377
|
+
const avgSqDiff = sqDiffs.reduce((a, b) => a + b, 0) / sqDiffs.length;
|
|
378
|
+
|
|
379
|
+
metrics.rendering = {
|
|
380
|
+
fps: Math.round((1000 / avg) * 100) / 100,
|
|
381
|
+
jitter: Math.round(Math.sqrt(avgSqDiff) * 100) / 100,
|
|
382
|
+
offscreenAnom: checkOffscreenAnom()
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
window.requestAnimationFrame(loop);
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Initialise l'espace Proof-of-Space persistant dans l'IndexedDB locale.
|
|
391
|
+
*/
|
|
392
|
+
async initializeSpace(seed, sizeMb) {
|
|
393
|
+
return new Promise((resolve, reject) => {
|
|
394
|
+
const request = indexedDB.open('pospace-db', 1);
|
|
395
|
+
request.onupgradeneeded = (e) => {
|
|
396
|
+
const db = e.target.result;
|
|
397
|
+
if (!db.objectStoreNames.contains('blocks')) {
|
|
398
|
+
db.createObjectStore('blocks');
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
request.onsuccess = async (e) => {
|
|
402
|
+
const db = e.target.result;
|
|
403
|
+
const tx = db.transaction('blocks', 'readwrite');
|
|
404
|
+
const store = tx.objectStore('blocks');
|
|
405
|
+
|
|
406
|
+
const maxBlocks = sizeMb * 1024;
|
|
407
|
+
const countReq = store.count();
|
|
408
|
+
countReq.onsuccess = async () => {
|
|
409
|
+
if (countReq.result < maxBlocks) {
|
|
410
|
+
for (let i = 0; i < maxBlocks; i++) {
|
|
411
|
+
const block = new Uint8Array(1024);
|
|
412
|
+
let h = 5381;
|
|
413
|
+
for (let j = 0; j < seed.length; j++) {
|
|
414
|
+
h = (h << 5) + h + seed.charCodeAt(j);
|
|
415
|
+
}
|
|
416
|
+
h = (h << 5) + h + i;
|
|
417
|
+
for (let k = 0; k < 1024; k++) {
|
|
418
|
+
h = Math.imul(h ^ k, 1597334677);
|
|
419
|
+
block[k] = h & 0xff;
|
|
420
|
+
}
|
|
421
|
+
store.put(block, i);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
resolve();
|
|
425
|
+
};
|
|
426
|
+
};
|
|
427
|
+
request.onerror = () => reject(new Error("Failed to open pospace database"));
|
|
428
|
+
});
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Lit un bloc spécifique de l'IndexedDB locale sous format hexadécimal.
|
|
433
|
+
*/
|
|
434
|
+
async readSpaceBlock(blockIdx) {
|
|
435
|
+
return new Promise((resolve, reject) => {
|
|
436
|
+
const request = indexedDB.open('pospace-db', 1);
|
|
437
|
+
request.onsuccess = (e) => {
|
|
438
|
+
const db = e.target.result;
|
|
439
|
+
const tx = db.transaction('blocks', 'readonly');
|
|
440
|
+
const store = tx.objectStore('blocks');
|
|
441
|
+
const getReq = store.get(blockIdx);
|
|
442
|
+
getReq.onsuccess = () => {
|
|
443
|
+
const block = getReq.result;
|
|
444
|
+
if (block) {
|
|
445
|
+
const hex = Array.from(block).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
446
|
+
resolve(hex);
|
|
447
|
+
} else {
|
|
448
|
+
reject(new Error("Block not found"));
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
getReq.onerror = () => reject(getReq.error);
|
|
452
|
+
};
|
|
453
|
+
request.onerror = () => reject(new Error("Failed to open pospace database"));
|
|
454
|
+
});
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Résout le Proof-of-Space en combinant optionnellement le bloc du pair.
|
|
459
|
+
*/
|
|
460
|
+
async solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock = '') {
|
|
461
|
+
const blocks = [];
|
|
462
|
+
for (const idx of queries) {
|
|
463
|
+
const blockHex = await this.readSpaceBlock(idx);
|
|
464
|
+
blocks.push(blockHex);
|
|
465
|
+
}
|
|
466
|
+
let finalPayload = blocks.join('') + peerBlock + nonce + ":" + clientSecret;
|
|
467
|
+
const encoder = new TextEncoder();
|
|
468
|
+
const data = encoder.encode(finalPayload);
|
|
469
|
+
const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);
|
|
470
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
471
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
472
|
+
},
|
|
473
|
+
|
|
293
474
|
/**
|
|
294
475
|
* Démarre le suivi des mouvements de la souris pour calculer l'entropie.
|
|
295
476
|
* À appeler une fois sur la page.
|
|
@@ -314,27 +495,77 @@ const ClientLibrary = {
|
|
|
314
495
|
},
|
|
315
496
|
|
|
316
497
|
/**
|
|
317
|
-
* Démarre le suivi de la dynamique de frappe pour calculer
|
|
498
|
+
* Démarre le suivi de la dynamique de frappe pour calculer le dwell time et le flight time (digraphie/trigraphie).
|
|
318
499
|
* À appeler une fois sur la page.
|
|
319
500
|
*/
|
|
320
501
|
startKeystrokeDynamicsTracker() {
|
|
321
502
|
// S'assurer de ne pas attacher l'écouteur plusieurs fois
|
|
322
|
-
if (
|
|
503
|
+
if (this._keystrokeTrackerAttached) return;
|
|
504
|
+
this._keystrokeTrackerAttached = true;
|
|
323
505
|
|
|
324
|
-
|
|
506
|
+
const activeKeys = new Map();
|
|
507
|
+
let lastKeyDownTime = 0;
|
|
508
|
+
let lastKeyName = '';
|
|
509
|
+
|
|
510
|
+
document.addEventListener('keydown', (e) => {
|
|
325
511
|
const now = performance.now();
|
|
512
|
+
const key = e.key;
|
|
513
|
+
const code = e.code;
|
|
514
|
+
if (!key && !code) return;
|
|
515
|
+
|
|
516
|
+
const keyIdentifier = code || key;
|
|
517
|
+
|
|
518
|
+
// Prevent key repeat triggering multiple events
|
|
519
|
+
if (activeKeys.has(keyIdentifier)) return;
|
|
520
|
+
activeKeys.set(keyIdentifier, now);
|
|
521
|
+
|
|
326
522
|
if (keystrokeTimestamps.length > 0) {
|
|
327
523
|
const lastTimestamp = keystrokeTimestamps[keystrokeTimestamps.length - 1];
|
|
328
524
|
const latency = now - lastTimestamp;
|
|
329
|
-
|
|
330
|
-
if (latency > 10 && latency < 2000) { // Augmenté à 2s
|
|
525
|
+
if (latency > 10 && latency < 2000) {
|
|
331
526
|
if (keystrokeLatencies.length >= KEYSTROKE_HISTORY_MAX) {
|
|
332
|
-
keystrokeLatencies.shift();
|
|
527
|
+
keystrokeLatencies.shift();
|
|
333
528
|
}
|
|
334
529
|
keystrokeLatencies.push(latency);
|
|
335
530
|
}
|
|
336
531
|
}
|
|
337
532
|
keystrokeTimestamps.push(now);
|
|
533
|
+
|
|
534
|
+
// Flight Time (KeyDown to KeyDown)
|
|
535
|
+
if (lastKeyDownTime > 0) {
|
|
536
|
+
const flightTime = now - lastKeyDownTime;
|
|
537
|
+
if (flightTime > 10 && flightTime < 2000) {
|
|
538
|
+
if (keystrokeFlightTimes.length >= KEYSTROKE_HISTORY_MAX) {
|
|
539
|
+
keystrokeFlightTimes.shift();
|
|
540
|
+
}
|
|
541
|
+
const digraph = lastKeyName ? this._hasher(lastKeyName + "_" + keyIdentifier).toString() : "unknown";
|
|
542
|
+
keystrokeFlightTimes.push({ digraph, time: flightTime });
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
lastKeyDownTime = now;
|
|
546
|
+
lastKeyName = keyIdentifier;
|
|
547
|
+
}, {passive: true});
|
|
548
|
+
|
|
549
|
+
document.addEventListener('keyup', (e) => {
|
|
550
|
+
const now = performance.now();
|
|
551
|
+
const key = e.key;
|
|
552
|
+
const code = e.code;
|
|
553
|
+
if (!key && !code) return;
|
|
554
|
+
|
|
555
|
+
const keyIdentifier = code || key;
|
|
556
|
+
|
|
557
|
+
if (activeKeys.has(keyIdentifier)) {
|
|
558
|
+
const pressTime = activeKeys.get(keyIdentifier);
|
|
559
|
+
const dwellTime = now - pressTime;
|
|
560
|
+
activeKeys.delete(keyIdentifier);
|
|
561
|
+
|
|
562
|
+
if (dwellTime > 5 && dwellTime < 1000) {
|
|
563
|
+
if (keystrokeDwellTimes.length >= KEYSTROKE_HISTORY_MAX) {
|
|
564
|
+
keystrokeDwellTimes.shift();
|
|
565
|
+
}
|
|
566
|
+
keystrokeDwellTimes.push(dwellTime);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
338
569
|
}, {passive: true});
|
|
339
570
|
},
|
|
340
571
|
|
|
@@ -374,22 +605,75 @@ const ClientLibrary = {
|
|
|
374
605
|
});
|
|
375
606
|
activeHoneypotListeners.clear();
|
|
376
607
|
|
|
377
|
-
// 2. Ajouter les nouveaux écouteurs
|
|
608
|
+
// 2. Ajouter les nouveaux écouteurs sur le DOM classique
|
|
378
609
|
honeypotFieldNames.forEach(fieldName => {
|
|
379
610
|
const field = document.querySelector(`[name="${fieldName}"]`);
|
|
380
611
|
if (field) {
|
|
381
|
-
// On utilise une fonction nommée (ou une référence) pour pouvoir la supprimer plus tard.
|
|
382
|
-
// L'option { once: true } est excellente, mais pour une réinitialisation complète,
|
|
383
|
-
// il est plus propre de gérer le nettoyage nous-mêmes.
|
|
384
612
|
const listener = () => {
|
|
385
613
|
this.onHoneypotTrigger();
|
|
386
|
-
// Se supprime lui-même après exécution, comme { once: true }
|
|
387
614
|
field.removeEventListener('input', listener);
|
|
388
615
|
};
|
|
389
616
|
field.addEventListener('input', listener);
|
|
390
617
|
activeHoneypotListeners.set(field, listener); // On stocke la référence
|
|
391
618
|
}
|
|
392
619
|
});
|
|
620
|
+
|
|
621
|
+
// 3. Générer des champs d'input pièges masqués dans un Shadow DOM fermé
|
|
622
|
+
if (typeof document !== 'undefined' && honeypotFieldNames.length > 0) {
|
|
623
|
+
const host = document.createElement('div');
|
|
624
|
+
host.setAttribute('aria-hidden', 'true');
|
|
625
|
+
host.style.position = 'absolute';
|
|
626
|
+
host.style.width = '0';
|
|
627
|
+
host.style.height = '0';
|
|
628
|
+
host.style.overflow = 'hidden';
|
|
629
|
+
|
|
630
|
+
const shadow = host.attachShadow({ mode: 'closed' });
|
|
631
|
+
|
|
632
|
+
const style = document.createElement('style');
|
|
633
|
+
style.textContent = `
|
|
634
|
+
:host {
|
|
635
|
+
--trap-pos-state: absolute;
|
|
636
|
+
--trap-off-val: -9999px;
|
|
637
|
+
--trap-vis-state: hidden;
|
|
638
|
+
--trap-scale-val: 0;
|
|
639
|
+
}
|
|
640
|
+
.shadow-form-wrapper {
|
|
641
|
+
position: var(--trap-pos-state);
|
|
642
|
+
left: var(--trap-off-val);
|
|
643
|
+
top: var(--trap-off-val);
|
|
644
|
+
visibility: var(--trap-vis-state);
|
|
645
|
+
transform: scale(var(--trap-scale-val));
|
|
646
|
+
}
|
|
647
|
+
`;
|
|
648
|
+
shadow.appendChild(style);
|
|
649
|
+
|
|
650
|
+
const wrapper = document.createElement('div');
|
|
651
|
+
wrapper.className = 'shadow-form-wrapper';
|
|
652
|
+
|
|
653
|
+
honeypotFieldNames.forEach(fieldName => {
|
|
654
|
+
const label = document.createElement('label');
|
|
655
|
+
label.textContent = fieldName;
|
|
656
|
+
const input = document.createElement('input');
|
|
657
|
+
input.type = 'text';
|
|
658
|
+
input.name = fieldName;
|
|
659
|
+
input.tabIndex = -1;
|
|
660
|
+
input.autocomplete = 'off';
|
|
661
|
+
|
|
662
|
+
const trigger = () => {
|
|
663
|
+
this.onHoneypotTrigger();
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
input.addEventListener('input', trigger, { passive: true });
|
|
667
|
+
input.addEventListener('change', trigger, { passive: true });
|
|
668
|
+
input.addEventListener('focus', trigger, { passive: true });
|
|
669
|
+
|
|
670
|
+
wrapper.appendChild(label);
|
|
671
|
+
wrapper.appendChild(input);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
shadow.appendChild(wrapper);
|
|
675
|
+
document.body.appendChild(host);
|
|
676
|
+
}
|
|
393
677
|
},
|
|
394
678
|
|
|
395
679
|
/**
|
|
@@ -409,6 +693,10 @@ const ClientLibrary = {
|
|
|
409
693
|
// NOUVEAU: Inclure l'historique des mouvements de la souris pour une analyse côté serveur.
|
|
410
694
|
metrics.mouseMovementsHistory = mouseMovementsHistory;
|
|
411
695
|
|
|
696
|
+
// NOUVEAU: Keystroke dynamics metrics (dwell and flight times)
|
|
697
|
+
metrics.keystrokeDwellTimes = keystrokeDwellTimes;
|
|
698
|
+
metrics.keystrokeFlightTimes = keystrokeFlightTimes;
|
|
699
|
+
|
|
412
700
|
// Calcule la latence moyenne des frappes
|
|
413
701
|
if (keystrokeLatencies.length > 0) {
|
|
414
702
|
const sum = keystrokeLatencies.reduce((a, b) => a + b, 0);
|
|
@@ -537,24 +825,61 @@ const ClientLibrary = {
|
|
|
537
825
|
return;
|
|
538
826
|
}
|
|
539
827
|
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
828
|
+
const host = document.createElement('div');
|
|
829
|
+
host.setAttribute('aria-hidden', 'true');
|
|
830
|
+
host.style.position = 'absolute';
|
|
831
|
+
host.style.width = '0';
|
|
832
|
+
host.style.height = '0';
|
|
833
|
+
host.style.overflow = 'hidden';
|
|
834
|
+
|
|
835
|
+
const shadow = host.attachShadow({ mode: 'closed' });
|
|
836
|
+
|
|
837
|
+
const style = document.createElement('style');
|
|
838
|
+
style.textContent = `
|
|
839
|
+
:host {
|
|
840
|
+
--trap-layout-pos: absolute;
|
|
841
|
+
--trap-offset-val: -9999px;
|
|
842
|
+
--trap-visibility-state: hidden;
|
|
843
|
+
--trap-scale-factor: 0;
|
|
844
|
+
--trap-ptr-events: none;
|
|
845
|
+
}
|
|
846
|
+
.shadow-trap-wrapper {
|
|
847
|
+
position: var(--trap-layout-pos);
|
|
848
|
+
left: var(--trap-offset-val);
|
|
849
|
+
top: var(--trap-offset-val);
|
|
850
|
+
visibility: var(--trap-visibility-state);
|
|
851
|
+
transform: scale(var(--trap-scale-factor));
|
|
852
|
+
pointer-events: var(--trap-ptr-events);
|
|
853
|
+
}
|
|
854
|
+
a {
|
|
855
|
+
color: transparent;
|
|
856
|
+
text-decoration: none;
|
|
857
|
+
}
|
|
858
|
+
`;
|
|
859
|
+
shadow.appendChild(style);
|
|
860
|
+
|
|
861
|
+
const wrapper = document.createElement('div');
|
|
862
|
+
wrapper.className = 'shadow-trap-wrapper';
|
|
547
863
|
|
|
548
|
-
urls.forEach((url,i) => {
|
|
864
|
+
urls.forEach((url, i) => {
|
|
549
865
|
const link = document.createElement('a');
|
|
550
866
|
link.href = url;
|
|
551
867
|
link.rel = 'nofollow';
|
|
552
|
-
link.tabIndex = -1;
|
|
553
|
-
link.innerHTML = `<span>> ${i+1}</span>`;
|
|
554
|
-
|
|
868
|
+
link.tabIndex = -1;
|
|
869
|
+
link.innerHTML = `<span>> ${i + 1}</span>`;
|
|
870
|
+
|
|
871
|
+
const trigger = () => {
|
|
872
|
+
this.onHoneypotTrigger();
|
|
873
|
+
};
|
|
874
|
+
link.addEventListener('click', trigger, { passive: true });
|
|
875
|
+
link.addEventListener('focus', trigger, { passive: true });
|
|
876
|
+
link.addEventListener('mouseover', trigger, { passive: true });
|
|
877
|
+
|
|
878
|
+
wrapper.appendChild(link);
|
|
555
879
|
});
|
|
556
880
|
|
|
557
|
-
|
|
881
|
+
shadow.appendChild(wrapper);
|
|
882
|
+
document.body.appendChild(host);
|
|
558
883
|
},
|
|
559
884
|
/**
|
|
560
885
|
* Intercepte une réponse de challenge JSON, le résout, et réessaie la requête.
|
|
@@ -614,6 +939,7 @@ const ClientLibrary = {
|
|
|
614
939
|
keystrokes = true,
|
|
615
940
|
clicks = true, // Add new option
|
|
616
941
|
touches = true, // Nouveau paramètre tactiles
|
|
942
|
+
rendering = true,
|
|
617
943
|
phantomTraps = true, // NOUVEAU
|
|
618
944
|
honeypots = [],
|
|
619
945
|
trapUrls = [], // Nouveau paramètre pour les URL pièges
|
|
@@ -638,6 +964,9 @@ const ClientLibrary = {
|
|
|
638
964
|
if (touches) {
|
|
639
965
|
this.startTouchEventTracker();
|
|
640
966
|
}
|
|
967
|
+
if (rendering) {
|
|
968
|
+
this.startRenderingTracker();
|
|
969
|
+
}
|
|
641
970
|
if (phantomTraps) {
|
|
642
971
|
this.injectPhantomTraps();
|
|
643
972
|
}
|
|
@@ -806,6 +1135,7 @@ const metrics = {
|
|
|
806
1135
|
honeypotInteraction: false,
|
|
807
1136
|
historyLength: 0,
|
|
808
1137
|
clientTimestamp: 0,
|
|
1138
|
+
rendering: { fps: 0, jitter: 0, offscreenAnom: false },
|
|
809
1139
|
};
|
|
810
1140
|
|
|
811
1141
|
let lastMousePos = { x: 0, y: 0 };
|
|
@@ -818,6 +1148,8 @@ const CLICKS_HISTORY_MAX = 50;
|
|
|
818
1148
|
let activeHoneypotListeners = new Map(); // Garde une trace des écouteurs actifs
|
|
819
1149
|
let keystrokeTimestamps = [];
|
|
820
1150
|
let keystrokeLatencies = []; // NOUVEAU: Tableau dédié pour les latences
|
|
1151
|
+
let keystrokeDwellTimes = [];
|
|
1152
|
+
let keystrokeFlightTimes = [];
|
|
821
1153
|
const KEYSTROKE_HISTORY_MAX = 20; // On garde l'historique des 20 dernières frappes
|
|
822
1154
|
|
|
823
1155
|
|
|
@@ -831,6 +1163,7 @@ export const startMouseEntropyTracker = ClientLibrary.startMouseEntropyTracker.b
|
|
|
831
1163
|
export const startKeystrokeDynamicsTracker = ClientLibrary.startKeystrokeDynamicsTracker.bind(ClientLibrary);
|
|
832
1164
|
export const startClickTracker = ClientLibrary.startClickTracker.bind(ClientLibrary);
|
|
833
1165
|
export const startTouchEventTracker = ClientLibrary.startTouchEventTracker.bind(ClientLibrary);
|
|
1166
|
+
export const startRenderingTracker = ClientLibrary.startRenderingTracker.bind(ClientLibrary);
|
|
834
1167
|
export const initializeHoneypots = ClientLibrary.initializeHoneypots.bind(ClientLibrary);
|
|
835
1168
|
export const getClientBehaviorMetrics = ClientLibrary.getClientBehaviorMetrics.bind(ClientLibrary);
|
|
836
1169
|
export const protectedFetch = ClientLibrary.protectedFetch.bind(ClientLibrary);
|
|
@@ -842,6 +1175,10 @@ export const initializeWasm = ClientLibrary.initializeWasm.bind(ClientLibrary);
|
|
|
842
1175
|
export const injectTrapLinks = ClientLibrary.injectTrapLinks.bind(ClientLibrary);
|
|
843
1176
|
export const injectPhantomTraps = ClientLibrary.injectPhantomTraps.bind(ClientLibrary);
|
|
844
1177
|
export const solveChallengeAndRetry = ClientLibrary.solveChallengeAndRetry.bind(ClientLibrary);
|
|
1178
|
+
export const generateZkpProof = ClientLibrary.generateZkpProof.bind(ClientLibrary);
|
|
1179
|
+
export const initializeSpace = ClientLibrary.initializeSpace.bind(ClientLibrary);
|
|
1180
|
+
export const readSpaceBlock = ClientLibrary.readSpaceBlock.bind(ClientLibrary);
|
|
1181
|
+
export const solveSpaceChallenge = ClientLibrary.solveSpaceChallenge.bind(ClientLibrary);
|
|
845
1182
|
|
|
846
1183
|
// Export the internal object for testing purposes
|
|
847
1184
|
export default ClientLibrary;
|