@anonympins/fingerprint 0.6.7 → 0.7.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +3 -7
  3. package/composer.json +8 -1
  4. package/package.json +1 -1
  5. package/public/fingerprint-wordpress.zip +0 -0
  6. package/public/fp.wasm +0 -0
  7. package/src/js/build-client.js +9 -11
  8. package/src/js/dynamic-wasm.js +7 -7
  9. package/src/js/fingerprint.builder.js +37 -38
  10. package/src/js/fingerprint.client.js +236 -152
  11. package/src/js/fingerprint.client.obfuscated.js +1 -1
  12. package/src/js/fingerprint.js +517 -101
  13. package/src/js/fingerprint.utils.js +213 -213
  14. package/src/js/library.js +355 -384
  15. package/src/js/mongodb-store.js +1 -1
  16. package/src/js/optimization.worker.js +27 -27
  17. package/src/js/pow.solver.inline.js +206 -62
  18. package/src/js/pow.solver.js +235 -101
  19. package/src/js/pow.worker.js +48 -26
  20. package/src/js/redis-store.js +1 -1
  21. package/src/js/tests/fingerprint.client.init.test.js +146 -143
  22. package/src/js/tests/fingerprint.test.js +101 -40
  23. package/src/js/tests/ip-reputation.test.js +1 -0
  24. package/src/js/upow-model-task.js +14 -15
  25. package/src/php/AutoTuner.php +34 -34
  26. package/src/php/Challenge/ChallengeUtils.php +204 -32
  27. package/src/php/Config/SecurityProfiles.php +21 -0
  28. package/src/php/DirectFingerprint.php +190 -145
  29. package/src/php/FingerprintBuilder.php +29 -30
  30. package/src/php/FingerprintClient.php +24 -25
  31. package/src/php/FingerprintEngine.php +1836 -1880
  32. package/src/php/Ja3AnomalyDetector.php +258 -227
  33. package/src/php/Optimization/FunctionRegistry.php +7 -7
  34. package/src/php/Optimization/Optimization.php +24 -24
  35. package/src/php/Optimization/OptimizationOperators.php +28 -27
  36. package/src/php/Optimization/ProblemInitializers.php +52 -52
  37. package/src/php/ProblemManager.php +31 -34
  38. package/src/php/RequestContext.php +8 -1
  39. package/src/php/Store/IStore.php +7 -8
  40. package/src/php/Store/InMemoryStore.php +66 -66
  41. package/src/php/Store/MongoDbStore.php +5 -5
  42. package/src/php/Store/RedisStore.php +5 -5
  43. package/src/php/Store/StoreManager.php +35 -35
  44. package/src/php/Tests/ChallengeUtilsTest.php +453 -453
  45. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  46. package/src/php/Tests/FingerprintEngineTest.php +662 -693
  47. package/src/php/Tests/IpReputationTest.php +10 -0
  48. package/src/php/Tests/MaliciousPatternsTest.php +103 -103
  49. package/src/php/Tests/MetricsTest.php +97 -97
  50. package/src/php/Tests/ProblemManagerTest.php +376 -376
  51. package/src/php/Tests/QuicFingerprintTest.php +53 -53
  52. package/src/php/Tests/RequestUtilsTest.php +471 -471
  53. package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
  54. package/src/php/Tests/config/ed25519_key.json +3 -3
  55. package/src/php/Utils/Env.php +49 -49
  56. package/src/php/Utils/MaliciousPatterns.php +74 -74
  57. package/src/php/Utils/MetricsManager.php +209 -209
  58. package/src/php/Utils/RequestUtils.php +236 -19
  59. package/src/php/Utils/TLSClientHelloParser.php +369 -212
  60. package/src/php/WordPress/WpDbStore.php +155 -0
  61. package/src/php/WordPress/fingerprint-wordpress.php +217 -0
  62. package/src/php/WordPress/package.php +123 -0
  63. package/src/php/bin/auto-tune.php +118 -118
