@anonympins/fingerprint 0.7.0 → 0.7.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +1 -1
  3. package/composer.json +8 -1
  4. package/package.json +1 -1
  5. package/public/fingerprint-wordpress.zip +0 -0
  6. package/src/js/build-client.js +8 -10
  7. package/src/js/fingerprint.builder.js +37 -38
  8. package/src/js/fingerprint.client.js +1531 -1463
  9. package/src/js/fingerprint.client.obfuscated.js +1 -0
  10. package/src/js/fingerprint.js +190 -16
  11. package/src/js/fingerprint.utils.js +213 -213
  12. package/src/js/library.js +340 -376
  13. package/src/js/mongodb-store.js +1 -1
  14. package/src/js/optimization.worker.js +27 -27
  15. package/src/js/pow.solver.inline.js +154 -56
  16. package/src/js/pow.solver.js +180 -92
  17. package/src/js/pow.worker.js +48 -48
  18. package/src/js/redis-store.js +1 -1
  19. package/src/js/tests/fingerprint.client.init.test.js +146 -143
  20. package/src/js/tests/fingerprint.test.js +124 -10
  21. package/src/js/tests/ip-reputation.test.js +1 -0
  22. package/src/js/upow-model-task.js +14 -15
  23. package/src/php/AutoTuner.php +34 -34
  24. package/src/php/Challenge/ChallengeUtils.php +57 -11
  25. package/src/php/Config/SecurityProfiles.php +20 -0
  26. package/src/php/DirectFingerprint.php +190 -145
  27. package/src/php/FingerprintBuilder.php +29 -30
  28. package/src/php/FingerprintClient.php +147 -148
  29. package/src/php/FingerprintEngine.php +44 -17
  30. package/src/php/Ja3AnomalyDetector.php +33 -32
  31. package/src/php/Optimization/FunctionRegistry.php +7 -7
  32. package/src/php/Optimization/Optimization.php +24 -24
  33. package/src/php/Optimization/OptimizationOperators.php +26 -25
  34. package/src/php/Optimization/ProblemInitializers.php +52 -52
  35. package/src/php/ProblemManager.php +31 -34
  36. package/src/php/RequestContext.php +8 -1
  37. package/src/php/Store/IStore.php +7 -8
  38. package/src/php/Store/InMemoryStore.php +66 -66
  39. package/src/php/Store/MongoDbStore.php +5 -5
  40. package/src/php/Store/RedisStore.php +5 -5
  41. package/src/php/Store/StoreManager.php +35 -35
  42. package/src/php/Tests/ChallengeUtilsTest.php +453 -453
  43. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  44. package/src/php/Tests/FingerprintEngineTest.php +38 -0
  45. package/src/php/Tests/IpReputationTest.php +10 -0
  46. package/src/php/Tests/MaliciousPatternsTest.php +103 -103
  47. package/src/php/Tests/MetricsTest.php +97 -97
  48. package/src/php/Tests/ProblemManagerTest.php +376 -376
  49. package/src/php/Tests/QuicFingerprintTest.php +53 -53
  50. package/src/php/Tests/RequestUtilsTest.php +471 -471
  51. package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
  52. package/src/php/Tests/config/ed25519_key.json +3 -3
  53. package/src/php/Utils/BlockList.php +99 -99
  54. package/src/php/Utils/Env.php +49 -49
  55. package/src/php/Utils/MaliciousPatterns.php +74 -74
  56. package/src/php/Utils/MetricsManager.php +209 -209
  57. package/src/php/Utils/RequestUtils.php +129 -7
  58. package/src/php/Utils/TLSClientHelloParser.php +369 -369
  59. package/src/php/WordPress/WpDbStore.php +155 -0
  60. package/src/php/WordPress/fingerprint-wordpress.php +554 -0
  61. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  62. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +262 -0
  63. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  64. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +268 -0
  65. package/src/php/WordPress/package.php +218 -0
  66. package/src/php/bin/auto-tune.php +118 -118
