@anonympins/fingerprint 0.5.2 → 0.5.5

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/package.json +1 -1
  3. package/src/js/build-client.js +67 -67
  4. package/src/js/fingerprint.client.js +280 -38
  5. package/src/js/fingerprint.client.obfuscated.js +1 -1
  6. package/src/js/fingerprint.js +474 -163
  7. package/src/js/fingerprint.utils.js +213 -213
  8. package/src/js/library.js +24 -8
  9. package/src/js/mongodb-store.js +165 -79
  10. package/src/js/obfuscation.worker.js +23 -0
  11. package/src/js/pow.solver.inline.js +509 -14
  12. package/src/js/pow.solver.js +350 -32
  13. package/src/js/problem-manager.js +1 -1
  14. package/src/js/redis-store.js +114 -42
  15. package/src/js/tests/cross_parity.test.js +175 -0
  16. package/src/js/tests/dns-circuit-breaker.test.js +55 -0
  17. package/src/js/tests/fingerprint.client.init.test.js +143 -143
  18. package/src/js/tests/fingerprint.client.test.js +128 -128
  19. package/src/js/tests/fingerprint.test.js +57 -2
  20. package/src/js/tests/ip-reputation.test.js +1 -1
  21. package/src/js/tests/obfuscation.test.js +48 -0
  22. package/src/js/tests/pow.solver.test.js +5 -2
  23. package/src/js/tests/usefulWorkSolver.test.js +162 -0
  24. package/src/php/AutoTuner.php +42 -0
  25. package/src/php/Challenge/ChallengeUtils.php +21 -8
  26. package/src/php/FingerprintClient.php +22 -5
  27. package/src/php/FingerprintEngine.php +233 -57
  28. package/src/php/Optimization/Optimization.php +23 -16
  29. package/src/php/Optimization/OptimizationOperators.php +27 -0
  30. package/src/php/ProblemManager.php +1 -1
  31. package/src/php/Store/MongoDbStore.php +184 -104
  32. package/src/php/Store/RedisStore.php +91 -11
  33. package/src/php/Tests/ChallengeUtilsTest.php +285 -285
  34. package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
  35. package/src/php/Tests/IpReputationTest.php +1 -1
  36. package/src/php/Tests/PowTest.php +1 -1
  37. package/src/php/Tests/RequestUtilsTest.php +29 -0
  38. package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
  39. package/src/php/Utils/BigInt.php +202 -202
  40. package/src/php/Utils/RequestUtils.php +74 -21
  41. package/src/php/Utils/TLSClientHelloParser.php +212 -116