package/CHANGELOG.md CHANGED
@@ -1,3 +1,36 @@
1
+ ## Version 0.7.1
2
+
3
+ - 🔌 **Native WordPress Plugin Integration (`fingerprint-wordpress`)**:
4
+ - Full official WordPress plugin integration (`fingerprint-wordpress.php`) without external runtime dependencies, leveraging `$wpdb` through a dedicated `WpDbStore` adapter.
5
+ - Automatic SQL cache table provisioning and periodic cleanup of expired tickets via WP-Cron (`fingerprint_prune_expired_entries`).
6
+ - Automated packaging script (`package.php`) generating a ready-to-deploy ZIP archive (`fingerprint-wordpress.zip`).
7
+ - Cross-environment honeypot tripwires: immediately detects and drops automated scanners probing for non-WordPress entrypoints (Laravel, Symfony, Django, Spring Boot `/actuator`, phpMyAdmin, `.env`, `.git`).
8
+ - Admin dashboard security notice if running over unencrypted HTTP (warning about modern browsers disabling native `crypto.subtle` APIs in insecure contexts).
9
+ - 🔬 **JavaScript Prototype Tampering Detection (`detectTamperedPrototypes`)**:
10
+ - Added runtime integrity inspection on critical browser APIs (`HTMLCanvasElement.prototype.toDataURL`, `CanvasRenderingContext2D.prototype.getImageData`, `WebGLRenderingContext.prototype.getParameter`, and `window.fetch`).
11
+ - Accurately detects function hooks and proxies injected by evasion frameworks (Puppeteer Stealth, Frida, Selenium) and applies an aggressive penalty (`+80` to `behaviorScore`) via the `prototypeTampered` indicator.
12
+ - 📱 **Device Motion Variance Tracking (`motionVariance`)**:
13
+ - Analyzed accelerometer and gyroscope micro-variations via the `devicemotion` API.
14
+ - Distinguishes authentic handheld mobile devices from rack-mounted phone farms or automated rigs (detecting active touch interactions with zero natural sensor tremor, `motionVariance === 0`).
15
+ - 📝 **Code Standardization & Internationalization**:
16
+ - Harmonized and fully translated technical comments into English across optimization modules (`library.js`), PoW solvers, and build scripts.
17
+
18
+ ## Version 0.7.0
19
+
20
+ - 🛡️ **Targeted Re-Challenge Strategy (`mustReChallenge`)**: Re-introduced and hardened the re-challenge workflow specifically targeted at honeypot triggers and critical indicators (e.g., `honeypotScore >= mediumThreshold` or any indicator score reaching $\ge 100$). Legitimate sessions with valid clearance tickets are preserved, while active attack attempts immediately invalidate existing tickets and force verification.
21
+ - 📡 **QUIC / HTTP/3 Transport Profiling & Protocol Anomaly Scoring**: Implemented deep inspection of QUIC transport parameters (`initial_max_data`, `initial_max_streams_bidi`, control frame ordering `s,m,p`, and extensible priority schemes). Deviations between browser User-Agents and QUIC transport implementations (e.g. `curl-impersonate`, `quiche`, custom bot clients) are surfaced in `protocolAnomalyScore` and `quicAnomalyScore`.
22
+ - ⚡ **WebRTC DataChannel for Cooperative Proof-of-Space**: Added direct peer-to-peer WebRTC DataChannel signaling (`webrtc_signal`, `RTCPeerConnection`) to cooperative Proof-of-Space challenges. Subnet nodes now exchange PoSpace verification blocks directly without routing heavy binary payloads through the application server, falling back seamlessly to HTTP mailbox polling if P2P negotiation fails.
23
+ - 📈 **Analog Asymptotic Inconsistency Scoring (`calculateAnalogInconsistencyScore`)**: Replaced abrupt, stepped similarity thresholds with a smooth sigmoid function asymptotically bounded at 99.9. This prevents discrete score cliffs and false positives for minor profile drifts across JavaScript, PHP, and Java engines.
24
+ - 🌐 **Bayesian Subnet Scoring & IP Dispersion**: Refactored `subnetScore` using Bayesian density estimation, User-Agent dispersion factors, and IP-to-device ratios scaled via hyperbolic tangent (`tanh`) with a 99.9 asymptote, effectively distinguishing dense CGNAT/corporate gateways from distributed proxy clusters.
25
+ - 🕵️ **Virtualization & Threat Intel Scoring**: Added `virtualizationScore` to expose virtual display drivers and headless emulators (SwiftShader, Mesa llvmpipe, soft-pipe renderers, and standard headless display resolutions), alongside `threatIntelScore` correlating RTT proxy timing and federated ZKP public keys.
26
+
27
+ ## Version 0.6.8
28
+
29
+ - 🧹 **Removal of Arbitrary Re-challenging**: Removed the `mustReChallenge` feature which forced an arbitrary re-challenge even when a user presented a valid clearance ticket. The system now relies exclusively on a real-time calculated suspicion score, ensuring a predictable user journey without arbitrary blocks.
30
+ - ⚙️ **WebAssembly & PoW Workers Optimization**: Drastic improvements to the WASM module loading routines and the execution of background threads (`pow.worker.js`). Parallel computations now run more smoothly, strictly adhere to restrictive Content Security Policy (CSP) directives, and benefit from a more robust fallback to the main thread in the absence of support.
31
+ - 🐘 **Native DNS Support in PHP**: Integrated the native `resolveDnsNative` method into the PHP engine for DNS resolution. This method autonomously handles IPv4 (A) and IPv6 (AAAA/PTR) records without external system calls, guaranteeing better performance and increased compatibility with asynchronous environments.
32
+ - 🔑 **Full Transition to Strong Cryptographic Randomness**: Migrated all random generation functions (`random()`, `secureRandomFloat()`) to native API CSPRNGs (`crypto.randomInt` and `crypto.randomBytes`). This permanently eliminates distribution bias (modulo bias) and resolves CodeQL static analysis warnings.
33
+
1
34
  ## Version 0.6.7
