@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 +29 -10
- package/fingerprint.builder.js +6 -2
- package/fingerprint.client.js +76 -21
- package/fingerprint.js +83 -19
- package/library.js +2 -0
- package/package.json +95 -94
- package/public/fp.wasm +0 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|

|
|
6
6
|
[](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
|
-
* [
|
|
57
|
-
* [
|
|
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
|
-
|
|
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
|
-
|
|
141
|
+
<a id="nodejs-quickstart"></a>
|
|
142
|
+
## NodeJS Configuration
|
|
138
143
|
|
|
139
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
package/fingerprint.builder.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Algorithme de hachage cyrb53 (rapide et faible taux de collision).
|
|
3
3
|
*/
|
|
4
|
-
|
|
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,
|
|
42
|
+
this.components.set(group, this.hasher(String(value)));
|
|
39
43
|
return this;
|
|
40
44
|
}
|
|
41
45
|
|
package/fingerprint.client.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
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.
|
|
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": "
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"fingerprint.js",
|
|
17
|
-
"fingerprint.client.js",
|
|
18
|
-
"fingerprint.
|
|
19
|
-
"
|
|
20
|
-
"pow.
|
|
21
|
-
"pow.
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|