@@ -35,7 +35,7 @@ export function createRedisStore(redisClient) {
35
35
  reconnecting = false;
36
36
  clearInterval(interval);
37
37
  } catch (err) {
38
- // Continue de tenter la reconnexion toutes les 5 secondes
38
+ // Keep retrying connection every 5 seconds
39
39
  }
40
40
  }, 5000);
41
41
  if (interval.unref) interval.unref();
@@ -1,144 +1,147 @@
1
- import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
2
- import {JSDOM} from 'jsdom';
3
- import ClientLibrary from '../fingerprint.client.js';
4
-
5
- // --- Setup JSDOM Environment ---
6
- // Vitest peut être configuré pour le faire automatiquement, mais le faire manuellement
7
- // ici rend le test explicite et portable.
8
- const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
9
- url: 'http://localhost',
10
- });
11
-
12
- // In recent Node.js versions, some global properties like 'navigator', 'performance',
13
- // and 'screen' are read-only. To ensure our JSDOM environment works correctly across
14
- // all versions, we use Object.defineProperty to make these properties writable.
15
- Object.defineProperty(global, 'window', { value: dom.window, writable: true });
16
- Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
17
- Object.defineProperty(global, 'navigator', { value: dom.window.navigator, writable: true });
18
- Object.defineProperty(global, 'screen', { value: dom.window.screen, writable: true });
19
- Object.defineProperty(global, 'performance', { value: dom.window.performance, writable: true });
20
-
21
- global.fetch = vi.fn(); // Mock global fetch
22
- global.Headers = dom.window.Headers;
23
- global.Request = dom.window.Request;
24
- global.URL = dom.window.URL;
25
- global.TextEncoder = dom.window.TextEncoder;
26
-
27
-
28
- describe('ClientLibrary.initializeClient', () => {
29
-
30
- // On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
31
- let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, startRenderingSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
32
-
33
- beforeEach(() => {
34
- // Réinitialiser l'état du client avant chaque test
35
- ClientLibrary._resetCache();
36
-
37
- // Créer les espions sur les méthodes internes
38
- startMouseSpy = vi.spyOn(ClientLibrary, 'startMouseEntropyTracker');
39
- startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
40
- startClickSpy = vi.spyOn(ClientLibrary, 'startClickTracker');
41
- startTouchSpy = vi.spyOn(ClientLibrary, 'startTouchEventTracker');
42
- startRenderingSpy = vi.spyOn(ClientLibrary, 'startRenderingTracker');
43
- initWasmSpy = vi.spyOn(ClientLibrary, 'initializeWasm').mockResolvedValue(undefined);
44
- initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
45
- injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
46
- initFetchSpy = vi.spyOn(ClientLibrary, 'initializeFetch');
47
- injectPhantomTrapsSpy = vi.spyOn(ClientLibrary, 'injectPhantomTraps');
48
- });
49
-
50
- afterEach(() => {
51
- // Restaurer les espions après chaque test pour ne pas affecter les autres tests
52
- vi.restoreAllMocks();
53
- });
54
-
55
- it('should enable all trackers by default', () => {
56
- ClientLibrary.initializeClient();
57
-
58
- expect(startMouseSpy).toHaveBeenCalled();
59
- expect(startKeystrokeSpy).toHaveBeenCalled();
60
- expect(startClickSpy).toHaveBeenCalled();
61
- expect(startTouchSpy).toHaveBeenCalled();
62
- expect(startRenderingSpy).toHaveBeenCalled();
63
- expect(injectPhantomTrapsSpy).toHaveBeenCalled();
64
- });
65
-
66
- it('should disable trackers when configured', () => {
67
- ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false, rendering: false });
68
-
69
- expect(startMouseSpy).not.toHaveBeenCalled();
70
- expect(startKeystrokeSpy).not.toHaveBeenCalled();
71
- expect(startClickSpy).not.toHaveBeenCalled();
72
- expect(startTouchSpy).not.toHaveBeenCalled();
73
- expect(startRenderingSpy).not.toHaveBeenCalled();
74
- });
75
-
76
- it('should initialize WASM if wasmPath is configured', () => {
77
- ClientLibrary.initializeClient({ wasmPath: '/fp.js' });
78
-
79
- expect(initWasmSpy).toHaveBeenCalledWith('/fp.js');
80
- });
81
-
82
- it('should disable phantom traps when configured', () => {
83
- ClientLibrary.initializeClient({ phantomTraps: false });
84
-
85
- expect(injectPhantomTrapsSpy).not.toHaveBeenCalled();
86
- });
87
-
88
- it('should initialize honeypots with the provided field names', () => {
89
- const honeypotFields = ['email_confirm', 'user_nickname'];
90
- ClientLibrary.initializeClient({ honeypots: honeypotFields });
91
-
92
- expect(initHoneypotsSpy).toHaveBeenCalledWith(honeypotFields);
93
- });
94
-
95
- it('should not initialize honeypots if the array is empty', () => {
96
- ClientLibrary.initializeClient({ honeypots: [] });
97
-
98
- expect(initHoneypotsSpy).not.toHaveBeenCalled();
99
- });
100
-
101
- it('should inject trap URLs when provided', () => {
102
- const urls = ['/trap1?sig=123', '/trap2?sig=456'];
103
- ClientLibrary.initializeClient({ trapUrls: urls });
104
-
105
- expect(injectTrapsSpy).toHaveBeenCalledWith(urls);
106
- });
107
-
108
- it('should not inject trap URLs if the array is empty', () => {
109
- ClientLibrary.initializeClient({ trapUrls: [] });
110
-
111
- expect(injectTrapsSpy).not.toHaveBeenCalled();
112
- });
113
-
114
- it('should initialize fetch interception when fetch config is present', () => {
115
- const targetDomains = ['api.example.com'];
116
- ClientLibrary.initializeClient({ fetch: { targetDomains } });
117
-
118
- expect(initFetchSpy).toHaveBeenCalledWith(targetDomains);
119
- });
120
-
121
- it('should not initialize fetch interception if fetch config is absent', () => {
122
- ClientLibrary.initializeClient(); // No fetch config
123
-
124
- expect(initFetchSpy).not.toHaveBeenCalled();
125
- });
126
-
127
- it('should call all initializers correctly when a full config is provided', () => {
128
- const config = {
129
- mouse: true,
130
- keystrokes: true,
131
- honeypots: ['field1'],
132
- trapUrls: ['/trap1'],
133
- fetch: { targetDomains: ['api.com'] }
134
- };
135
- ClientLibrary.initializeClient(config);
136
-
137
- expect(startMouseSpy).toHaveBeenCalled();
138
- expect(startKeystrokeSpy).toHaveBeenCalled();
139
- expect(initHoneypotsSpy).toHaveBeenCalledWith(config.honeypots);
140
- expect(injectTrapsSpy).toHaveBeenCalledWith(config.trapUrls);
141
- expect(initFetchSpy).toHaveBeenCalledWith(config.fetch.targetDomains);
142
- });
143
-
1
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
2
+ import {JSDOM} from 'jsdom';
3
+ import ClientLibrary from '../fingerprint.client.js';
4
+
5
+ // --- Setup JSDOM Environment ---
6
+ // Vitest peut être configuré pour le faire automatiquement, mais le faire manuellement
7
+ // ici rend le test explicite et portable.
8
+ const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
9
+ url: 'http://localhost',
10
+ });
11
+
12
+ // In recent Node.js versions, some global properties like 'navigator', 'performance',
13
+ // and 'screen' are read-only. To ensure our JSDOM environment works correctly across
14
+ // all versions, we use Object.defineProperty to make these properties writable.
15
+ Object.defineProperty(global, 'window', { value: dom.window, writable: true });
16
+ Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
17
+ Object.defineProperty(global, 'navigator', { value: dom.window.navigator, writable: true });
18
+ Object.defineProperty(global, 'screen', { value: dom.window.screen, writable: true });
19
+ Object.defineProperty(global, 'performance', { value: dom.window.performance, writable: true });
20
+
21
+ global.fetch = vi.fn(); // Mock global fetch
22
+ global.Headers = dom.window.Headers;
23
+ global.Request = dom.window.Request;
24
+ global.URL = dom.window.URL;
25
+ global.TextEncoder = dom.window.TextEncoder;
26
+
27
+
28
+ describe('ClientLibrary.initializeClient', () => {
29
+
30
+ // On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
31
+ let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, startMotionSpy, startRenderingSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
32
+
33
+ beforeEach(() => {
34
+ // Réinitialiser l'état du client avant chaque test
35
+ ClientLibrary._resetCache();
36
+
37
+ // Créer les espions sur les méthodes internes
38
+ startMouseSpy = vi.spyOn(ClientLibrary, 'startMouseEntropyTracker');
39
+ startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
40
+ startClickSpy = vi.spyOn(ClientLibrary, 'startClickTracker');
41
+ startTouchSpy = vi.spyOn(ClientLibrary, 'startTouchEventTracker');
42
+ startMotionSpy = vi.spyOn(ClientLibrary, 'startMotionTracker');
43
+ startRenderingSpy = vi.spyOn(ClientLibrary, 'startRenderingTracker');
44
+ initWasmSpy = vi.spyOn(ClientLibrary, 'initializeWasm').mockResolvedValue(undefined);
45
+ initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
46
+ injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
47
+ initFetchSpy = vi.spyOn(ClientLibrary, 'initializeFetch');
48
+ injectPhantomTrapsSpy = vi.spyOn(ClientLibrary, 'injectPhantomTraps');
49
+ });
50
+
51
+ afterEach(() => {
52
+ // Restaurer les espions après chaque test pour ne pas affecter les autres tests
53
+ vi.restoreAllMocks();
54
+ });
55
+
56
+ it('should enable all trackers by default', () => {
57
+ ClientLibrary.initializeClient();
58
+
59
+ expect(startMouseSpy).toHaveBeenCalled();
60
+ expect(startKeystrokeSpy).toHaveBeenCalled();
61
+ expect(startClickSpy).toHaveBeenCalled();
62
+ expect(startTouchSpy).toHaveBeenCalled();
63
+ expect(startMotionSpy).toHaveBeenCalled();
64
+ expect(startRenderingSpy).toHaveBeenCalled();
65
+ expect(injectPhantomTrapsSpy).toHaveBeenCalled();
66
+ });
67
+
68
+ it('should disable trackers when configured', () => {
69
+ ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false, motion: false, rendering: false });
70
+
71
+ expect(startMouseSpy).not.toHaveBeenCalled();
72
+ expect(startKeystrokeSpy).not.toHaveBeenCalled();
73
+ expect(startClickSpy).not.toHaveBeenCalled();
74
+ expect(startTouchSpy).not.toHaveBeenCalled();
75
+ expect(startMotionSpy).not.toHaveBeenCalled();
76
+ expect(startRenderingSpy).not.toHaveBeenCalled();
77
+ });
78
+
79
+ it('should initialize WASM if wasmPath is configured', () => {
80
+ ClientLibrary.initializeClient({ wasmPath: '/fp.js' });
81
+
82
+ expect(initWasmSpy).toHaveBeenCalledWith('/fp.js');
83
+ });
84
+
85
+ it('should disable phantom traps when configured', () => {
86
+ ClientLibrary.initializeClient({ phantomTraps: false });
87
+
88
+ expect(injectPhantomTrapsSpy).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it('should initialize honeypots with the provided field names', () => {
92
+ const honeypotFields = ['email_confirm', 'user_nickname'];
93
+ ClientLibrary.initializeClient({ honeypots: honeypotFields });
94
+
95
+ expect(initHoneypotsSpy).toHaveBeenCalledWith(honeypotFields);
96
+ });
97
+
98
+ it('should not initialize honeypots if the array is empty', () => {
99
+ ClientLibrary.initializeClient({ honeypots: [] });
100
+
101
+ expect(initHoneypotsSpy).not.toHaveBeenCalled();
102
+ });
103
+
104
+ it('should inject trap URLs when provided', () => {
105
+ const urls = ['/trap1?sig=123', '/trap2?sig=456'];
106
+ ClientLibrary.initializeClient({ trapUrls: urls });
107
+
108
+ expect(injectTrapsSpy).toHaveBeenCalledWith(urls);
109
+ });
110
+
111
+ it('should not inject trap URLs if the array is empty', () => {
112
+ ClientLibrary.initializeClient({ trapUrls: [] });
113
+
114
+ expect(injectTrapsSpy).not.toHaveBeenCalled();
115
+ });
116
+
117
+ it('should initialize fetch interception when fetch config is present', () => {
118
+ const targetDomains = ['api.example.com'];
119
+ ClientLibrary.initializeClient({ fetch: { targetDomains } });
120
+
121
+ expect(initFetchSpy).toHaveBeenCalledWith(targetDomains);
122
+ });
123
+
124
+ it('should not initialize fetch interception if fetch config is absent', () => {
125
+ ClientLibrary.initializeClient(); // No fetch config
126
+
127
+ expect(initFetchSpy).not.toHaveBeenCalled();
128
+ });
129
+
130
+ it('should call all initializers correctly when a full config is provided', () => {
131
+ const config = {
132
+ mouse: true,
133
+ keystrokes: true,
134
+ honeypots: ['field1'],
135
+ trapUrls: ['/trap1'],
136
+ fetch: { targetDomains: ['api.com'] }
137
+ };
138
+ ClientLibrary.initializeClient(config);
139
+
140
+ expect(startMouseSpy).toHaveBeenCalled();
141
+ expect(startKeystrokeSpy).toHaveBeenCalled();
142
+ expect(initHoneypotsSpy).toHaveBeenCalledWith(config.honeypots);
143
+ expect(injectTrapsSpy).toHaveBeenCalledWith(config.trapUrls);
144
+ expect(initFetchSpy).toHaveBeenCalledWith(config.fetch.targetDomains);
145
+ });
146
+
144
147
  });