2
35
 
3
36
  - 🧬 **WebAssembly (WASM)** : new client version
package/README.md CHANGED
@@ -86,16 +86,12 @@ pip install anonympins-fingerprint
86
86
  ```
87
87
 
88
88
  ### Java
89
- ```bash
90
- mvn clean install
91
- ```
92
-
93
- Then, in your **pom.xml**, add :
89
+ In your **pom.xml**, add :
94
90
  ```xml
95
91
  <dependency>
96
92
  <groupId>com.anonympins</groupId>
97
- <artifactId>fingerprint</artifactId>
98
- <version>0.6.7</version>
93
+ <artifactId>fingerprint-engine</artifactId>
94
+ <version>0.7.1</version>
99
95
  </dependency>
100
96
  ```
101
97
  ## Documentation
package/composer.json CHANGED
@@ -36,5 +36,12 @@
36
36
  "config": {
37
37
  "sort-packages": true
38
38
  },
39
- "minimum-stability": "stable"
39
+ "minimum-stability": "stable",
40
+ "scripts": {
41
+ "build:wordpress": "@php src/php/WordPress/package.php",
42
+ "package:wordpress": "@php src/php/WordPress/package.php"
43
+ },
44
+ "scripts-descriptions": {
45
+ "build:wordpress": "Empaquette et génère le fichier zip du plugin WordPress dans public/"
46
+ }
40
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anonympins/fingerprint",
3
- "version": "0.6.7",
3
+ "version": "0.7.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",
Binary file
package/public/fp.wasm CHANGED
Binary file
@@ -8,13 +8,13 @@ const __filename = fileURLToPath(import.meta.url);
8
8
  const __dirname = dirname(__filename);
9
9
 
10
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é).
11
+ * Attempts to run the build command for the WASM module.
12
+ * Does not fail the build if the command fails (e.g. em++ not found).
13
13
  */
14
14
  function buildWasm() {
15
15
  return new Promise((resolve) => {
16
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=['_malloc','_free','_hash_string','_solve_cpu_target','_solve_memory_challenge','_generate_gpu_pow_trajectory']\" -O3 --no-entry";
17
+ const command = "em++ -msimd128 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=['_malloc','_free','_hash_string','_solve_cpu_target','_solve_memory_challenge','_generate_gpu_pow_trajectory']\" -O3 --no-entry";
18
18
 
19
19
  exec(command, (error, stdout, stderr) => {
20
20
  if (error) {
@@ -22,7 +22,7 @@ function buildWasm() {
22
22
  } else {
23
23
  console.log('WASM module built successfully.');
24
24
  }
25
- resolve(); // Toujours résoudre la promesse pour ne pas bloquer le build principal.
25
+ resolve(); // Always resolve the promise so the main build is not blocked.
26
26
  });
27
27
  });
28
28
  }
@@ -36,14 +36,12 @@ async function buildClientScript() {
36
36
  console.log('Obfuscating client script...');
37
37
  const obfuscationResult = JavaScriptObfuscator.obfuscate(clientScriptContent, {
38
38
  compact: true,
39
- controlFlowFlattening: true, // Aplatit le flux de contrôle
40
- deadCodeInjection: true, // Injecte du code mort
39
+ controlFlowFlattening: true, // Flattens control flow
40
+ deadCodeInjection: true, // Injects dead code
41
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()
42
+ stringArrayRotate: true, // Rotates the string array
43
+ stringArrayShuffle: true, // Shuffles the string array
44
+ // Using a fixed seed yields deterministic obfuscation. For unique builds, use Math.random()
47
45
  seed: Math.random(),
48
46
  selfDefending: true,
49
47
  });
@@ -31,19 +31,19 @@ function getRandomNonTrivialOpcodes(tempLocal) {
31
31
  const insts = [];
32
32
  const ops = [
33
33
  // local.get tempLocal, i32.const rand, rotl, local.set tempLocal
34
- () => [0x20, tempLocal, 0x41, ...encodeSLEB128(Math.floor(Math.random() * 31) + 1), 0x77, 0x21, tempLocal],
34
+ () => [0x20, tempLocal, 0x41, ...encodeSLEB128(crypto.randomInt(1, 32)), 0x77, 0x21, tempLocal],
35
35
  // local.get tempLocal, i32.const rand, rotr, local.set tempLocal
36
- () => [0x20, tempLocal, 0x41, ...encodeSLEB128(Math.floor(Math.random() * 31) + 1), 0x78, 0x21, tempLocal],
36
+ () => [0x20, tempLocal, 0x41, ...encodeSLEB128(crypto.randomInt(1, 32)), 0x78, 0x21, tempLocal],
37
37
  // local.get tempLocal, i32.const rand, xor, local.set tempLocal
38
- () => [0x20, tempLocal, 0x41, ...encodeSLEB128(Math.floor(Math.random() * 10000)), 0x73, 0x21, tempLocal],
38
+ () => [0x20, tempLocal, 0x41, ...encodeSLEB128(crypto.randomInt(0, 10000)), 0x73, 0x21, tempLocal],
39
39
  // local.get tempLocal, popcnt, i32.const rand, mul, local.set tempLocal
40
- () => [0x20, tempLocal, 0x69, 0x41, ...encodeSLEB128(Math.floor(Math.random() * 1000) + 1), 0x6c, 0x21, tempLocal],
40
+ () => [0x20, tempLocal, 0x69, 0x41, ...encodeSLEB128(crypto.randomInt(1, 1001)), 0x6c, 0x21, tempLocal],
41
41
  // local.get tempLocal, clz, local.get tempLocal, ctz, add, local.set tempLocal
42
42
  () => [0x20, tempLocal, 0x67, 0x20, tempLocal, 0x68, 0x6a, 0x21, tempLocal]
43
43
  ];
44
- const count = 2 + Math.floor(Math.random() * 3);
44
+ const count = crypto.randomInt(2, 5);
45
45
  for (let i = 0; i < count; i++) {
46
- const op = ops[Math.floor(Math.random() * ops.length)];
46
+ const op = ops[crypto.randomInt(0, ops.length)];
47
47
  insts.push(...op());
48
48
  }
49
49
  return insts;
@@ -53,7 +53,7 @@ function generatePolymorphicInstructions(stateLocal = 4, tempLocal = 5) {
53
53
  const insts = [];
54
54
 
55
55
  // Initialize tempLocal with a random value
56
- const initVal = Math.floor(Math.random() * 1000) - 500;
56
+ const initVal = crypto.randomInt(-500, 500);
57
57
  insts.push(0x41, ...encodeSLEB128(initVal), 0x21, tempLocal);
58
58
 
59
59
  // Initialize stateLocal to 0
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Algorithme de hachage cyrb53 (rapide et faible taux de collision).
2
+ * cyrb53 hashing algorithm (fast with low collision rate).
3
3
  */
4
- // Exporté pour être utilisé comme fallback par fingerprint.client.js
4
+ // Exported for fallback usage by fingerprint.client.js
5
5
  export const cyrb53 = (str, seed = 0) => {
6
6
  const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
7
7
  let h1 = 0xdeadbeef ^ seed,
@@ -21,25 +21,25 @@ export const cyrb53 = (str, seed = 0) => {
21
21
  };
22
22
 
23
23
  /**
24
- * Classe pour construire une empreinte composite (Multi-Hash).
25
- * Format de sortie : "grp1:hash1|grp2:hash2|grp3:hash3"
24
+ * Class for constructing a composite fingerprint (Multi-Hash).
25
+ * Output format: "grp1:hash1|grp2:hash2|grp3:hash3"
26
26
  */
27
27
  export class FingerprintBuilder {
28
28
  constructor() {
29
- // Le hasher est maintenant une propriété pour pouvoir être surchargé par le client WASM.
29
+ // The hasher is an instance property so it can be overridden by the WASM client.
30
30
  this.components = new Map();
31
- // FIX: Initialiser le hasher par défaut à l'implémentation JS.
31
+ // Default hasher set to the JavaScript cyrb53 implementation.
32
32
  this.hasher = cyrb53;
33
33
  }
34
34
 
35
35
  /**
36
- * Ajoute un composant au hash global.
37
- * @param {string} group - Le nom du groupe (ex: 'hw', 'screen', 'geo')
38
- * @param {string|number|boolean} value - La valeur brute à hasher
36
+ * Adds a component to the global hash.
37
+ * @param {string} group - Component group identifier (e.g. 'hw', 'screen', 'geo').
38
+ * @param {string|number|boolean} value - Raw value to hash.
39
39
  */
40
40
  add(group, value) {
41
41
  if (value === undefined || value === null) return this;
42
- // On hash la valeur individuellement pour l'anonymiser et réduire sa taille
42
+ // Hash the value individually to anonymize and normalize length
43
43
  this.components.set(group, this.hasher(String(value)));
44
44
  return this;
45
45
  }
@@ -57,8 +57,8 @@ export class FingerprintBuilder {
57
57
  }
58
58
 
59
59
  /**
60
- * Affiche les composants actuels dans la console.
61
- * @param {string} [title='FingerprintBuilder Components'] - Un titre pour le log.
60
+ * Prints current components to the console.
61
+ * @param {string} [title='FingerprintBuilder Components'] - Title for output display.
62
62
  */
63
63
  log(title = 'FingerprintBuilder Components') {
64
64
  console.log(`--- ${title} ---`);
@@ -71,12 +71,12 @@ export class FingerprintBuilder {
71
71
  }
72
72
 
73
73
  /**
74
- * Génère la chaîne de signature finale.
75
- * Trie les clés pour garantir un ordre déterministe.
74
+ * Produces the final serialized signature string.
75
+ * Keys are sorted to guarantee deterministic serialization.
76
76
  */
77
77
  toString() {
78
78
  return Array.from(this.components.entries())
79
- .sort((a, b) => a[0].localeCompare(b[0])) // Tri alphabétique des clés
79
+ .sort((a, b) => a[0].localeCompare(b[0])) // Alphabetical key ordering
80
80
  .map(([key, hash]) => `${key}:${hash}`)
81
81
  .join("|");
82
82
  }
@@ -118,30 +118,29 @@ export class FingerprintBuilder {
118
118
  'x_forwarded_for', 'x_real_ip', 'cf_connecting_ip'
119
119
  ]);
120
120
 
121
- // Poids de "véracité" (Entropie/Stabilité)
122
- // Les poids sont augmentés pour donner plus d'importance aux signaux forts.
121
+ // Entropy / stability reliability weights
122
+ // Strongly invariant signals receive higher weighting
123
123
  const weights = {
124
- // --- Signaux très forts (difficiles à usurper) ---
125
- cvs: 5.0, // Canvas: Très haute entropie (Rendu unique du GPU/driver)
126
- gpu: 4.0, // GPU: Haute entropie (Matériel spécifique)
127
- ja3: 3.5, // JA3: Identifie la librairie TLS (très stable pour un client donné)
128
- ja4: 4.0, // JA4: Plus moderne, inclut HTTP/2
124
+ // --- High-entropy invariants (tamper-resistant) ---
125
+ cvs: 5.0, // Canvas: High entropy (GPU/driver rendering quirks)
126
+ gpu: 4.0, // GPU: High entropy (Physical graphics chipset)
127
+ ja3: 3.5, // JA3: TLS client stack fingerprint
128
+ ja4: 4.0, // JA4: Modern TLS fingerprint including HTTP/2
129
129
  h2: 3.0, // HTTP/2 settings frame fingerprint
130
130
  tcp: 2.5, // TCP/IP fingerprint
131
- ua: 2.0, // User-Agent: Signal fort, bien que modifiable
131
+ ua: 2.0, // User-Agent string
132
132
 
133
- // --- Signaux composites et dérivés ---
134
- client_fp_hash: 3.0, // Le hash de l'empreinte client est un signal très fort.
135
- browser: 1.5, // Le navigateur extrait du UA.
136
- os_version: 1.5, // L'OS extrait du UA.
137
- device_type: 1.0, // Le type d'appareil extrait du UA.
138
-
139
- // --- Signaux moyens ---
140
- hw: 1.5, // Hardware (CPU, RAM): Stabilité moyenne
141
- scr: 1.0, // Screen: Stabilité moyenne
142
- // 'os' est souvent la même chose que 'ch_platform', on peut le déprécier ou lui donner un poids faible.
143
- os: 0.8, // OS (nav.platform): Assez stable
144
- geo: 0.5, // Geo/Langue: Peut changer (VPN, voyage)
133
+ // --- Derived / composite signals ---
134
+ client_fp_hash: 3.0, // Client-side fingerprint hash
135
+ browser: 1.5, // Parsed browser family
136
+ os_version: 1.5, // Parsed OS family
137
+ device_type: 1.0, // Device form factor
138
+
139
+ // --- Medium-stability signals ---
140
+ hw: 1.5, // Hardware (cores, RAM): Moderate stability
141
+ scr: 1.0, // Screen geometry: Moderate stability
142
+ os: 0.8, // OS (nav.platform)
143
+ geo: 0.5, // Geo/locale: Subject to VPNs and travel
145
144
  };
146
145
 
147
146
  let weightedMatches = 0;
@@ -150,16 +149,16 @@ export class FingerprintBuilder {
150
149
  const allKeys = new Set([...map1.keys(), ...map2.keys()]);
151
150
 
152
151
  allKeys.forEach((key) => {
153
- // On ignore les clés volatiles pour cette comparaison spécifique.
152
+ // Ignore volatile keys during comparative evaluation
154
153
  if (volatileKeys.has(key)) {
155
154
  return;
156
155
  }
157
156
 
158
157
  const weight = weights[key] ?? 0;
159
- // On ne compte une clé dans le poids total que si elle est présente dans au moins une des deux empreintes.
158
+ // Only tally total weight if key is present in at least one fingerprint
160
159
  if (!map1.has(key) && !map2.has(key)) return;
161
160
 
162
- totalWeight += weight; // N'incrémenter que si la clé est pertinente.
161
+ totalWeight += weight;
163
162
  if (map1.get(key) === map2.get(key)) {
164
163
  weightedMatches += weight;
165
164
  }