package/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ ## Version 0.5.5
2
+
3
+ - **🔒 Client Session Handshake & HMAC Metric Signing**: Implemented elliptic-curve Diffie-Hellman (ECDH over curve `P-256`) key exchange protocol during client initialization (`?fp_handshake=1`). Leveraged the negotiated session key to cryptographically sign behavioral metrics (`signMetrics`) on the client using HMAC-SHA256, protecting against replay attacks and telemetry tampering.
4
+ - **🧵 Non-Blocking Polymorphic Obfuscation Worker**: Offloaded CPU-heavy JavaScript obfuscation and parsing pipelines (`compilePolymorphicJs`) to a dedicated Node.js Worker Thread (`obfuscation.worker.js`). This isolates resource-intensive obfuscation tasks from the main event loop, preventing latency spikes on production servers.
5
+ - **💾 Performance Optimizations with SimpleLRUCache**: Added an in-memory `SimpleLRUCache` to eliminate synchronous file system calls (`readFileSync`). Standardized caching of custom HTML challenge pages and static files (`fp.js`/`fp.wasm`).
6
+ - **🛡️ Auto-Tuner Stability Guardrails & Rollbacks**: Implemented post-calculation validation checks with a configurable `validationTolerance` threshold (default `0.15`). Automatically rolls back unstable parameters and triggers an `autotuning_instability_alert` if proposed changes degrade FPR/FNR metrics.
7
+ - **🧬 Strict Multi-Language Cross-Parity**: Aligned 32-bit single-precision floating-point (IEEE 754) calculations for chaotic map trajectories across WebGPU, WebGL, C++ WASM, and PHP environments. Guaranteed bit-to-bit convergence across 1,000 random seeds.
8
+ - **🧮 Fixed Zero Threshold PoW Shift Error**: Patched a potential bitwise shift crash (`1n << shift` with a negative shift) inside `calculateTarget` when the suspicion score dropped to or below zero. Legitimate requests now resolve directly to the maximum target range.
9
+
10
+ ## Version 0.5.4
11
+
12
+ - **🔒 JA3/JA4 Handshake Parsing Hardening (PHP/Python)**: Fixed ALPN length constraints and untangled nested loops in the binary TLS Client Hello decoder, ensuring native and highly reliable raw JA4 fingerprint calculations directly at the socket layer.
13
+ - **⚡ Dynamic WebAssembly (WASM) & Memory Safety**: Secured heap memory allocation and the dynamic loading of the polymorphic WASM module. Added comprehensive client-side unit tests to validate safe execution and clean memory deallocation.
14
+ - **🛠️ Useful Proof-of-Work (uPoW) & Integration Tests**: Finalized the end-to-end integration and orchestration of uPoW tasks, backed by robust validation tests for multi-objective optimization models (specifically CPC and fraud detection).
15
+ - **🛡️ Store Resilience & CHIPS Cookie Support**: Enhanced fault tolerance for Redis and MongoDB database adapters with seamless, automatic fallback to local in-memory storage during network drops. Standardized CHIPS (`Partitioned`) cookie attributes across secure HTTPS environments.
16
+ - **📁 Deterministic Path Resolution Fixes**: Implemented robust absolute and relative path resolution for loading configuration files (such as `problems.config.json`) uniformly across Node.js, PHP, and Python environments.
17
+
18
+ ## Version 0.5.3
19
+
20
+ - **DNS Circuit Breaker with Strict Timeouts**: Protected reverse/forward DNS verification of legitimate crawlers against DNS server degradation or DDoS exhaustion. Integrated a stateful Circuit Breaker (`CLOSED`, `OPEN`, `HALF-OPEN`) with a strict **500 ms timeout** and **5-minute temporary negative caching** on resolution failures.
21
+ - **Anti-Poisoning via Hardware Clustering**: Hardened `sanitizeTrafficData` to cluster traffic logs by stable hardware signatures (Canvas + GPU + CPU). Contributions from any single hardware footprint are strictly capped to **2% of the dataset**, neutralizing massive proxy-rotation Sybil attacks designed to manipulate tuning thresholds.
22
+ - **Memory-Safe Traffic Pruning**: Standardized `pruneTrafficData` across Node.js and PHP engines. Includes strict time-based decay and size-capping policy, keeping the in-memory footprint minimal and preventing leaks during long-running background tuning sessions.
23
+ - **Enhanced Subnet Detection**: Upgraded IPv4 and IPv6 subnet masking routines (`getIpSubnet`). Implemented proper binary bitwise masking and IPv6 compression normalization (`::` expansion), ensuring high-reliability network block reputation scoring (`subnetScore`).
24
+ - **Centralized Problem Configuration**: Relocated `problems.config.json` to the central `/config` directory for unified configuration management and cleaner multi-environment deployments.
25
+ - **Stealthy Honeypot Enhancements**: Hardened client-side honeypots by dynamically rendering hidden input elements inside a closed Shadow DOM using nested randomized CSS variables, effectively evading detection by headless crawlers.
26
+ - **Mathematical Benford Test Optimization**: Re-engineered `benfordTest` across JavaScript, PHP, and Python to extract the first significant digit using math operations (logarithms and modulo) instead of string casts, significantly boosting performance on large time-series.
27
+
1
28
  ## Version 0.5.2
2
29
 
3
30
  - Security updates (CodeQL issues fixed)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anonympins/fingerprint",