@@ -10,6 +10,8 @@ import * as fingerprint from '../fingerprint.js';
10
10
  vi.mock('import-meta-env', () => ({
11
11
  env: { NODE_ENV: 'test', POW_SECRET: 'fallback-dev-secret-32-chars-minimum' },
12
12
  }));
13
+ // Mock the entire dns module
14
+ vi.mock('node:dns/promises');
13
15
  // Mock readFileSync for custom challenge page tests
14
16
  vi.mock('node:fs', async () => {
15
17
  const actualFs = await vi.importActual('node:fs');
@@ -35,9 +37,8 @@ const {
35
37
  } = fingerprint;
36
38
  const { store, getRequestPatternScore, getDeviceHash } = __internal;
37
39
  const { getRenderingAnomalyScore } = __internal;
38
- let { getBehaviorScore, getClickVarianceScore } = __internal;
39
- // Mock the entire dns module
40
- vi.mock('node:dns/promises');
40
+ let { getBehaviorScore, getClickVarianceScore } = __internal; // eslint-disable-line prefer-const
41
+
41
42
 
42
43
  describe('Fingerprint & PoW Security Suite', () => {
43
44
  test('cyrb53 should be deterministic', () => {
@@ -1764,6 +1765,92 @@ describe('Fingerprint & PoW Security Suite', () => {
1764
1765
  });
1765
1766
  });
1766
1767
  });
1768
+ describe('Googlebot Whitelisting (Node.js)', () => {
1769
+ const inMemoryStore = {
1770
+ _map: new Map(),
1771
+ async get(key) { return this._map.get(key); },
1772
+ async set(key, value) { this._map.set(key, value); },
1773
+ async has(key) { return this._map.has(key); },
1774
+ async delete(key) { this._map.delete(key); },
1775
+ };
1776
+
1777
+ const securityConfig = {
1778
+ weights: { headerAnomalyScore: 1.0, tlsSpoofingScore: 1.0 }, // Simple config for testing
1779
+ thresholds: { low: 20, medium: 45, high: 75, block: 95 },
1780
+ whitelist: [
1781
+ { userAgent: 'Googlebot', hostnameSuffix: '.googlebot.com' }
1782
+ ]
1783
+ };
1784
+
1785
+ let engine;
1786
+
1787
+ beforeEach(() => {
1788
+ inMemoryStore._map.clear();
1789
+ configureStore(inMemoryStore);
1790
+ vi.clearAllMocks(); // Clear mocks before each test
1791
+ engine = new FingerprintEngine(securityConfig);
1792
+ });
1793
+
1794
+ it('should correctly identify and allow a legitimate Googlebot request', async () => {
1795
+ const googleIp = '::ffff:66.249.66.39'; // An IP from the official list
1796
+ const googleHostname = 'crawl-66-249-66-39.googlebot.com';
1797
+ const googleUa = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
1798
+
1799
+ // Mock the DNS verification process
1800
+ vi.mocked(dns.reverse).mockResolvedValue([googleHostname]);
1801
+ vi.mocked(dns.resolve).mockResolvedValue([googleIp]);
1802
+
1803
+ const requestContext = {
1804
+ clientIp: googleIp,
1805
+ path: '/',
1806
+ headers: { 'user-agent': googleUa },
1807
+ isStatic: false,
1808
+ cookies: {},
1809
+ query: {},
1810
+ body: null,
1811
+ httpVersion: '1.1',
1812
+ rawHeaders: ['user-agent', googleUa]
1813
+ };
1814
+
1815
+ const decision = await engine.processRequest(requestContext);
1816
+
1817
+ expect(decision.action).toBe('next');
1818
+ expect(decision.score).toBe(0);
1819
+ expect(decision.vector.whitelisted).toBe(100);
1820
+ expect(decision.vector.type).toBe('bot');
1821
+
1822
+ expect(dns.reverse).toHaveBeenCalledWith(googleIp);
1823
+ expect(dns.resolve).toHaveBeenCalledWith(googleHostname);
1824
+ });
1825
+
1826
+ it('should challenge or block a request spoofing a Googlebot User-Agent from a non-Google IP', async () => {
1827
+ const fakeGoogleIp = '1.2.3.4';
1828
+ const googleUa = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
1829
+
1830
+ vi.mocked(dns.reverse).mockRejectedValue(new Error('Host not found'));
1831
+
1832
+ const requestContext = {
1833
+ clientIp: fakeGoogleIp,
1834
+ path: '/',
1835
+ headers: { 'user-agent': googleUa, 'accept-language': 'en-US' },
1836
+ isStatic: false,
1837
+ cookies: {},
1838
+ query: {},
1839
+ body: null,
1840
+ httpVersion: '1.1',
1841
+ rawHeaders: ['user-agent', googleUa, 'accept-language', 'en-US']
1842
+ };
1843
+
1844
+ const decision = await engine.processRequest(requestContext);
1845
+
1846
+ expect(decision.action).toBe('block');
1847
+ expect(decision.score).toBeGreaterThan(0);
1848
+ expect(decision.vector.whitelisted).toBeUndefined();
1849
+
1850
+ expect(dns.reverse).toHaveBeenCalledWith(fakeGoogleIp);
1851
+ expect(dns.resolve).not.toHaveBeenCalled();
1852
+ });
1853
+ });
1767
1854
 
1768
1855
  describe('TLS Session Resumption (Cookieless Identity)', () => {
1769
1856
  const inMemoryStore = {
@@ -2369,20 +2456,47 @@ describe('Subnet Scoring (Node.js)', () => {
2369
2456
  // 2. Some high scores, few devices. The subnet is calculated from the context.
2370
2457
  // The key is hardcoded here to match what the implementation would store.
2371
2458
  await inMemoryStore.set('subnet:10.0.0.0/24', {
2372
- highScoreCount: 5, // score += 5 * 2 = 10
2373
- deviceIds: ['d1', 'd2'], // count < 10, score += 0
2459
+ highScoreCount: 5,
2460
+ deviceIds: ['d1', 'd2'],
2374
2461
  });
2375
2462
  ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2376
- expect(subnetScore).toBe(58.6);
2463
+ expect(subnetScore).toBeGreaterThan(0);
2464
+ expect(subnetScore).toBeLessThanOrEqual(45.0); // Ambient score capped in Medium zone
2377
2465
 
2378
- // 3. Many devices and high scores
2466
+ // 3. Many devices and high scores, ambient score still capped at 45.0 (Medium asymptote)
2379
2467
  const deviceIds = Array.from({ length: 20 }, (_, i) => `d${i}`);
2380
2468
  await inMemoryStore.set('subnet:10.0.0.0/24', {
2381
- highScoreCount: 30, // score += min(40, 30 * 2) = 40
2382
- deviceIds: deviceIds, // count = 20. score += (20-10)*5 = 50
2469
+ highScoreCount: 30,
2470
+ deviceIds: deviceIds,
2383
2471
  });
2384
2472
  ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2385
- expect(subnetScore).toBe(99.4);
2473
+ expect(subnetScore).toBeLessThanOrEqual(45.0);
2474
+ expect(subnetScore).toBeGreaterThanOrEqual(30.0);
2475
+
2476
+ // 4. Nearby attacker in /30 (+45 boost), escalates score into High territory
2477
+ await inMemoryStore.set('subnet:10.0.0.0/24', {
2478
+ highScoreCount: 30,
2479
+ deviceIds: deviceIds,
2480
+ attackerIps: [{ ip: '10.0.0.26', lastSeen: Date.now(), score: 85 }]
2481
+ });
2482
+ ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2483
+ expect(subnetScore).toBeGreaterThanOrEqual(80.0);
2484
+
2485
+ // 5. Exact IP match (+55 boost), reaches 90-100
2486
+ await inMemoryStore.set('subnet:10.0.0.0/24', {
2487
+ highScoreCount: 30,
2488
+ deviceIds: deviceIds,
2489
+ attackerIps: [{ ip: '10.0.0.25', lastSeen: Date.now(), score: 95 }]
2490
+ });
2491
+ ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2492
+ expect(subnetScore).toBeGreaterThanOrEqual(90.0);
2493
+ });
2494
+
2495
+ it('getIpCommonPrefixLength should accurately calculate common prefix bits', () => {
2496
+ const { getIpCommonPrefixLength } = __internal;
2497
+ expect(getIpCommonPrefixLength('10.0.0.25', '10.0.0.25')).toBe(32);
2498
+ expect(getIpCommonPrefixLength('10.0.0.24', '10.0.0.25')).toBe(31);
2499
+ expect(getIpCommonPrefixLength('192.168.1.10', '192.168.2.10')).toBe(22);
2386
2500
  });
2387
2501
  });
2388
2502
 
@@ -138,5 +138,6 @@ describe('IP Reputation Local System (Node.js)', () => {
138
138
  }
139
139
  score = await __internal.getSubnetScore(mockContext);
140
140
  expect(score.subnetScore).toBeGreaterThan(0);
141
+ expect(score.subnetScore).toBeLessThanOrEqual(45.0); // Ambient cap
141
142
  });
142
143
  });
