@anonympins/fingerprint 0.7.4 → 0.8.0

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 (64) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/public/anonympins-bot-mitigation-pow.zip +0 -0
  5. package/src/js/dynamic-wasm.js +218 -218
  6. package/src/js/fingerprint.client.js +1784 -1738
  7. package/src/js/fingerprint.client.obfuscated.js +1 -1
  8. package/src/js/fingerprint.js +489 -99
  9. package/src/js/fingerprint.utils.js +252 -2
  10. package/src/js/pow.worker.js +2 -2
  11. package/src/js/tests/cross_parity.test.js +3 -3
  12. package/src/js/tests/dns-circuit-breaker.test.js +6 -6
  13. package/src/js/tests/dynamic-wasm.test.js +77 -77
  14. package/src/js/tests/fingerprint.builder.test.js +76 -78
  15. package/src/js/tests/fingerprint.client.init.test.js +78 -0
  16. package/src/js/tests/fingerprint.client.test.js +16 -17
  17. package/src/js/tests/fingerprint.test.js +37 -12
  18. package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
  19. package/src/js/tests/library.test.js +94 -95
  20. package/src/js/tests/obfuscation.test.js +8 -8
  21. package/src/js/tests/quicFingerprint.test.js +47 -0
  22. package/src/js/tests/tcpFingerprint.test.js +4 -4
  23. package/src/js/tests/threatIntel.test.js +40 -1
  24. package/src/js/upow-model-task.js +155 -112
  25. package/src/php/Challenge/ChallengeUtils.php +392 -86
  26. package/src/php/Config/SecurityProfiles.php +79 -61
  27. package/src/php/DirectFingerprint.php +55 -8
  28. package/src/php/FingerprintBuilder.php +13 -13
  29. package/src/php/FingerprintClient.php +6 -6
  30. package/src/php/FingerprintEngine.php +2122 -1690
  31. package/src/php/Ja3AnomalyDetector.php +22 -22
  32. package/src/php/Optimization/FunctionRegistry.php +4 -4
  33. package/src/php/Optimization/Optimization.php +6 -6
  34. package/src/php/RequestContext.php +11 -11
  35. package/src/php/Store/InMemoryStore.php +96 -66
  36. package/src/php/Store/MongoDbStore.php +2 -2
  37. package/src/php/Store/RedisStore.php +1 -1
  38. package/src/php/Store/StoreManager.php +48 -35
  39. package/src/php/Tests/ChallengeUtilsTest.php +31 -0
  40. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  41. package/src/php/Tests/FingerprintClientTest.php +70 -70
  42. package/src/php/Tests/PowTest.php +37 -39
  43. package/src/php/Tests/QuicFingerprintTest.php +115 -53
  44. package/src/php/Tests/ThreatIntelTest.php +34 -0
  45. package/src/php/Utils/BigInt.php +202 -202
  46. package/src/php/Utils/BlockList.php +13 -13
  47. package/src/php/Utils/Logger.php +19 -1
  48. package/src/php/Utils/MaliciousPatterns.php +6 -6
  49. package/src/php/Utils/MetricsManager.php +209 -209
  50. package/src/php/Utils/RequestUtils.php +362 -1
  51. package/src/php/Utils/TLSClientHelloParser.php +385 -369
  52. package/src/php/WordPress/WpDbStore.php +7 -7
  53. package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1121 -0
  54. package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
  55. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
  56. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
  57. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  58. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
  59. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  60. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
  61. package/src/php/WordPress/package.php +80 -101
  62. package/src/php/WordPress/readme.txt +21 -5
  63. package/src/php/bin/auto-tune.php +1 -1
  64. package/public/fingerprint-anti-bot.zip +0 -0
