@anonympins/fingerprint 0.3.1 → 0.3.2

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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/anonympins/fingerprint)
6
6
  [![Watchers](https://img.shields.io/github/watchers/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/watchers)
7
7
 
8
- An HTTP(S) client mitigation and anti-bot protection library for Node.js/Express, based on digital fingerprinting and dynamic Proof-of-Work (PoW) challenges.
8
+ An HTTP(S) client mitigation and anti-bot protection library for both PHP and Node.js/Express, based on digital fingerprinting and dynamic Proof-of-Work (PoW) challenges.
9
9
 
10
10
  ## How It Works
11
11
 
@@ -47,16 +47,15 @@ For API clients, the challenge is delivered as a `404` JSON response, and the cl
47
47
  - **Timing Attack Protection**: Uses `crypto.timingSafeEqual` for secure validation of tickets and other signatures.
48
48
  - **Bot Whitelisting**: Includes a DNS-based verification mechanism to reliably identify and whitelist legitimate crawlers like Googlebot and Bingbot, preventing them from being challenged. The results are cached for optimal performance.
49
49
  - **Automatic Parameter Tuning**: Includes a genetic algorithm-based optimizer (`startThresholdAutoTuning`) that analyzes real traffic to dynamically adjust suspicion thresholds, weights, and behavioral pattern detection parameters, improving accuracy and reducing false positives over time. The tuner is hardened against data poisoning attempts.
50
+ - **Optional WASM Acceleration**: The client-side library can be accelerated with a WebAssembly module for high-performance hashing. The build process handles this optionally, and the client gracefully falls back to a pure JavaScript implementation if WASM is unavailable.
50
51
  - **Hardened Security**: Protects against various attacks, including DoS via memory exhaustion, invalid nonce submission, and uses cryptographically secure randomness for all sensitive operations.
51
52
 
52
53
  ## Installation and Usage
53
54
 
54
55
  This library is available for both **Node.js** and **PHP**.
55
56
 
56
- * [Node.js (Express) Quickstart](#nodejs-express)
57
- * [PHP Quickstart](#php)
58
- * [PHP Quickstart (Direct Integration)](#php-quickstart-direct-integration)
59
- * [PHP Quickstart (PSR-15 Middleware)](#php-quickstart-psr-15-middleware)
57
+ * [PHP Quickstart](#php-quickstart)
58
+ * [Node.js Quickstart](#nodejs-quickstart)
60
59
 
61
60
  ### Prerequisites
62
61
 
@@ -66,7 +65,7 @@ This library is available for both **Node.js** and **PHP**.
66
65
 
67
66
  ---
68
67
 
69
- ### Prerequisites
68
+ <a id="php-quickstart"></a>
70
69
 
71
70
  ## PHP Quickstart (Direct Integration)
72
71
 
@@ -114,6 +113,11 @@ $securityConfig = SecurityProfiles::createSecurityProfile('balanced', [
114
113
  'verbose' => true,
115
114
  ]);
116
115
 
116
+ // IMPORTANT: For PHP environments, TLS fingerprinting (JA3/JA4) requires a reverse proxy
117
+ // (like Nginx, HAProxy, or a cloud load balancer) to inspect the TLS handshake and
118
+ // pass the fingerprint hashes to the PHP application via HTTP headers
119
+ // (e.g., `X-JA3-Hash`, `X-JA4-Hash`).
120
+
117
121
  // 2. Create an instance of the DirectFingerprint protector.
118
122
  $protector = new DirectFingerprint($securityConfig);
119
123
 
@@ -134,13 +138,14 @@ echo "<p>Your suspicion score was: " . round($score, 2) . "</p>";
134
138
  ?>
135
139
  ```
136
140
 
137
- ### [NodeJS Configuration](nodejs-express)
141
+ <a id="nodejs-quickstart"></a>
142
+ ## NodeJS Configuration
138
143
 
139
- #### Prerequisites for Node.js
144
+ ### Prerequisites for Node.js
140
145
 
141
146
  Ensure you have middleware for parsing cookies (like `cookie-parser`) and request bodies (like `express.json` and `express.urlencoded`) set up in your Express application *before* the `powMiddleware`.
142
147
 
143
- #### Configuration
148
+ ### Configuration
144
149
  Define a secret key for signing PoW tickets in your environment variables.
145
150
 
146
151
  ```bash
@@ -255,7 +260,7 @@ const securityConfig = {
255
260
  },
256
261
  cpu: {
257
262
  minDifficultyBits: 8,
258
- maxDifficultyBits: 24,
263
+ maxDifficultyBits: 32,
259
264
  },
260
265
  // (Optional) Configure the duration (in milliseconds) for various temporary data.
261
266
  ticketMaxAge: 3600000, // 1 hour. Duration for which a solved challenge ticket is valid.
@@ -361,6 +366,10 @@ const securityConfig = {
361
366
  enableUsefulWork: true,
362
367
  usefulWorkConfigPath: './path/to/your/problems.config.json' // (Optional) Path to the useful work configuration.
363
368
  };
369
+
370
+
371
+ // Create an instance of the middleware with your security configuration.
372
+ const powMiddlewareInstance = powMiddleware(securityConfig);
364
373
  ```
365
374
 
366
375
 
@@ -618,6 +627,11 @@ initializeClient({
618
627
  // (Optional) An array of `name` attributes for hidden form fields that act as bot traps.
619
628
  honeypots: ['email_confirm', 'user_nickname', 'website_url'],
620
629
 
630
+ // (Optional) Path to the WebAssembly loader script (`fp.js`) for accelerated hashing.
631
+ // If provided, the client will attempt to load the WASM module. If it fails or is not available,
632
+ // it will gracefully fall back to the pure JavaScript implementation.
633
+ wasmPath: '/fp.js',
634
+
621
635
  // (Optional) Enables automatic protection for `fetch` requests.
622
636
  // If the `fetch` object is present, the protection is active.
623
637
  fetch: {
@@ -743,6 +757,11 @@ app.get('/api/problems/solutions', (req, res) => {
743
757
 
744
758
  ```
745
759
 
760
+ #### `getBestTuningSolution()`
761
+
762
+ Returns the last best solution object found by the auto-tuner. This is particularly useful for "FinOps" or for auditing the tuner's performance, as it allows you to log the exact configuration that the genetic algorithm identified as optimal.
763
+
764
+ * **Returns**: (`object|null`) The best solution object `{ solution, objectives }` or `null` if no tuning cycle has completed yet. The `solution` property contains the optimized `weights`, `thresholds`, and `patterns`, while `objectives` contains the performance scores (e.g., false positive/negative rates) for that solution.
746
765
 
747
766
  #### `problemManager.updateProblemPayload(problemId, newPayload)`
748
767
 
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Algorithme de hachage cyrb53 (rapide et faible taux de collision).
3
3
  */
4
- export const cyrb53 = (str, seed = 0) => {
4
+ // Exporté pour être utilisé comme fallback par fingerprint.client.js
5
+ export const cyrb53 = (str, seed = 0) => {
5
6
  let h1 = 0xdeadbeef ^ seed,
6
7
  h2 = 0x41c6ce57 ^ seed;
7
8
  for (let i = 0, ch; i < str.length; i++) {
@@ -24,7 +25,10 @@ export const cyrb53 = (str, seed = 0) => {
24
25
  */
25
26
  export class FingerprintBuilder {
26
27
  constructor() {
28
+ // Le hasher est maintenant une propriété pour pouvoir être surchargé par le client WASM.
27
29
  this.components = new Map();
30
+ // FIX: Initialiser le hasher par défaut à l'implémentation JS.
31
+ this.hasher = cyrb53;
28
32
  }
29
33
 
30
34
  /**
@@ -35,7 +39,7 @@ export class FingerprintBuilder {
35
39
  add(group, value) {
36
40
  if (value === undefined || value === null) return this;
37
41
  // On hash la valeur individuellement pour l'anonymiser et réduire sa taille
38
- this.components.set(group, cyrb53(String(value)));
42
+ this.components.set(group, this.hasher(String(value)));
39
43
  return this;
40
44
  }
41
45
 
@@ -1,9 +1,19 @@
1
- import { cyrb53, FingerprintBuilder } from './fingerprint.builder.js';
1
+ import { cyrb53 as jsCyrb53, FingerprintBuilder } from './fingerprint.builder.js';
2
2
  import { solveChallenge } from './pow.solver.js';
3
3
 
4
+ // Variable pour stocker la fonction de hachage active.
5
+ // Par défaut, c'est l'implémentation JavaScript.
6
+ let activeCyrb53 = jsCyrb53;
7
+
4
8
  const ClientLibrary = {
5
9
  // Cache pour éviter de recalculer les constantes (Hardware, etc.)
6
10
  _cachedBuilder: null,
11
+ /**
12
+ * Wrapper interne pour la fonction de hachage.
13
+ * @private
14
+ */
15
+ _hasher: (str, seed) => activeCyrb53(str, seed),
16
+
7
17
  /**
8
18
  * Génère l'empreinte de l'appareil actuel.
9
19
  */
@@ -12,7 +22,7 @@ const ClientLibrary = {
12
22
  console.error("getDeviceFingerprint can only be called on the client-side.");
13
23
  return "";
14
24
  }
15
-
25
+
16
26
  if (!this._cachedBuilder) {
17
27
  const nav = window.navigator;
18
28
  const screen = window.screen;
@@ -21,7 +31,7 @@ const ClientLibrary = {
21
31
 
22
32
  // 1. Hardware (Très stable) : Cœurs, RAM, GPU (si dispo via canvas), Touch
23
33
  this._cachedBuilder.add(
24
- "hw",
34
+ "hw", // Utilise maintenant le hasher actif
25
35
  `${nav.hardwareConcurrency}_${nav.deviceMemory}_${nav.maxTouchPoints}`,
26
36
  );
27
37
 
@@ -111,7 +121,7 @@ const ClientLibrary = {
111
121
  .sort()
112
122
  .map((k) => `${k}=${payload[k]}`)
113
123
  .join("&");
114
- const payloadHash = cyrb53(sortedPayload);
124
+ const payloadHash = this._hasher(sortedPayload);
115
125
  return `${deviceFp}|req:${payloadHash}`;
116
126
  },
117
127
 
@@ -138,6 +148,8 @@ const ClientLibrary = {
138
148
  * Resets the cached fingerprint builder. Used for testing purposes.
139
149
  */
140
150
  _resetCache() {
151
+ // Réinitialise le hasher à l'implémentation JS par défaut.
152
+ activeCyrb53 = jsCyrb53;
141
153
  this._cachedBuilder = null;
142
154
  },
143
155
 
@@ -345,7 +357,7 @@ const ClientLibrary = {
345
357
  };
346
358
 
347
359
  this.addFetchInterceptor(fingerprintInterceptor);
348
- },
360
+ }, // <-- VIRGULE AJOUTÉE ICI
349
361
 
350
362
  /**
351
363
  * Intercepte une réponse de challenge JSON, le résout, et réessaie la requête.
@@ -388,18 +400,11 @@ const ClientLibrary = {
388
400
  console.error('[Fingerprint] Failed to solve or retry challenge:', e);
389
401
  return response; // Retourne la réponse 429 originale en cas d'échec
390
402
  }
391
- },
392
- /**
393
- * @typedef {object} ClientConfig
394
- * @property {boolean} [mouse=true] - Activer le suivi de l'entropie de la souris.
395
- * @property {boolean} [keystrokes=true] - Activer le suivi de la dynamique de frappe.
396
- * @property {string[]} [honeypots] - Noms des champs de formulaire honeypot à initialiser.
397
- * @property {object} [fetch] - Configuration pour l'interception de fetch.
398
- * @property {string[]} [fetch.targetDomains] - Domaines à protéger. Si non fourni, protège les requêtes de même origine.
399
- */
403
+ }, // <-- VIRGULE AJOUTÉE ICI
400
404
 
401
405
  /**
402
406
  * Initialise toutes les protections côté client en une seule fois.
407
+ * Tente également de charger le module WASM si `wasmPath` est fourni.
403
408
  * C'est la méthode d'initialisation recommandée.
404
409
  * @param {ClientConfig} [config={}] - L'objet de configuration.
405
410
  */
@@ -408,9 +413,15 @@ const ClientLibrary = {
408
413
  mouse = true,
409
414
  keystrokes = true,
410
415
  honeypots = [],
411
- fetch: fetchConfig = {},
416
+ wasmPath, // Nouveau paramètre
417
+ fetch: fetchConfig = {}
412
418
  } = config;
413
419
 
420
+ // Tentative de chargement du WASM si le chemin est fourni
421
+ if (wasmPath) {
422
+ this.initializeWasm(wasmPath);
423
+ }
424
+
414
425
  if (mouse) {
415
426
  this.startMouseEntropyTracker();
416
427
  }
@@ -426,14 +437,57 @@ const ClientLibrary = {
426
437
 
427
438
  // Ajoute l'intercepteur pour la résolution de challenge
428
439
  if (fetchConfig.handleChallenges !== false) {
429
- this.addFetchInterceptor(async (resource, options, next) => {
430
- const originalResponse = await next(resource, options);
431
- // On clone la réponse pour que la lecture du corps par solveChallengeAndRetry ne la consomme pas pour l'appelant original.
432
- return this.solveChallengeAndRetry(originalResponse.clone(), resource, options);
433
- });
440
+ this.addFetchInterceptor(async (resource, options, next) => {
441
+ const originalResponse = await next(resource, options);
442
+ // On clone la réponse pour que la lecture du corps par solveChallengeAndRetry ne la consomme pas pour l'appelant original.
443
+ return this.solveChallengeAndRetry(originalResponse.clone(), resource, options);
444
+ });
445
+ }
446
+ }
447
+ },
448
+
449
+ /**
450
+ * Tente de charger et d'initialiser le module WebAssembly pour un hachage plus rapide.
451
+ * Si le chargement échoue, il se rabat silencieusement sur l'implémentation JS.
452
+ * @param {string} wasmPath - Le chemin vers le script de chargement du module WASM (ex: '/fp.js').
453
+ */
454
+ async initializeWasm(wasmPath) {
455
+ try {
456
+ // 1. Injecter le script qui charge le module WASM
457
+ const script = document.createElement('script');
458
+ script.src = wasmPath;
459
+ await new Promise((resolve, reject) => {
460
+ script.onload = resolve;
461
+ script.onerror = reject;
462
+ document.head.appendChild(script);
463
+ });
464
+
465
+ // 2. Attendre que la fonction globale `createFingerprintModule` soit disponible
466
+ if (typeof window.createFingerprintModule !== 'function') {
467
+ throw new Error('WASM loader script did not expose createFingerprintModule.');
468
+ }
469
+
470
+ // 3. Initialiser le module
471
+ const wasmModule = await window.createFingerprintModule();
472
+ if (typeof wasmModule._hash_string !== 'function') {
473
+ throw new Error('WASM module did not export _hash_string.');
474
+ }
475
+
476
+ // 4. Remplacer la fonction de hachage par la version WASM
477
+ activeCyrb53 = (str) => {
478
+ // La fonction C++ attend un pointeur, Emscripten gère la conversion
479
+ return wasmModule._hash_string(str);
480
+ };
481
+
482
+ console.log('[Fingerprint] WASM module loaded successfully. Using fast hashing.');
483
+ // NOUVEAU: Ajoute un indicateur à l'empreinte pour que le serveur sache que le WASM est actif.
484
+ if (this._cachedBuilder) {
485
+ this._cachedBuilder.addRaw('wasm', 'true');
486
+ }
487
+ } catch (error) {
488
+ console.warn('[Fingerprint] WASM module failed to load. Falling back to JS implementation. Error:', error);
434
489
  }
435
490
  }
436
- }
437
491
  };
438
492
 
439
493
  /**
@@ -477,6 +531,7 @@ export const addFetchInterceptor = ClientLibrary.addFetchInterceptor.bind(Client
477
531
  export const patchGlobalFetch = ClientLibrary.patchGlobalFetch.bind(ClientLibrary);
478
532
  export const initializeFetch = ClientLibrary.initializeFetch.bind(ClientLibrary);
479
533
  export const initializeClient = ClientLibrary.initializeClient.bind(ClientLibrary);
534
+ export const initializeWasm = ClientLibrary.initializeWasm.bind(ClientLibrary);
480
535
  export const solveChallengeAndRetry = ClientLibrary.solveChallengeAndRetry.bind(ClientLibrary);
481
536
 
482
537
  // Export the internal object for testing purposes
package/fingerprint.js CHANGED
@@ -235,6 +235,38 @@ const getPowSolverCode = () => {
235
235
  return readFileSync(solverPath, 'utf-8');
236
236
  };
237
237
 
238
+ /**
239
+ * @private
240
+ * A mapping of IANA cipher suite names (as used by Node.js) to their decimal IDs.
241
+ * This is essential for correct JA3 fingerprint calculation.
242
+ * The list is not exhaustive but covers the most common cipher suites.
243
+ */
244
+ const cipherSuiteMap = {
245
+ 'TLS_AES_128_GCM_SHA256': 4865,
246
+ 'TLS_AES_256_GCM_SHA384': 4866,
247
+ 'TLS_CHACHA20_POLY1305_SHA256': 4867,
248
+ 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256': 49195,
249
+ 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256': 49199,
250
+ 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384': 49196,
251
+ 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384': 49200,
252
+ 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256': 52393,
253
+ 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256': 52392,
254
+ 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA': 49171,
255
+ 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA': 49172,
256
+ 'TLS_RSA_WITH_AES_128_GCM_SHA256': 156,
257
+ 'TLS_RSA_WITH_AES_256_GCM_SHA384': 157,
258
+ 'TLS_RSA_WITH_AES_128_CBC_SHA': 47,
259
+ 'TLS_RSA_WITH_AES_256_CBC_SHA': 53,
260
+ // Older/Less common suites
261
+ 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA': 49161,
262
+ 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA': 49162,
263
+ 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256': 158,
264
+ 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384': 159,
265
+ 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA': 51,
266
+ 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA': 57,
267
+ 'TLS_RSA_WITH_3DES_EDE_CBC_SHA': 10,
268
+ };
269
+
238
270
  /**
239
271
  * Extracts TLS fingerprints (JA3 and JA4) from request context.
240
272
  * Prioritizes headers from reverse proxies (x-ja4-hash) and falls back to JA3 calculation
@@ -265,15 +297,19 @@ function getTlsFingerprint(context) {
265
297
 
266
298
  // The official JA3 spec includes the TLS version.
267
299
  // Node.js provides it as a string like 'TLSv1.3', we need the corresponding decimal value.
268
- const tlsVersionMap = {
300
+ const tlsVersionMap = { // NOSONAR
269
301
  'TLSv1': 769, 'TLSv1.1': 770, 'TLSv1.2': 771, 'TLSv1.3': 772
270
302
  };
271
303
  const tlsVersionId = tlsVersionMap[version] || 0;
272
304
 
305
+ // Convert cipher suite names to their decimal IDs.
306
+ const cipherIds = Array.isArray(ciphers)
307
+ ? ciphers.map(c => cipherSuiteMap[c.name] || c).join('-') // Use the raw ID if name is not in map
308
+ : '';
309
+
273
310
  const ja3String = [
274
311
  tlsVersionId,
275
- // The ciphers array from clientHello is an array of objects, not just IDs.
276
- Array.isArray(ciphers) ? ciphers.join('-') : '',
312
+ cipherIds,
277
313
  extensions?.join('-') || '',
278
314
  ellipticCurves?.join('-') || '',
279
315
  ellipticCurvePointFormats?.join('-') || ''
@@ -906,7 +942,7 @@ function getHoneypotScore(context, honeypotConfig = {}) {
906
942
  */
907
943
  const injectionPatterns = {
908
944
  // SQL/NoSQL injections, including time-based attacks
909
- sql: /(\$ne|' *OR *'1'='1|['";]\s*--|; ?(DROP|TRUNCATE|DELETE)|UNION SELECT|SLEEP\(|BENCHMARK\(|WAITFOR DELAY)/i,
945
+ sql: /(\$ne|\' *OR *\'1\'=\'1|['";]\s*--|; ?(DROP|TRUNCATE|DELETE)|UNION SELECT|(?:SLEEP|BENCHMARK)\s*\(|WAITFOR DELAY)/i,
910
946
  // Log4Shell (JNDI injection)
911
947
  log4shell: /\$\{jndi:(ldap|rmi|dns):/i,
912
948
  // Server-Side Template Injection (SSTI) for engines like Jinja2, Twig, etc.
@@ -983,7 +1019,7 @@ function getBehaviorScore(context) {
983
1019
  if (keystrokeDeviation > 0.15) score += 40;
984
1020
  }
985
1021
 
986
- return { behaviorScore: Math.max(0, Math.min(100, score)) }; // Assure que le score reste entre 0 et 100
1022
+ return { behaviorScore: Math.min(100, score) }; // Assure que le score ne dépasse pas 100, mais peut être négatif (bonus)
987
1023
  } catch (e) {
988
1024
  return { behaviorScore: 10 }; // En-tête malformé = légèrement suspect.
989
1025
  }
@@ -3093,6 +3129,7 @@ export const __internal = {
3093
3129
  // --- THRESHOLD AUTO-TUNING SECTION ---
3094
3130
 
3095
3131
  let autoTuningJobId = null;
3132
+ let lastBestSolution = null; // NOUVEAU: Stocke la meilleure solution trouvée
3096
3133
 
3097
3134
  /**
3098
3135
  * Executes a threshold optimization pass using collected traffic data.
@@ -3151,30 +3188,47 @@ function runThresholdOptimization(securityConfig, trafficData, minDataPoints, ma
3151
3188
 
3152
3189
  /**
3153
3190
  * Met à jour un objet de configuration (ex: thresholds, weights) en douceur.
3191
+ * Cette nouvelle version préserve la proportionnalité des valeurs initiales.
3154
3192
  * @param {object} currentConfig - La configuration actuelle à modifier.
3155
3193
  * @param {object} targetConfig - La configuration cible proposée par l'optimiseur.
3156
3194
  */
3157
3195
  const applyInertialUpdate = (currentConfig, targetConfig) => {
3158
- if (!currentConfig || !targetConfig) return; // Vérifier aussi currentConfig
3159
- for (const key in targetConfig) {
3160
- if (Object.hasOwnProperty.call(currentConfig, key)) {
3161
- const currentValue = currentConfig[key];
3162
- const targetValue = targetConfig[key];
3163
- const delta = targetValue - currentValue;
3164
- const maxChange = Math.abs(currentValue * MAX_CHANGE_VELOCITY);
3165
-
3166
- // Limite le changement à la vélocité maximale
3167
- const change = Math.max(-maxChange, Math.min(maxChange, delta));
3168
-
3169
- currentConfig[key] += change;
3170
- }
3196
+ if (!currentConfig || !targetConfig) return; // Vérifier aussi currentConfig
3197
+
3198
+ // --- NOUVELLE LOGIQUE PROPORTIONNELLE ---
3199
+ let totalCurrentWeight = 0;
3200
+ let totalTargetWeight = 0;
3201
+
3202
+ // 1. Calculer la somme des poids actuels et cibles pour les clés communes.
3203
+ for (const key in currentConfig) {
3204
+ if (Object.hasOwnProperty.call(targetConfig, key)) {
3205
+ totalCurrentWeight += currentConfig[key];
3206
+ totalTargetWeight += targetConfig[key];
3171
3207
  }
3172
- };
3208
+ }
3173
3209
 
3210
+ if (totalCurrentWeight === 0) return; // Éviter la division par zéro
3211
+
3212
+ // 2. Déterminer le ratio de changement global et le limiter par la vélocité.
3213
+ // Cela crée un "facteur d'ajustement" unique pour l'ensemble de la configuration.
3214
+ const globalChangeRatio = (totalTargetWeight - totalCurrentWeight) / totalCurrentWeight;
3215
+ const adjustmentFactor = Math.max(-MAX_CHANGE_VELOCITY, Math.min(MAX_CHANGE_VELOCITY, globalChangeRatio));
3216
+
3217
+ // 3. Appliquer ce facteur à chaque valeur de la configuration actuelle.
3218
+ // Cela fait "glisser" l'ensemble de la configuration tout en préservant les proportions.
3219
+ for (const key in currentConfig) {
3220
+ if (Object.hasOwnProperty.call(targetConfig, key)) {
3221
+ currentConfig[key] *= (1 + adjustmentFactor);
3222
+ }
3223
+ }
3224
+ };
3174
3225
  applyInertialUpdate(securityConfig.thresholds, newConfig.thresholds);
3175
3226
  applyInertialUpdate(securityConfig.weights, newConfig.weights);
3176
3227
  applyInertialUpdate(securityConfig.patterns, newConfig.patterns);
3177
3228
 
3229
+ // NOUVEAU: Stocker la meilleure solution pour une consultation externe
3230
+ lastBestSolution = bestSolution;
3231
+
3178
3232
  console.log("[AutoTuning] Nouvelle configuration de sécurité optimisée appliquée.");
3179
3233
  console.log("[AutoTuning] Objectifs atteints :", { falsePositiveRate: bestSolution.objectives[0].toFixed(4), falseNegativeRate: bestSolution.objectives[1].toFixed(4) });
3180
3234
  console.log("[AutoTuning] Nouveaux seuils :", securityConfig.thresholds);
@@ -3228,3 +3282,13 @@ export function stopThresholdAutoTuning() {
3228
3282
  console.log("[AutoTuning] Job d'optimisation des seuils arrêté.");
3229
3283
  }
3230
3284
  }
3285
+
3286
+ /**
3287
+ * Returns the last best solution found by the auto-tuner.
3288
+ * This is useful for logging or creating a "finops" security configuration.
3289
+ * @export
3290
+ * @returns {object|null} The best solution object { solution, objectives } or null if no tuning has run.
3291
+ */
3292
+ export function getBestTuningSolution() {
3293
+ return lastBestSolution;
3294
+ }
package/library.js CHANGED
@@ -1635,6 +1635,8 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1635
1635
  behaviorScore: secureRandom(),
1636
1636
  crossLayerInconsistencyScore: secureRandom(),
1637
1637
  timeInconsistencyScore: secureRandom(),
1638
+ tlsSpoofingScore: secureRandom(), // NOUVEAU: Ajout du poids pour le spoofing TLS
1639
+ botScore: secureRandom(), // NOUVEAU: Ajout du poids pour la détection de bot
1638
1640
  },
1639
1641
  patterns: {
1640
1642
  velocityThreshold: 100 + secureRandom() * 400, // 100-500ms
package/package.json CHANGED
@@ -1,94 +1,95 @@
1
- {
2
- "name": "@anonympins/fingerprint",
3
- "version": "0.3.1",
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
- "main": "fingerprint.js",
6
- "type": "module",
7
- "engines": {
8
- "node": ">=20.0.0"
9
- },
10
- "scripts": {
11
- "test": "vitest run --reporter=verbose",
12
- "build": "npm run build:client",
13
- "build:client": "node build-client.js"
14
- },
15
- "files": [
16
- "fingerprint.js",
17
- "fingerprint.client.js",
18
- "fingerprint.client.obfuscated.js",
19
- "fingerprint.builder.js",
20
- "pow.solver.js",
21
- "pow.worker.js",
22
- "pow.solver.inline.js",
23
- "problem-manager.js",
24
- "optimization.worker.js",
25
- "library.js",
26
- "redis-store.js",
27
- "mongodb-store.js",
28
- "sql-store.js",
29
- "README.md",
30
- "LICENSE"
31
- ],
32
- "repository": {
33
- "type": "git",
34
- "url": "git+https://github.com/anonympins/fingerprint.git"
35
- },
36
- "keywords": [
37
- "anti-bot",
38
- "bot-detection",
39
- "security",
40
- "middleware",
41
- "express",
42
- "nodejs",
43
- "fingerprint",
44
- "device-fingerprinting",
45
- "ja3",
46
- "tls-fingerprinting",
47
- "proof-of-work",
48
- "pow",
49
- "useful-proof-of-work",
50
- "upow",
51
- "waf",
52
- "scraping",
53
- "scalping",
54
- "mitigation",
55
- "rate-limiting",
56
- "honeypot",
57
- "behavioral-analysis"
58
- ],
59
- "author": "anonympins",
60
- "license": "MIT",
61
- "bugs": {
62
- "url": "https://github.com/anonympins/fingerprint/issues"
63
- },
64
- "homepage": "https://github.com/anonympins/fingerprint#readme",
65
- "devDependencies": {
66
- "body-parser": "^1.20.2",
67
- "cookie-parser": "^1.4.6",
68
- "express": "^4.18.2",
69
- "prom-client": "^15.1.2",
70
- "vitest": "^4.1.11",
71
- "javascript-obfuscator": "^4.1.0",
72
- "terser": "^5.30.3"
73
- },
74
- "peerDependencies": {
75
- "ioredis": "^5.3.2",
76
- "knex": ">=3.0.0",
77
- "mongodb": "^6.3.0",
78
- "sqlite3": "^5.1.7"
79
- },
80
- "peerDependenciesMeta": {
81
- "ioredis": {
82
- "optional": true
83
- },
84
- "mongodb": {
85
- "optional": true
86
- },
87
- "knex": {
88
- "optional": true
89
- },
90
- "sqlite3": {
91
- "optional": true
92
- }
93
- }
94
- }
1
+ {
2
+ "name": "@anonympins/fingerprint",
3
+ "version": "0.3.2",
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
+ "main": "fingerprint.js",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=20.0.0"
9
+ },
10
+ "scripts": {
11
+ "test": "vitest run --reporter=verbose",
12
+ "build": "node build-client.js"
13
+ },
14
+ "files": [
15
+ "fingerprint.js",
16
+ "fingerprint.client.js",
17
+ "fingerprint.client.obfuscated.js",
18
+ "fingerprint.builder.js",
19
+ "pow.solver.js",
20
+ "pow.worker.js",
21
+ "pow.solver.inline.js",
22
+ "problem-manager.js",
23
+ "optimization.worker.js",
24
+ "library.js",
25
+ "redis-store.js",
26
+ "mongodb-store.js",
27
+ "sql-store.js",
28
+ "public/fp.wasm",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/anonympins/fingerprint.git"
35
+ },
36
+ "keywords": [
37
+ "anti-bot",
38
+ "bot-detection",
39
+ "security",
40
+ "middleware",
41
+ "express",
42
+ "nodejs",
43
+ "fingerprint",
44
+ "device-fingerprinting",
45
+ "ja3",
46
+ "tls-fingerprinting",
47
+ "proof-of-work",
48
+ "pow",
49
+ "useful-proof-of-work",
50
+ "upow",
51
+ "waf",
52
+ "scraping",
53
+ "scalping",
54
+ "mitigation",
55
+ "rate-limiting",
56
+ "honeypot",
57
+ "behavioral-analysis"
58
+ ],
59
+ "author": "anonympins",
60
+ "license": "MIT",
61
+ "bugs": {
62
+ "url": "https://github.com/anonympins/fingerprint/issues"
63
+ },
64
+ "homepage": "https://github.com/anonympins/fingerprint#readme",
65
+ "devDependencies": {
66
+ "body-parser": "^1.20.2",
67
+ "cookie-parser": "^1.4.6",
68
+ "express": "^4.18.2",
69
+ "prom-client": "^15.1.2",
70
+ "vitest": "^4.1.11",
71
+ "javascript-obfuscator": "^4.1.0",
72
+ "terser": "^5.30.3",
73
+ "jsdom": "^24.0.0"
74
+ },
75
+ "peerDependencies": {
76
+ "ioredis": "^5.3.2",
77
+ "knex": ">=3.0.0",
78
+ "mongodb": "^6.3.0",
79
+ "sqlite3": "^5.1.7"
80
+ },
81
+ "peerDependenciesMeta": {
82
+ "ioredis": {
83
+ "optional": true
84
+ },
85
+ "mongodb": {
86
+ "optional": true
87
+ },
88
+ "knex": {
89
+ "optional": true
90
+ },
91
+ "sqlite3": {
92
+ "optional": true
93
+ }
94
+ }
95
+ }
package/public/fp.wasm ADDED
Binary file