3
- "version": "0.5.2",
3
+ "version": "0.5.5",
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",
@@ -1,68 +1,68 @@
1
- import {promises as fs} from 'node:fs';
2
- import {dirname, join} from 'node:path';
3
- import {fileURLToPath} from 'node:url';
4
- import {exec} from 'node:child_process';
5
- import JavaScriptObfuscator from 'javascript-obfuscator';
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = dirname(__filename);
9
-
10
- /**
11
- * Tente d'exécuter la commande de build pour le module WASM.
12
- * Ne bloque pas le build si la commande échoue (ex: em++ non trouvé).
13
- */
14
- function buildWasm() {
15
- return new Promise((resolve) => {
16
- console.log('Attempting to build WASM module (optional)...');
17
- const command = "em++ src/cpp/main.cpp src/cpp/utils.cpp -o public/fp.js -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME='createFingerprintModule' -s \"EXPORTED_FUNCTIONS=['_hash_string','_solve_cpu_target','_solve_memory_challenge']\" -O3 --no-entry";
18
-
19
- exec(command, (error, stdout, stderr) => {
20
- if (error) {
21
- console.warn('WASM build failed (this is optional and can be ignored):', stderr);
22
- } else {
23
- console.log('WASM module built successfully.');
24
- }
25
- resolve(); // Toujours résoudre la promesse pour ne pas bloquer le build principal.
26
- });
27
- });
28
- }
29
-
30
- async function buildClientScript() {
31
- try {
32
- console.log('Reading client script...');
33
- const clientScriptPath = join(__dirname, 'fingerprint.client.js');
34
- const clientScriptContent = await fs.readFile(clientScriptPath, 'utf-8');
35
-
36
- console.log('Obfuscating client script...');
37
- const obfuscationResult = JavaScriptObfuscator.obfuscate(clientScriptContent, {
38
- compact: true,
39
- controlFlowFlattening: true, // Aplatit le flux de contrôle
40
- deadCodeInjection: true, // Injecte du code mort
41
- stringArray: true,
42
- stringArrayRotate: true, // Fait tourner le tableau de chaînes
43
- stringArrayShuffle: true, // Mélange le tableau de chaînes
44
- // L'utilisation d'une graine (seed) de 0 signifie que l'obfuscation sera déterministe
45
- // pour une même entrée. Pour une obfuscation unique à chaque build, on peut utiliser
46
- // une graine aléatoire, par exemple : seed: Math.random()
47
- seed: Math.random(),
48
- selfDefending: true,
49
- });
50
-
51
- const obfuscatedCode = obfuscationResult.getObfuscatedCode();
52
-
53
- console.log('Writing obfuscated script to fingerprint.client.obfuscated.js...');
54
- await fs.writeFile(join(__dirname, './fingerprint.client.obfuscated.js'), obfuscatedCode);
55
-
56
- console.log('Client script build process completed successfully.');
57
- } catch (error) {
58
- console.error('Error during client script build:', error);
59
- process.exit(1);
60
- }
61
- }
62
-
63
- async function main() {
64
- await buildClientScript();
65
- await buildWasm();
66
- }
67
-
1
+ import {promises as fs} from 'node:fs';
2
+ import {dirname, join} from 'node:path';
3
+ import {fileURLToPath} from 'node:url';
4
+ import {exec} from 'node:child_process';
5
+ import JavaScriptObfuscator from 'javascript-obfuscator';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ /**
11
+ * Tente d'exécuter la commande de build pour le module WASM.
12
+ * Ne bloque pas le build si la commande échoue (ex: em++ non trouvé).
13
+ */
14
+ function buildWasm() {
15
+ return new Promise((resolve) => {
16
+ console.log('Attempting to build WASM module (optional)...');
17
+ const command = "em++ src/cpp/main.cpp src/cpp/utils.cpp -o public/fp.js -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME='createFingerprintModule' -s \"EXPORTED_FUNCTIONS=['_hash_string','_solve_cpu_target','_solve_memory_challenge','_generate_gpu_pow_trajectory']\" -O3 --no-entry";
18
+
19
+ exec(command, (error, stdout, stderr) => {
20
+ if (error) {
21
+ console.warn('WASM build failed (this is optional and can be ignored):', stderr);
22
+ } else {
23
+ console.log('WASM module built successfully.');
24
+ }
25
+ resolve(); // Toujours résoudre la promesse pour ne pas bloquer le build principal.
26
+ });
27
+ });
28
+ }
29
+
30
+ async function buildClientScript() {
31
+ try {
32
+ console.log('Reading client script...');
33
+ const clientScriptPath = join(__dirname, 'fingerprint.client.js');
34
+ const clientScriptContent = await fs.readFile(clientScriptPath, 'utf-8');
35
+
36
+ console.log('Obfuscating client script...');
37
+ const obfuscationResult = JavaScriptObfuscator.obfuscate(clientScriptContent, {
38
+ compact: true,
39
+ controlFlowFlattening: true, // Aplatit le flux de contrôle
40
+ deadCodeInjection: true, // Injecte du code mort
41
+ stringArray: true,
42
+ stringArrayRotate: true, // Fait tourner le tableau de chaînes
43
+ stringArrayShuffle: true, // Mélange le tableau de chaînes
44
+ // L'utilisation d'une graine (seed) de 0 signifie que l'obfuscation sera déterministe
45
+ // pour une même entrée. Pour une obfuscation unique à chaque build, on peut utiliser
46
+ // une graine aléatoire, par exemple : seed: Math.random()
47
+ seed: Math.random(),
48
+ selfDefending: true,
49
+ });
50
+
51
+ const obfuscatedCode = obfuscationResult.getObfuscatedCode();
52
+
53
+ console.log('Writing obfuscated script to fingerprint.client.obfuscated.js...');
54
+ await fs.writeFile(join(__dirname, './fingerprint.client.obfuscated.js'), obfuscatedCode);
55
+
56
+ console.log('Client script build process completed successfully.');
57
+ } catch (error) {
58
+ console.error('Error during client script build:', error);
59
+ process.exit(1);
60
+ }
61
+ }
62
+
63
+ async function main() {
64
+ await buildClientScript();
65
+ await buildWasm();
66
+ }
67
+
68
68
  main();