@@ -1,8 +1,8 @@
1
1
  import crypto from "node:crypto";
2
2
 
3
3
  /**
4
- * Classe de base abstraite pour définir des tâches uPoW basées sur l'entraînement
5
- * ou l'évaluation de modèles IA (PyTorch exportés au format ONNX).
4
+ * Abstract base class defining useful Proof-of-Work (uPoW) tasks based on ML training
5
+ * or inference validation (e.g. PyTorch models exported to ONNX format).
6
6
  */
7
7
  export class UpowModelTask {
8
8
  constructor(problemId, modelPath, config = {}) {
@@ -20,7 +20,7 @@ export class UpowModelTask {
20
20
  }
21
21
 
22
22
  /**
23
- * Récupère l'identifiant du problème.
23
+ * Returns the unique problem identifier.
24
24
  * @returns {string}
25
25
  */
26
26
  getProblemId() {
@@ -28,7 +28,7 @@ export class UpowModelTask {
28
28
  }
29
29
 
30
30
  /**
31
- * Récupère le chemin réseau du modèle ONNX.
31
+ * Returns the network asset path of the model.
32
32
  * @returns {string}
33
33
  */
34
34
  getModelAssetPath() {
@@ -36,18 +36,18 @@ export class UpowModelTask {
36
36
  }
37
37
 
38
38
  /**
39
- * Génère la charge utile de travail pour le client.
40
- * Envoie l'état actuel des poids du modèle et un lot de données (mini-batch) à traiter.
39
+ * Dispatches task workload to the client.
40
+ * Transmits current model weights and mini-batch sample data.
41
41
  * @param {number} suspicionFactor
42
- * @returns {object} Données à envoyer au client
42
+ * @returns {object} Task payload dispatched to client
43
43
  */
44
44
  dispatchTask(suspicionFactor) {
45
45
  throw new Error("Method 'dispatchTask(suspicionFactor)' must be implemented.");
46
46
  }
47
47
 
48
48
  /**
49
- * Vérifie la validité de la solution retournée par le client.
50
- * Implémente des gardes-fous contre l'empoisonnement (ex: vérification des gradients).
49
+ * Validates solution returned by the client.
50
+ * Implements anti-poisoning guards and gradient bounds checks.
51
51
  * @param {object} taskContext
52
52
  * @param {object} solution
53
53
  * @returns {boolean}
@@ -57,8 +57,7 @@ export class UpowModelTask {
57
57
  }
58
58
 
59
59
  /**
60
- * Intègre les gradients ou résultats validés dans le modèle global.
61
- * Par exemple : algorithme FedAvg (Federated Averaging).
60
+ * Integrates validated gradients into global model weights (e.g. FedAvg).
62
61
  * @param {object} solution
63
62
  */
64
63
  integrateSolution(solution) {
@@ -67,17 +66,17 @@ export class UpowModelTask {
67
66
  }
68
67
 
69
68
  /**
70
- * Exemple d'implémentation d'une tâche d'entraînement (Classifieur de requêtes suspectes)
69
+ * Example training task implementation (suspicious request classifier).
71
70
  */
72
71
  export class RequestClassifierTask extends UpowModelTask {
73
72
  constructor(modelPath, config) {
74
73
  super("request_classifier_nn", modelPath, config);
75
- // Poids initiaux du modèle (Réseau de neurones linéaire simple)
74
+ // Initial model weights (linear neural network)
76
75
  this.weights = [0.1, -0.2, 0.8, 0.5];
77
76
  }
78
77
 
79
78
  dispatchTask(suspicionFactor) {
80
- // Génération de données d'entraînement anonymisées pour le client
79
+ // Anonymized sample training batch dispatched to client
81
80
  const inputs = [
82
81
  [1.0, 0.5, 0.0, 1.2],
83
82
  [0.0, 1.0, -0.5, 0.8]
@@ -95,7 +94,7 @@ export class RequestClassifierTask extends UpowModelTask {
95
94
  if (!Array.isArray(gradients) || gradients.length !== this.weights.length) {
96
95
  return false;
97
96
  }
98
- // Anti-poisoning : s'assurer que les gradients ne contiennent pas de valeurs aberrantes (clipping)
97
+ // Anti-poisoning guard: ensure gradients do not exceed threshold norms (gradient clipping)
99
98
  const maxGradientNorm = 10.0;
100
99
  for (const grad of gradients) {
101
100
  if (typeof grad !== 'number' || isNaN(grad) || Math.abs(grad) > maxGradientNorm) {