@@ -1,136 +1,135 @@
1
- import crypto from 'crypto';
2
- import {getTlsSpoofingScore, parseJa3} from '../fingerprint.js';
3
- import {vi} from 'vitest';
4
- import { verifyZkpProof, decodePolymorphicFingerprint, deepMerge, getHeaderSignature, parseJa3, modPow, hashNetwork, normalizeReferer, isPrivateIp, parseUserAgent } from "../../js/fingerprint.utils.js";
5
-
6
- describe('JA3 Anomaly Detector (Node.js)', () => {
7
-
8
- // Mock d'un store de cache asynchrone en mémoire
9
- const createMockStore = () => {
10
- const storage = {};
11
- return {
12
- get: vi.fn(async (key) => storage[key] || null),
13
- set: vi.fn(async (key, val) => { storage[key] = val; })
14
- };
15
- };
16
-
17
- describe('parseJa3', () => {
18
- it('devrait parser correctement une chaine JA3 brute', () => {
19
- const rawJa3 = '771,4865-4866-4867,0-23-65281-10-11,29-23-24,0';
20
- const parsed = parseJa3(rawJa3);
21
-
22
- expect(parsed).not.toBeNull();
23
- expect(parsed.tlsVersion).toBe(771);
24
- expect(parsed.ciphers).toEqual([4865, 4866, 4867]);
25
- expect(parsed.extensions).toEqual([0, 23, 65281, 10, 11]);
26
- });
27
-
28
- it('devrait retourner null pour des chaines invalides', () => {
29
- expect(parseJa3('')).toBeNull();
30
- expect(parseJa3('771,4865')).toBeNull();
31
- });
32
- });
33
-
34
- describe('getTlsSpoofingScore (Anomalies JA3 hachées)', () => {
35
- it('devrait détecter l\'usurpation d\'identité d\'une bibliothèque (Python)', async () => {
36
- const pythonJa3Hash = '47344a349b75c4e82333475553b5f358';
37
- const chromeUa = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
38
-
39
- const context = {
40
- headers: {
41
- 'user-agent': chromeUa,
42
- 'x-ja3-hash': pythonJa3Hash
43
- }
44
- };
45
-
46
- const result = await getTlsSpoofingScore(context);
47
- expect(result.tlsSpoofingScore).toBe(90);
48
- });
49
-
50
- it('devrait détecter la rotation d\'User-Agent sur un même hash JA3 (Stagnation)', async () => {
51
- const mockStore = createMockStore();
52
- const unknownJa3 = '00000000000000000000000000000000';
53
-
54
- const contextChrome = {
55
- headers: {
56
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0',
57
- 'x-ja3-hash': unknownJa3
58
- }
59
- };
60
-
61
- const contextFirefox = {
62
- headers: {
63
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/120.0',
64
- 'x-ja3-hash': unknownJa3
65
- }
66
- };
67
-
68
- // Injecter le mockStore dans le module de test si nécessaire ou mocker l'importation du store global
69
- // Premier passage : Chrome
70
- const res1 = await getTlsSpoofingScore(contextChrome, mockStore);
71
-
72
- // Deuxième passage : Firefox (Détection de la rotation de UA)
73
- const res2 = await getTlsSpoofingScore(contextFirefox, mockStore);
74
- expect(res2.tlsSpoofingScore).toBe(85);
75
- });
76
- });
77
-
78
- describe('getTlsSpoofingScore (Anomalies JA3 brutes)', () => {
79
- it('devrait suspecter un faux Chrome n\'utilisant pas le mécanisme GREASE', async () => {
80
- const chromeUa = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
81
- // Pas de valeurs GREASE dans la suite
82
- const ja3RawNoGrease = '771,4865-4866,0-23-10,29,0';
83
- const ja3Hash = crypto.createHash('md5').update(ja3RawNoGrease).digest('hex');
84
-
85
- const context = {
86
- headers: {
87
- 'user-agent': chromeUa,
88
- 'x-ja3-raw': ja3RawNoGrease,
89
- 'x-ja3-hash': ja3Hash
90
- },
91
- httpVersion: '2.0'
92
- };
93
-
94
- const result = await getTlsSpoofingScore(context);
95
- expect(result.tlsSpoofingScore).toBe(75);
96
- });
97
-
98
- it('devrait valider un Chrome légitime utilisant des valeurs GREASE', async () => {
99
- const chromeUa = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
100
- // 2570 est une suite GREASE valide
101
- const ja3RawWithGrease = '771,4865-2570,0-23-10-16,29,0';
102
- const ja3Hash = crypto.createHash('md5').update(ja3RawWithGrease).digest('hex');
103
-
104
- const context = {
105
- headers: {
106
- 'user-agent': chromeUa,
107
- 'x-ja3-raw': ja3RawWithGrease,
108
- 'x-ja3-hash': ja3Hash
109
- },
110
- httpVersion: '2.0'
111
- };
112
-
113
- const result = await getTlsSpoofingScore(context);
114
- expect(result.tlsSpoofingScore).toBeLessThan(70);
115
- });
116
-
117
- it('devrait détecter l\'absence d\'extension ALPN pour une connexion HTTP/2', async () => {
118
- const chromeUa = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
119
- // Pas d'extension 16 (ALPN)
120
- const ja3RawNoAlpn = '771,4865-2570,0-23-10,29,0';
121
- const ja3Hash = crypto.createHash('md5').update(ja3RawNoAlpn).digest('hex');
122
-
123
- const context = {
124
- headers: {
125
- 'user-agent': chromeUa,
126
- 'x-ja3-raw': ja3RawNoAlpn,
127
- 'x-ja3-hash': ja3Hash
128
- },
129
- httpVersion: '2.0'
130
- };
131
-
132
- const result = await getTlsSpoofingScore(context);
133
- expect(result.tlsSpoofingScore).toBe(70);
134
- });
135
- });
136
- });
1
+ import crypto from 'crypto';
2
+ import {getTlsSpoofingScore, parseJa3} from '../fingerprint.js';
3
+ import {vi} from 'vitest';
4
+ import { verifyZkpProof, decodePolymorphicFingerprint, deepMerge, getHeaderSignature, parseJa3, modPow, hashNetwork, normalizeReferer, isPrivateIp, parseUserAgent } from "../../js/fingerprint.utils.js";
5
+
6
+ describe('JA3 Anomaly Detector (Node.js)', () => {
7
+
8
+ // Mock async in-memory cache store
9
+ const createMockStore = () => {
10
+ const storage = {};
11
+ return {
12
+ get: vi.fn(async (key) => storage[key] || null),
13
+ set: vi.fn(async (key, val) => { storage[key] = val; })
14
+ };
15
+ };
16
+
17
+ describe('parseJa3', () => {
18
+ it('should correctly parse raw JA3 string', () => {
19
+ const rawJa3 = '771,4865-4866-4867,0-23-65281-10-11,29-23-24,0';
20
+ const parsed = parseJa3(rawJa3);
21
+
22
+ expect(parsed).not.toBeNull();
23
+ expect(parsed.tlsVersion).toBe(771);
24
+ expect(parsed.ciphers).toEqual([4865, 4866, 4867]);
25
+ expect(parsed.extensions).toEqual([0, 23, 65281, 10, 11]);
26
+ });
27
+
28
+ it('should return null for invalid strings', () => {
29
+ expect(parseJa3('')).toBeNull();
30
+ expect(parseJa3('771,4865')).toBeNull();
31
+ });
32
+ });
33
+
34
+ describe('getTlsSpoofingScore (Anomalies JA3 hachées)', () => {
35
+ it('should detect library spoofing (Python)', async () => {
36
+ const pythonJa3Hash = '47344a349b75c4e82333475553b5f358';
37
+ const chromeUa = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
38
+
39
+ const context = {
40
+ headers: {
41
+ 'user-agent': chromeUa,
42
+ 'x-ja3-hash': pythonJa3Hash
43
+ }
44
+ };
45
+
46
+ const result = await getTlsSpoofingScore(context);
47
+ expect(result.tlsSpoofingScore).toBe(90);
48
+ });
49
+
50
+ it('should detect User-Agent rotation on identical JA3 hash (Stagnation)', async () => {
51
+ const mockStore = createMockStore();
52
+ const unknownJa3 = '00000000000000000000000000000000';
53
+
54
+ const contextChrome = {
55
+ headers: {
56
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0',
57
+ 'x-ja3-hash': unknownJa3
58
+ }
59
+ };
60
+
61
+ const contextFirefox = {
62
+ headers: {
63
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/120.0',
64
+ 'x-ja3-hash': unknownJa3
65
+ }
66
+ };
67
+
68
+ // First call: Chrome
69
+ const res1 = await getTlsSpoofingScore(contextChrome, mockStore);
70
+
71
+ // Second call: Firefox (Detects UA rotation)
72
+ const res2 = await getTlsSpoofingScore(contextFirefox, mockStore);
73
+ expect(res2.tlsSpoofingScore).toBe(85);
74
+ });
75
+ });
76
+
77
+ describe('getTlsSpoofingScore (Anomalies JA3 brutes)', () => {
78
+ it('should flag fake Chrome lacking GREASE values', async () => {
79
+ const chromeUa = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
80
+ // No GREASE values in ciphers/extensions
81
+ const ja3RawNoGrease = '771,4865-4866,0-23-10,29,0';
82
+ const ja3Hash = crypto.createHash('md5').update(ja3RawNoGrease).digest('hex');
83
+
84
+ const context = {
85
+ headers: {
86
+ 'user-agent': chromeUa,
87
+ 'x-ja3-raw': ja3RawNoGrease,
88
+ 'x-ja3-hash': ja3Hash
89
+ },
90
+ httpVersion: '2.0'
91
+ };
92
+
93
+ const result = await getTlsSpoofingScore(context);
94
+ expect(result.tlsSpoofingScore).toBe(75);
95
+ });
96
+
97
+ it('should validate legitimate Chrome using GREASE values', async () => {
98
+ const chromeUa = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
99
+ // 2570 is a valid GREASE value
100
+ const ja3RawWithGrease = '771,4865-2570,0-23-10-16,29,0';
101
+ const ja3Hash = crypto.createHash('md5').update(ja3RawWithGrease).digest('hex');
102
+
103
+ const context = {
104
+ headers: {
105
+ 'user-agent': chromeUa,
106
+ 'x-ja3-raw': ja3RawWithGrease,
107
+ 'x-ja3-hash': ja3Hash
108
+ },
109
+ httpVersion: '2.0'
110
+ };
111
+
112
+ const result = await getTlsSpoofingScore(context);
113
+ expect(result.tlsSpoofingScore).toBeLessThan(70);
114
+ });
115
+
116
+ it('should detect missing ALPN extension on HTTP/2 connections', async () => {
117
+ const chromeUa = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0';
118
+ // Missing extension 16 (ALPN)
119
+ const ja3RawNoAlpn = '771,4865-2570,0-23-10,29,0';
120
+ const ja3Hash = crypto.createHash('md5').update(ja3RawNoAlpn).digest('hex');
121
+
122
+ const context = {
123
+ headers: {
124
+ 'user-agent': chromeUa,
125
+ 'x-ja3-raw': ja3RawNoAlpn,
126
+ 'x-ja3-hash': ja3Hash
127
+ },
128
+ httpVersion: '2.0'
129
+ };
130
+
131
+ const result = await getTlsSpoofingScore(context);
132
+ expect(result.tlsSpoofingScore).toBe(70);
133
+ });
134
+ });
135
+ });
@@ -1,96 +1,95 @@
1
- import {describe, expect, it} from 'vitest';
2
- import {Optimization} from '../library.js';
3
-
4
- describe('Optimization.Operators.benfordTest', () => {
5
-
6
- it('should return 0 for non-array inputs', () => {
7
- expect(Optimization.Operators.benfordTest("12345")).toBe(0);
8
- expect(Optimization.Operators.benfordTest(null)).toBe(0);
9
- expect(Optimization.Operators.benfordTest(undefined)).toBe(0);
10
- expect(Optimization.Operators.benfordTest({ a: 1 })).toBe(0);
11
- });
12
-
13
- it('should return 0 for arrays with less than 10 valid numbers', () => {
14
- const smallArray = [1, 2, 3, 4, 5, 6, 7, 8, 9];
15
- expect(Optimization.Operators.benfordTest(smallArray)).toBe(0);
16
- });
17
-
18
- it('should return 0 for an empty array', () => {
19
- expect(Optimization.Operators.benfordTest([])).toBe(0);
20
- });
21
-
22
- it('should ignore zeros, non-numeric strings, and leading spaces', () => {
23
- // This array contains only 9 valid leading digits.
24
- const dirtyArray = [0, " 123", "abc", 2, 3, 4, 5, 6, 7, 8, 9, null, undefined];
25
- expect(Optimization.Operators.benfordTest(dirtyArray)).toBe(0);
26
- });
27
-
28
- it('should return a very low score for a distribution that perfectly matches Benford\'s law', () => {
29
- // Création d'un échantillon de 1000 nombres qui suit la loi de Benford
30
- const benfordSample = [
31
- ...Array(301).fill(100), // 301 nombres commençant par 1
32
- ...Array(176).fill(200), // 176 nombres commençant par 2
33
- ...Array(125).fill(300), // 125 nombres commençant par 3
34
- ...Array(97).fill(400), // etc.
35
- ...Array(79).fill(500),
36
- ...Array(67).fill(600),
37
- ...Array(58).fill(700),
38
- ...Array(51).fill(800),
39
- ...Array(46).fill(900),
40
- ];
41
-
42
- const score = Optimization.Operators.benfordTest(benfordSample);
43
- // Le score devrait être très proche de 0. On utilise toBeLessThan pour tolérer les imprécisions de calcul.
44
- expect(score).toBeLessThan(1e-9);
45
- });
46
-
47
- it('should return a high (suspect) score for a uniform distribution', () => {
48
- // Une distribution uniforme est très peu naturelle pour ce genre de données.
49
- const uniformSample = [];
50
- for (let i = 1; i <= 9; i++) {
51
- for (let j = 0; j < 100; j++) {
52
- uniformSample.push(i * 100 + j);
53
- }
54
- }
55
-
56
- const score = Optimization.Operators.benfordTest(uniformSample);
57
- // Un score > 0.15 est considéré comme suspect.
58
- expect(score).toBeGreaterThan(0.15);
59
- });
60
-
61
- it('should return a high (suspect) score for a distribution skewed towards high digits', () => {
62
- // L'inverse de la loi de Benford, très suspect.
63
- const inverseBenfordSample = [
64
- ...Array(46).fill(100),
65
- ...Array(51).fill(200),
66
- ...Array(58).fill(300),
67
- ...Array(67).fill(400),
68
- ...Array(79).fill(500),
69
- ...Array(97).fill(600),
70
- ...Array(125).fill(700),
71
- ...Array(176).fill(800),
72
- ...Array(301).fill(900),
73
- ];
74
-
75
- const score = Optimization.Operators.benfordTest(inverseBenfordSample);
76
- expect(score).toBeGreaterThan(0.3); // Score attendu encore plus élevé
77
- });
78
-
79
- it('should handle real-world-like data (request timings)', () => {
80
- // Simule des délais de requêtes générés par un bot (ex: aléatoire uniforme entre 500 et 1500ms)
81
- const botTimings = Array.from({ length: 100 }, () => 500 + Math.random() * 1000);
82
- const botScore = Optimization.Operators.benfordTest(botTimings);
83
-
84
- // Simule des délais humains (plus de petits délais, quelques longs délais)
85
- const humanTimings = [
86
- 123, 234, 180, 345, 150, 456, 110, 190, 210, 280, 567, 130, 890, 1200, 310, 160
87
- ];
88
- const humanScore = Optimization.Operators.benfordTest(humanTimings);
89
-
90
- // Le score du bot devrait être significativement plus élevé que celui de l'humain.
91
- // Les valeurs exactes peuvent varier, mais la tendance doit être claire.
92
- expect(botScore).toBeGreaterThan(0.1);
93
- expect(humanScore).toBeLessThan(botScore);
94
- });
95
-
1
+ import {describe, expect, it} from 'vitest';
2
+ import {Optimization} from '../library.js';
3
+
4
+ describe('Optimization.Operators.benfordTest', () => {
5
+
6
+ it('should return 0 for non-array inputs', () => {
7
+ expect(Optimization.Operators.benfordTest("12345")).toBe(0);
8
+ expect(Optimization.Operators.benfordTest(null)).toBe(0);
9
+ expect(Optimization.Operators.benfordTest(undefined)).toBe(0);
10
+ expect(Optimization.Operators.benfordTest({ a: 1 })).toBe(0);
11
+ });
12
+
13
+ it('should return 0 for arrays with less than 10 valid numbers', () => {
14
+ const smallArray = [1, 2, 3, 4, 5, 6, 7, 8, 9];
15
+ expect(Optimization.Operators.benfordTest(smallArray)).toBe(0);
16
+ });
17
+
18
+ it('should return 0 for an empty array', () => {
19
+ expect(Optimization.Operators.benfordTest([])).toBe(0);
20
+ });
21
+
22
+ it('should ignore zeros, non-numeric strings, and leading spaces', () => {
23
+ // This array contains only 9 valid leading digits.
24
+ const dirtyArray = [0, " 123", "abc", 2, 3, 4, 5, 6, 7, 8, 9, null, undefined];
25
+ expect(Optimization.Operators.benfordTest(dirtyArray)).toBe(0);
26
+ });
27
+
28
+ it('should return a very low score for a distribution that perfectly matches Benford\'s law', () => {
29
+ // Sample of 1000 numbers matching Benford's Law distribution
30
+ const benfordSample = [
31
+ ...Array(301).fill(100), // 301 numbers leading with 1
32
+ ...Array(176).fill(200), // 176 numbers leading with 2
33
+ ...Array(125).fill(300), // 125 numbers leading with 3
34
+ ...Array(97).fill(400), // etc.
35
+ ...Array(79).fill(500),
36
+ ...Array(67).fill(600),
37
+ ...Array(58).fill(700),
38
+ ...Array(51).fill(800),
39
+ ...Array(46).fill(900),
40
+ ];
41
+
42
+ const score = Optimization.Operators.benfordTest(benfordSample);
43
+ // Score should be close to 0; tolerance used for floating point variance
44
+ expect(score).toBeLessThan(1e-9);
45
+ });
46
+
47
+ it('should return a high (suspect) score for a uniform distribution', () => {
48
+ // Uniform distribution is unnatural for this category of metrics
49
+ const uniformSample = [];
50
+ for (let i = 1; i <= 9; i++) {
51
+ for (let j = 0; j < 100; j++) {
52
+ uniformSample.push(i * 100 + j);
53
+ }
54
+ }
55
+
56
+ const score = Optimization.Operators.benfordTest(uniformSample);
57
+ // A score > 0.15 is considered suspicious
58
+ expect(score).toBeGreaterThan(0.15);
59
+ });
60
+
61
+ it('should return a high (suspect) score for a distribution skewed towards high digits', () => {
62
+ // Inverted Benford distribution: highly unnatural
63
+ const inverseBenfordSample = [
64
+ ...Array(46).fill(100),
65
+ ...Array(51).fill(200),
66
+ ...Array(58).fill(300),
67
+ ...Array(67).fill(400),
68
+ ...Array(79).fill(500),
69
+ ...Array(97).fill(600),
70
+ ...Array(125).fill(700),
71
+ ...Array(176).fill(800),
72
+ ...Array(301).fill(900),
73
+ ];
74
+
75
+ const score = Optimization.Operators.benfordTest(inverseBenfordSample);
76
+ expect(score).toBeGreaterThan(0.3); // Significantly elevated score expected
77
+ });
78
+
79
+ it('should handle real-world-like data (request timings)', () => {
80
+ // Simulates bot request intervals (uniform random between 500 and 1500ms)
81
+ const botTimings = Array.from({ length: 100 }, () => 500 + Math.random() * 1000);
82
+ const botScore = Optimization.Operators.benfordTest(botTimings);
83
+
84
+ // Simulates human timings (more short delays, few long pauses)
85
+ const humanTimings = [
86
+ 123, 234, 180, 345, 150, 456, 110, 190, 210, 280, 567, 130, 890, 1200, 310, 160
87
+ ];
88
+ const humanScore = Optimization.Operators.benfordTest(humanTimings);
89
+
90
+ // Bot score should be significantly higher than human score
91
+ expect(botScore).toBeGreaterThan(0.1);
92
+ expect(humanScore).toBeLessThan(botScore);
93
+ });
94
+
96
95
  });
@@ -1,13 +1,13 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { __internal } from '../fingerprint.js';
3
3
 
4
- describe('Système de Polymorphisme et Compilateur d\'Obfuscation', () => {
4
+ describe('Polymorphic System and Obfuscation Compiler', () => {
5
5
 
6
6
  it('should generate a valid session mapping with randomized headers and globals', () => {
7
7
  const mapping1 = __internal.generateSessionMapping();
8
8
  const mapping2 = __internal.generateSessionMapping();
9
9
 
10
- // Vérification de la présence des structures attendues
10
+ // Verify expected mapping structure
11
11
  expect(mapping1).toBeDefined();
12
12
  expect(mapping1.headers).toHaveProperty('x-device-fingerprint');
13
13
  expect(mapping1.headers).toHaveProperty('x-behavior-metrics');
@@ -17,7 +17,7 @@ describe('Système de Polymorphisme et Compilateur d\'Obfuscation', () => {
17
17
  expect(mapping1.keys).toHaveProperty('ua');
18
18
  expect(mapping1.wasmConstants).toHaveProperty('seed');
19
19
 
20
- // Vérification de l'aléa (deux sessions consécutives doivent avoir des mappings uniques)
20
+ // Verify randomness: two consecutive sessions must yield unique mappings
21
21
  expect(mapping1.headers['x-device-fingerprint']).not.toBe(mapping2.headers['x-device-fingerprint']);
22
22
  expect(mapping1.globals['ClientLibrary']).not.toBe(mapping2.globals['ClientLibrary']);
23
23
  expect(mapping1.keys['ua']).not.toBe(mapping2.keys['ua']);
@@ -27,22 +27,22 @@ describe('Système de Polymorphisme et Compilateur d\'Obfuscation', () => {
27
27
  it('should compile and obfuscate client script asynchronously using the obfuscation worker', async () => {
28
28
  const mapping = __internal.generateSessionMapping();
29
29
 
30
- // On lance la compilation lourde qui instancie le Worker d'arrière-plan
30
+ // Run background worker obfuscation task
31
31
  const obfuscatedCode = await __internal.compilePolymorphicJs(mapping);
32
32
 
33
33
  expect(obfuscatedCode).toBeDefined();
34
34
  expect(typeof obfuscatedCode).toBe('string');
35
35
  expect(obfuscatedCode.length).toBeGreaterThan(0);
36
36
 
37
- // Vérification que les placeholders originaux ont bien été purgés du code client final
37
+ // Verify original identifiers were rewritten in compiled client script
38
38
  expect(obfuscatedCode).not.toContain('X-Device-Fingerprint');
39
39
  expect(obfuscatedCode).not.toContain('X-Behavior-Metrics');
40
40
  expect(obfuscatedCode).not.toContain('ClientLibrary');
41
41
 
42
- // Vérification que le code est minifié/obfusqué (ne contient pas de commentaires de développement par exemple)
42
+ // Verify code is obfuscated without plain dev comments
43
43
  expect(obfuscatedCode).not.toContain('// Nom trompeur aléatoire pour attirer les analyseurs');
44
44
 
45
- // La structure globale de javascript-obfuscator génère typiquement un tableau de chaînes au début
45
+ // Obfuscator emits string array wrapper prefix
46
46
  expect(obfuscatedCode).toMatch(/_0x/);
47
- }, 15000); // Augmentation du timeout à 15s car l'obfuscation via Worker thread consomme du CPU
47
+ }, 15000); // 15s timeout for CPU-intensive worker execution
48
48
  });
@@ -43,4 +43,51 @@ describe('Protocol Anomaly Flow Profiling', () => {
43
43
  const score = getProtocolAnomalyScore(context);
44
44
  expect(score.protocolAnomalyScore).toBe(100.0);
45
45
  });
46
+
47
+ it('should detect RFC 9218 invalid urgency value out of range (u=9)', () => {
48
+ const context = {
49
+ headers: {
50
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
51
+ 'x-quic-fp': '1;1=1572864,4=100;u=9,i'
52
+ }
53
+ };
54
+ const score = getProtocolAnomalyScore(context);
55
+ expect(score.quicAnomalyScore).toBeGreaterThanOrEqual(50.0);
56
+ });
57
+
58
+ it('should detect HTTP/2 flat dependency tree from Go net/http2 scraper', () => {
59
+ const context = {
60
+ headers: {
61
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
62
+ 'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:16|m,a,s,p|p:3,w:4,c:1'
63
+ },
64
+ httpVersion: '2.0'
65
+ };
66
+ const score = getProtocolAnomalyScore(context);
67
+ expect(score.http2AnomalyScore).toBeGreaterThanOrEqual(55.0);
68
+ });
69
+
70
+ it('should detect HTTP/2 self-dependency violation', () => {
71
+ const context = {
72
+ headers: {
73
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
74
+ 'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|3:0:3:256|m,a,s,p|p:3,w:4,c:1'
75
+ },
76
+ httpVersion: '2.0'
77
+ };
78
+ const score = getProtocolAnomalyScore(context);
79
+ expect(score.http2AnomalyScore).toBeGreaterThanOrEqual(60.0);
80
+ });
81
+
82
+ it('should detect scrapers with 0 dynamic table entries on repeated requests', () => {
83
+ const context = {
84
+ headers: {
85
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
86
+ 'x-compression-info': 'req_count:5,dynamic_table_entries:0'
87
+ },
88
+ httpVersion: '2.0'
89
+ };
90
+ const score = getProtocolAnomalyScore(context);
91
+ expect(score.http2AnomalyScore).toBeGreaterThanOrEqual(45.0);
92
+ });
46
93
  });
@@ -5,7 +5,7 @@ const { parseTcpSyn, classifyTcpOs, getTcpAnomalyScore } = __internal;
5
5
 
6
6
  describe('Passive TCP/IP Fingerprinting (Type p0f)', () => {
7
7
 
8
- // Mock d'un paquet IP + TCP SYN d'un système Linux (TTL=64, WS=7)
8
+ // Mock Linux IP + TCP SYN packet (TTL=64, WS=7)
9
9
  const mockLinuxSynPacket = Buffer.from([
10
10
  0x45, 0x00, 0x00, 0x3c, 0x1a, 0x2b, 0x40, 0x00,
11
11
  0x40, 0x06, 0x3c, 0x1a, 0x7f, 0x00, 0x00, 0x01, // TTL=0x40 (64)
@@ -19,7 +19,7 @@ describe('Passive TCP/IP Fingerprinting (Type p0f)', () => {
19
19
  0x03, 0x03, 0x07 // Option WS: 7
20
20
  ]);
21
21
 
22
- // Mock d'un paquet IP + TCP SYN d'un système Windows (TTL=128, WS=8)
22
+ // Mock Windows IP + TCP SYN packet (TTL=128, WS=8)
23
23
  const mockWindowsSynPacket = Buffer.from([
24
24
  0x45, 0x00, 0x00, 0x3c, 0x1a, 0x2b, 0x40, 0x00,
25
25
  0x80, 0x06, 0x3c, 0x1a, 0x7f, 0x00, 0x00, 0x01, // TTL=0x80 (128)
@@ -57,7 +57,7 @@ describe('Passive TCP/IP Fingerprinting (Type p0f)', () => {
57
57
  });
58
58
 
59
59
  it('should calculate correct TCP/IP anomaly scores', () => {
60
- // Cas 1 : Le User-Agent prétend être Windows, mais la pile TCP/IP est Linux
60
+ // Case 1: User-Agent claims Windows, but TCP/IP stack signature is Linux
61
61
  const contextAnomaly = {
62
62
  headers: {
63
63
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0',
@@ -66,7 +66,7 @@ describe('Passive TCP/IP Fingerprinting (Type p0f)', () => {
66
66
  };
67
67
  expect(getTcpAnomalyScore(contextAnomaly).tcpAnomalyScore).toBe(80.1);
68
68
 
69
- // Cas 2 : Cohérence complète (UA Windows et pile TCP/IP Windows)
69
+ // Case 2: Consistent stack (Windows UA and Windows TCP/IP stack)
70
70
  const contextCoherent = {
71
71
  headers: {
72
72
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0',