@@ -1,14 +1,96 @@
1
1
  import {cyrb53 as jsCyrb53, FingerprintBuilder} from './fingerprint.builder.js';
2
2
  import {solveChallenge} from './pow.solver.js';
3
3
 
4
+ function secureRandom() {
5
+ if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
6
+ const array = new Uint32Array(1);
7
+ (window.crypto || window.msCrypto).getRandomValues(array);
8
+ return array[0] / 0x100000000;
9
+ }
10
+ return Math.random();
11
+ }
12
+
4
13
  // Variable pour stocker la fonction de hachage active.
5
14
  // Par défaut, c'est l'implémentation JavaScript.
6
15
  let activeCyrb53 = jsCyrb53;
16
+ let derivedKey = null;
17
+
18
+ async function negotiateSessionKey() {
19
+ if (typeof window === 'undefined' || !window.crypto || !window.crypto.subtle) return;
20
+ try {
21
+ const keyPair = await window.crypto.subtle.generateKey(
22
+ { name: "ECDH", namedCurve: "P-256" },
23
+ false,
24
+ ["deriveKey"]
25
+ );
26
+ const clientPubKeyBuffer = await window.crypto.subtle.exportKey("raw", keyPair.publicKey);
27
+ const clientPubKeyHex = Array.from(new Uint8Array(clientPubKeyBuffer))
28
+ .map(b => b.toString(16).padStart(2, '0'))
29
+ .join('');
30
+
31
+ const response = await fetch(window.location.pathname + '?fp_handshake=1', {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Type': 'application/json',
35
+ 'x-client-ephemeral-key': clientPubKeyHex
36
+ },
37
+ body: JSON.stringify({ fp: ClientLibrary.getDeviceFingerprint() })
38
+ });
39
+
40
+ const serverPubKeyHex = response.headers.get('x-server-ephemeral-key');
41
+ if (serverPubKeyHex) {
42
+ const serverPubKeyBuffer = new Uint8Array(
43
+ serverPubKeyHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
44
+ );
45
+ const serverPubKey = await window.crypto.subtle.importKey(
46
+ "raw",
47
+ serverPubKeyBuffer,
48
+ { name: "ECDH", namedCurve: "P-256" },
49
+ true,
50
+ []
51
+ );
52
+ derivedKey = await window.crypto.subtle.deriveKey(
53
+ { name: "ECDH", public: serverPubKey },
54
+ keyPair.privateKey,
55
+ { name: "HMAC", hash: "SHA-256", length: 256 },
56
+ true,
57
+ ["sign"]
58
+ );
59
+ console.log('[Fingerprint] Cryptographic session negotiated successfully.');
60
+ }
61
+ } catch (e) {
62
+ console.warn('[Fingerprint] Cryptographic session negotiation failed:', e);
63
+ }
64
+ }
65
+
66
+ async function signMetrics(metricsObj) {
67
+ if (!derivedKey) return metricsObj;
68
+ try {
69
+ const copy = JSON.parse(JSON.stringify(metricsObj));
70
+ delete copy.signature;
71
+ const dataToSign = new TextEncoder().encode(JSON.stringify(copy));
72
+ const signatureBuffer = await window.crypto.subtle.sign(
73
+ "HMAC",
74
+ derivedKey,
75
+ dataToSign
76
+ );
77
+ copy.signature = Array.from(new Uint8Array(signatureBuffer))
78
+ .map(b => b.toString(16).padStart(2, '0'))
79
+ .join('');
80
+ return copy;
81
+ } catch (e) {
82
+ return metricsObj;
83
+ }
84
+ }
7
85
 
8
86
  const DB_NAME = 'wasm-cache-db';
9
87
  const DB_VERSION = 1;
10
88
  const STORE_NAME = 'wasm-modules';
11
89
 
90
+ const genRandStr = (len = 8) => {
91
+ return Array.from({ length: len }, () => String.fromCharCode(97 + Math.floor(secureRandom() * 26))).join('');
92
+ };
93
+
12
94
  function getCachedWasm(url) {
13
95
  return new Promise((resolve) => {
14
96
  if (typeof indexedDB === 'undefined') return resolve(null);
@@ -282,8 +364,10 @@ const ClientLibrary = {
282
364
  // Création d'un élément interactif fantôme
283
365
  const phantom = document.createElement('a');
284
366
  phantom.href = '#';
285
- // Nom trompeur pour attirer les analyseurs automatiques de liens / formulaires
286
- phantom.id = 'sys-session-recovery';
367
+ // Nom trompeur aléatoire pour attirer les analyseurs automatiques de liens / formulaires
368
+ const phantomNames = ['sys-session-recovery', 'auth-token-refresh', 'debug-console-login', 'admin-portal-access', 'security-bypass-bypass', 'recovery-key-session', 'api-key-test', 'client-secrets-access'];
369
+ phantom.id = phantomNames[Math.floor(secureRandom() * phantomNames.length)] + '-' + genRandStr(6);
370
+ phantom.className = genRandStr(8);
287
371
  phantom.tabIndex = 0; // Dans le flux naturel de tabulation
288
372
  phantom.setAttribute('aria-hidden', 'true'); // Masqué pour les screen readers légitimes
289
373
 
@@ -305,7 +389,21 @@ const ClientLibrary = {
305
389
  phantom.addEventListener('focus', triggerTrap, { passive: true });
306
390
  phantom.addEventListener('mouseover', triggerTrap, { passive: true });
307
391
 
308
- document.body.appendChild(phantom);
392
+ // Imbrication polymorphique du piège fantôme dans le corps du document
393
+ const nestingOptions = [
394
+ () => document.body.appendChild(phantom),
395
+ () => {
396
+ const wrapper = document.createElement(secureRandom() > 0.5 ? 'span' : 'div');
397
+ wrapper.className = genRandStr(8);
398
+ wrapper.style.position = 'absolute';
399
+ wrapper.style.width = '0';
400
+ wrapper.style.height = '0';
401
+ wrapper.style.overflow = 'hidden';
402
+ wrapper.appendChild(phantom);
403
+ document.body.appendChild(wrapper);
404
+ }
405
+ ];
406
+ nestingOptions[Math.floor(secureRandom() * nestingOptions.length)]();
309
407
  },
310
408
 
311
409
  /**
@@ -631,26 +729,35 @@ const ClientLibrary = {
631
729
 
632
730
  const shadow = host.attachShadow({ mode: 'closed' });
633
731
 
732
+ // Aléatisation des classes, variables CSS et balises de structure
733
+ const wrapperClass = genRandStr(10);
734
+ const posStateVar = `--${genRandStr(8)}`;
735
+ const offValVar = `--${genRandStr(8)}`;
736
+ const visStateVar = `--${genRandStr(8)}`;
737
+ const scaleValVar = `--${genRandStr(8)}`;
738
+
634
739
  const style = document.createElement('style');
635
740
  style.textContent = `
636
741
  :host {
637
- --trap-pos-state: absolute;
638
- --trap-off-val: -9999px;
639
- --trap-vis-state: hidden;
640
- --trap-scale-val: 0;
742
+ ${posStateVar}: absolute;
743
+ ${offValVar}: -9999px;
744
+ ${visStateVar}: hidden;
745
+ ${scaleValVar}: 0;
641
746
  }
642
- .shadow-form-wrapper {
643
- position: var(--trap-pos-state);
644
- left: var(--trap-off-val);
645
- top: var(--trap-off-val);
646
- visibility: var(--trap-vis-state);
647
- transform: scale(var(--trap-scale-val));
747
+ .${wrapperClass} {
748
+ position: var(${posStateVar});
749
+ left: var(${offValVar});
750
+ top: var(${offValVar});
751
+ visibility: var(${visStateVar});
752
+ transform: scale(var(${scaleValVar}));
648
753
  }
649
754
  `;
650
755
  shadow.appendChild(style);
651
756
 
652
- const wrapper = document.createElement('div');
653
- wrapper.className = 'shadow-form-wrapper';
757
+ const wrapperTags = ['div', 'section', 'p', 'span', 'form', 'main'];
758
+ const selectedWrapperTag = wrapperTags[Math.floor(secureRandom() * wrapperTags.length)];
759
+ const wrapper = document.createElement(selectedWrapperTag);
760
+ wrapper.className = wrapperClass;
654
761
 
655
762
  honeypotFieldNames.forEach(fieldName => {
656
763
  const label = document.createElement('label');
@@ -659,6 +766,8 @@ const ClientLibrary = {
659
766
  input.type = 'text';
660
767
  input.name = fieldName;
661
768
  input.tabIndex = -1;
769
+ input.className = genRandStr(6);
770
+ input.id = genRandStr(8);
662
771
  input.autocomplete = 'off';
663
772
 
664
773
  const trigger = () => {
@@ -669,14 +778,108 @@ const ClientLibrary = {
669
778
  input.addEventListener('change', trigger, { passive: true });
670
779
  input.addEventListener('focus', trigger, { passive: true });
671
780
 
672
- wrapper.appendChild(label);
673
- wrapper.appendChild(input);
781
+ // Imbrication polymorphique du label et de l'input
782
+ const nestingType = Math.floor(secureRandom() * 3);
783
+ if (nestingType === 1) {
784
+ label.appendChild(input);
785
+ wrapper.appendChild(label);
786
+ } else if (nestingType === 2) {
787
+ const innerContainer = document.createElement(secureRandom() > 0.5 ? 'span' : 'div');
788
+ innerContainer.className = genRandStr(5);
789
+ innerContainer.appendChild(label);
790
+ innerContainer.appendChild(input);
791
+ wrapper.appendChild(innerContainer);
792
+ } else {
793
+ wrapper.appendChild(label);
794
+ wrapper.appendChild(input);
795
+ }
674
796
  });
675
797
 
676
798
  shadow.appendChild(wrapper);
677
799
  document.body.appendChild(host);
678
800
  }
679
801
  },
802
+ /**
803
+ * Sollicite silencieusement l'attestation matérielle FIDO2 (WebAuthn)
804
+ * pour ancrer l'identité physique de l'appareil via TPM / Secure Enclave.
805
+ * @returns {Promise<object|null>}
806
+ */
807
+ async getWebAuthnAnchor() {
808
+ if (typeof window === 'undefined' || !window.PublicKeyCredential) return null;
809
+ try {
810
+ const isPlatformAvailable = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
811
+ if (!isPlatformAvailable) return null;
812
+
813
+ const storedCredIdBase64 = localStorage.getItem('fp_webauthn_cred_id');
814
+ const challenge = new Uint8Array([109, 101, 116, 114, 105, 99, 115, 95, 97, 110, 99, 104, 111, 114, 95, 115, 101, 99, 117, 114, 101]); // Static stable challenge
815
+
816
+ if (storedCredIdBase64) {
817
+ // Tentative d'assertion silencieuse (Authentification)
818
+ const credentialId = Uint8Array.from(atob(storedCredIdBase64), c => c.charCodeAt(0));
819
+ const assertion = await navigator.credentials.get({
820
+ publicKey: {
821
+ challenge,
822
+ allowCredentials: [{
823
+ id: credentialId,
824
+ type: 'public-key'
825
+ }],
826
+ userVerification: 'discouraged',
827
+ timeout: 1000
828
+ }
829
+ });
830
+ if (assertion) {
831
+ return {
832
+ type: 'assertion',
833
+ credentialId: storedCredIdBase64,
834
+ signature: btoa(String.fromCharCode(...new Uint8Array(assertion.response.signature))),
835
+ authenticatorData: btoa(String.fromCharCode(...new Uint8Array(assertion.response.authenticatorData))),
836
+ clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(assertion.response.clientDataJSON)))
837
+ };
838
+ }
839
+ } else {
840
+ // Création silencieuse (Enregistrement initial)
841
+ const options = {
842
+ publicKey: {
843
+ challenge,
844
+ rp: { name: window.location.hostname, id: window.location.hostname },
845
+ user: {
846
+ id: new Uint8Array([102, 112, 105, 100]),
847
+ name: 'silent-device-anchor',
848
+ displayName: 'Hardware Device Anchor'
849
+ },
850
+ pubKeyCredParams: [
851
+ { type: 'public-key', alg: -7 }, // ES256 (P-256) - Secure Enclave / TPM / Android Keystore
852
+ { type: 'public-key', alg: -257 } // RS256 - Windows Hello TPM
853
+ ],
854
+ authenticatorSelection: {
855
+ authenticatorAttachment: 'platform',
856
+ userVerification: 'discouraged',
857
+ residentKey: 'preferred'
858
+ },
859
+ timeout: 1500,
860
+ attestation: 'indirect'
861
+ }
862
+ };
863
+ const credential = await navigator.credentials.create(options);
864
+ if (credential) {
865
+ const credIdBase64 = btoa(String.fromCharCode(...new Uint8Array(credential.rawId)));
866
+ localStorage.setItem('fp_webauthn_cred_id', credIdBase64);
867
+ const publicKeyDer = credential.response.getPublicKey ? credential.response.getPublicKey() : null;
868
+ return {
869
+ type: 'registration',
870
+ credentialId: credIdBase64,
871
+ publicKey: publicKeyDer ? btoa(String.fromCharCode(...new Uint8Array(publicKeyDer))) : null,
872
+ attestationObject: btoa(String.fromCharCode(...new Uint8Array(credential.response.getAttestationObject()))),
873
+ clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(credential.response.clientDataJSON)))
874
+ };
875
+ }
876
+ }
877
+ } catch (e) {
878
+ // Ignore et bypass silencieusement pour garder l'invisibilité en cas de non-support
879
+ console.log('[WebAuthn-Anchor] Silent attestation bypassed:', e.message);
880
+ }
881
+ return null;
882
+ },
680
883
 
681
884
  /**
682
885
  * Récupère les métriques comportementales collectées.
@@ -717,7 +920,8 @@ const ClientLibrary = {
717
920
  */
718
921
  async protectedFetch(resource, options = {}) {
719
922
  const fp = this.getDeviceFingerprint();
720
- const behavior = this.getClientBehaviorMetrics();
923
+ const rawBehavior = this.getClientBehaviorMetrics();
924
+ const behavior = await signMetrics(rawBehavior);
721
925
 
722
926
  const headers = new Headers(options.headers || {});
723
927
  headers.set('X-Device-Fingerprint', fp);
@@ -784,7 +988,7 @@ const ClientLibrary = {
784
988
  * Si non fourni, protège les requêtes de même origine.
785
989
  */
786
990
  initializeFetch(targetDomains = []) {
787
- const fingerprintInterceptor = (resource, options, next) => {
991
+ const fingerprintInterceptor = async (resource, options, next) => {
788
992
  const requestUrl = (resource instanceof Request) ? resource.url : String(resource);
789
993
  let shouldProtect = false;
790
994
 
@@ -803,7 +1007,8 @@ const ClientLibrary = {
803
1007
 
804
1008
  if (shouldProtect) {
805
1009
  const fp = this.getDeviceFingerprint();
806
- const behavior = this.getClientBehaviorMetrics();
1010
+ const rawBehavior = this.getClientBehaviorMetrics();
1011
+ const behavior = await signMetrics(rawBehavior);
807
1012
  const headers = new Headers(options.headers || {});
808
1013
  headers.set('X-Device-Fingerprint', fp);
809
1014
  headers.set('X-Behavior-Metrics', JSON.stringify(behavior));
@@ -836,39 +1041,67 @@ const ClientLibrary = {
836
1041
 
837
1042
  const shadow = host.attachShadow({ mode: 'closed' });
838
1043
 
1044
+ // Aléatisation des classes, variables CSS et balises de structure
1045
+ const wrapperClass = genRandStr(10);
1046
+ const layoutPosVar = `--${genRandStr(8)}`;
1047
+ const offsetValVar = `--${genRandStr(8)}`;
1048
+ const visibilityStateVar = `--${genRandStr(8)}`;
1049
+ const scaleFactorVar = `--${genRandStr(8)}`;
1050
+ const ptrEventsVar = `--${genRandStr(8)}`;
1051
+ const linkColorClass = genRandStr(6);
1052
+
839
1053
  const style = document.createElement('style');
840
1054
  style.textContent = `
841
1055
  :host {
842
- --trap-layout-pos: absolute;
843
- --trap-offset-val: -9999px;
844
- --trap-visibility-state: hidden;
845
- --trap-scale-factor: 0;
846
- --trap-ptr-events: none;
1056
+ ${layoutPosVar}: absolute;
1057
+ ${offsetValVar}: -9999px;
1058
+ ${visibilityStateVar}: hidden;
1059
+ ${scaleFactorVar}: 0;
1060
+ ${ptrEventsVar}: none;
847
1061
  }
848
- .shadow-trap-wrapper {
849
- position: var(--trap-layout-pos);
850
- left: var(--trap-offset-val);
851
- top: var(--trap-offset-val);
852
- visibility: var(--trap-visibility-state);
853
- transform: scale(var(--trap-scale-factor));
854
- pointer-events: var(--trap-ptr-events);
1062
+ .${wrapperClass} {
1063
+ position: var(${layoutPosVar});
1064
+ left: var(${offsetValVar});
1065
+ top: var(${offsetValVar});
1066
+ visibility: var(${visibilityStateVar});
1067
+ transform: scale(var(${scaleFactorVar}));
1068
+ pointer-events: var(${ptrEventsVar});
855
1069
  }
856
- a {
1070
+ .${linkColorClass} {
857
1071
  color: transparent;
858
1072
  text-decoration: none;
859
1073
  }
860
1074
  `;
861
1075
  shadow.appendChild(style);
862
1076
 
863
- const wrapper = document.createElement('div');
864
- wrapper.className = 'shadow-trap-wrapper';
1077
+ const wrapperTags = ['div', 'section', 'p', 'span', 'nav', 'aside'];
1078
+ const selectedWrapperTag = wrapperTags[Math.floor(secureRandom() * wrapperTags.length)];
1079
+ const wrapper = document.createElement(selectedWrapperTag);
1080
+ wrapper.className = wrapperClass;
865
1081
 
866
1082
  urls.forEach((url, i) => {
867
1083
  const link = document.createElement('a');
868
1084
  link.href = url;
869
1085
  link.rel = 'nofollow';
870
1086
  link.tabIndex = -1;
871
- link.innerHTML = `<span>&gt; ${i + 1}</span>`;
1087
+ link.className = linkColorClass;
1088
+ link.id = genRandStr(8);
1089
+
1090
+ // Structure interne polymorphique du lien
1091
+ const contentNestingType = Math.floor(secureRandom() * 3);
1092
+ if (contentNestingType === 1) {
1093
+ const span = document.createElement('span');
1094
+ span.className = genRandStr(5);
1095
+ span.innerHTML = `&gt; ${i + 1}`;
1096
+ link.appendChild(span);
1097
+ } else if (contentNestingType === 2) {
1098
+ const b = document.createElement('b');
1099
+ b.className = genRandStr(5);
1100
+ b.innerHTML = `&gt; ${i + 1}`;
1101
+ link.appendChild(b);
1102
+ } else {
1103
+ link.innerHTML = `<span>&gt; ${i + 1}</span>`;
1104
+ }
872
1105
 
873
1106
  const trigger = () => {
874
1107
  this.onHoneypotTrigger();
@@ -877,7 +1110,15 @@ const ClientLibrary = {
877
1110
  link.addEventListener('focus', trigger, { passive: true });
878
1111
  link.addEventListener('mouseover', trigger, { passive: true });
879
1112
 
880
- wrapper.appendChild(link);
1113
+ // Imbrication du lien de manière polymorphique
1114
+ if (secureRandom() > 0.5) {
1115
+ const itemContainer = document.createElement('span');
1116
+ itemContainer.className = genRandStr(5);
1117
+ itemContainer.appendChild(link);
1118
+ wrapper.appendChild(itemContainer);
1119
+ } else {
1120
+ wrapper.appendChild(link);
1121
+ }
881
1122
  });
882
1123
 
883
1124
  shadow.appendChild(wrapper);
@@ -912,7 +1153,7 @@ const ClientLibrary = {
912
1153
 
913
1154
  this._dispatchEvent('challengeSolved', { solution: solutionWrapper.rawSolution });
914
1155
  // Ajouter la solution aux paramètres de la requête pour le nouvel essai
915
- const url = new URL((resource instanceof Request) ? resource.url : String(resource), window.location.origin);
1156
+ const url = new URL((resource instanceof Request) ? resource.url : String(resource), window.location.origin);
916
1157
  // La logique de formatage est maintenant cachée dans la classe ChallengeSolution.
917
1158
  solutionWrapper.applyToUrl(url);
918
1159
 
@@ -949,6 +1190,7 @@ const ClientLibrary = {
949
1190
  fetch: fetchConfig = {}
950
1191
  } = config;
951
1192
 
1193
+ negotiateSessionKey();
952
1194
  // Tentative de chargement du WASM si le chemin est fourni
953
1195
  if (wasmPath) {
954
1196
  this.initializeWasm(wasmPath);