@anonympins/fingerprint 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/composer.json +8 -1
- package/package.json +1 -1
- package/public/fingerprint-wordpress.zip +0 -0
- package/src/js/build-client.js +8 -10
- package/src/js/fingerprint.builder.js +37 -38
- package/src/js/fingerprint.client.js +1531 -1463
- package/src/js/fingerprint.client.obfuscated.js +1 -0
- package/src/js/fingerprint.js +152 -10
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +340 -376
- package/src/js/mongodb-store.js +1 -1
- package/src/js/optimization.worker.js +27 -27
- package/src/js/pow.solver.inline.js +154 -56
- package/src/js/pow.solver.js +180 -92
- package/src/js/pow.worker.js +48 -48
- package/src/js/redis-store.js +1 -1
- package/src/js/tests/fingerprint.client.init.test.js +146 -143
- package/src/js/tests/fingerprint.test.js +34 -7
- package/src/js/tests/ip-reputation.test.js +1 -0
- package/src/js/upow-model-task.js +14 -15
- package/src/php/AutoTuner.php +34 -34
- package/src/php/Challenge/ChallengeUtils.php +56 -10
- package/src/php/Config/SecurityProfiles.php +20 -0
- package/src/php/DirectFingerprint.php +190 -145
- package/src/php/FingerprintBuilder.php +29 -30
- package/src/php/FingerprintClient.php +147 -148
- package/src/php/FingerprintEngine.php +7 -5
- package/src/php/Ja3AnomalyDetector.php +33 -32
- package/src/php/Optimization/FunctionRegistry.php +7 -7
- package/src/php/Optimization/Optimization.php +24 -24
- package/src/php/Optimization/OptimizationOperators.php +26 -25
- package/src/php/Optimization/ProblemInitializers.php +52 -52
- package/src/php/ProblemManager.php +31 -34
- package/src/php/RequestContext.php +8 -1
- package/src/php/Store/IStore.php +7 -8
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +5 -5
- package/src/php/Store/RedisStore.php +5 -5
- package/src/php/Store/StoreManager.php +35 -35
- package/src/php/Tests/ChallengeUtilsTest.php +453 -453
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintEngineTest.php +662 -662
- package/src/php/Tests/IpReputationTest.php +10 -0
- package/src/php/Tests/MaliciousPatternsTest.php +103 -103
- package/src/php/Tests/MetricsTest.php +97 -97
- package/src/php/Tests/ProblemManagerTest.php +376 -376
- package/src/php/Tests/QuicFingerprintTest.php +53 -53
- package/src/php/Tests/RequestUtilsTest.php +471 -471
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
- package/src/php/Tests/config/ed25519_key.json +3 -3
- package/src/php/Utils/Env.php +49 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +129 -7
- package/src/php/Utils/TLSClientHelloParser.php +369 -369
- package/src/php/WordPress/WpDbStore.php +155 -0
- package/src/php/WordPress/fingerprint-wordpress.php +217 -0
- package/src/php/WordPress/package.php +123 -0
- package/src/php/bin/auto-tune.php +118 -118
package/src/js/redis-store.js
CHANGED
|
@@ -35,7 +35,7 @@ export function createRedisStore(redisClient) {
|
|
|
35
35
|
reconnecting = false;
|
|
36
36
|
clearInterval(interval);
|
|
37
37
|
} catch (err) {
|
|
38
|
-
//
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
expect(
|
|
60
|
-
expect(
|
|
61
|
-
expect(
|
|
62
|
-
expect(
|
|
63
|
-
expect(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
expect(
|
|
72
|
-
expect(
|
|
73
|
-
expect(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
expect(
|
|
141
|
-
expect(
|
|
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
|
});
|
|
@@ -2369,20 +2369,47 @@ describe('Subnet Scoring (Node.js)', () => {
|
|
|
2369
2369
|
// 2. Some high scores, few devices. The subnet is calculated from the context.
|
|
2370
2370
|
// The key is hardcoded here to match what the implementation would store.
|
|
2371
2371
|
await inMemoryStore.set('subnet:10.0.0.0/24', {
|
|
2372
|
-
highScoreCount: 5,
|
|
2373
|
-
deviceIds: ['d1', 'd2'],
|
|
2372
|
+
highScoreCount: 5,
|
|
2373
|
+
deviceIds: ['d1', 'd2'],
|
|
2374
2374
|
});
|
|
2375
2375
|
({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
|
|
2376
|
-
expect(subnetScore).
|
|
2376
|
+
expect(subnetScore).toBeGreaterThan(0);
|
|
2377
|
+
expect(subnetScore).toBeLessThanOrEqual(45.0); // Ambient score capped in Medium zone
|
|
2377
2378
|
|
|
2378
|
-
// 3. Many devices and high scores
|
|
2379
|
+
// 3. Many devices and high scores, ambient score still capped at 45.0 (Medium asymptote)
|
|
2379
2380
|
const deviceIds = Array.from({ length: 20 }, (_, i) => `d${i}`);
|
|
2380
2381
|
await inMemoryStore.set('subnet:10.0.0.0/24', {
|
|
2381
|
-
highScoreCount: 30,
|
|
2382
|
-
deviceIds: deviceIds,
|
|
2382
|
+
highScoreCount: 30,
|
|
2383
|
+
deviceIds: deviceIds,
|
|
2383
2384
|
});
|
|
2384
2385
|
({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
|
|
2385
|
-
|
|
2386
|
+
expect(subnetScore).toBeLessThanOrEqual(45.0);
|
|
2387
|
+
expect(subnetScore).toBeGreaterThanOrEqual(30.0);
|
|
2388
|
+
|
|
2389
|
+
// 4. Nearby attacker in /30 (+45 boost), escalates score into High territory
|
|
2390
|
+
await inMemoryStore.set('subnet:10.0.0.0/24', {
|
|
2391
|
+
highScoreCount: 30,
|
|
2392
|
+
deviceIds: deviceIds,
|
|
2393
|
+
attackerIps: [{ ip: '10.0.0.26', lastSeen: Date.now(), score: 85 }]
|
|
2394
|
+
});
|
|
2395
|
+
({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
|
|
2396
|
+
expect(subnetScore).toBeGreaterThanOrEqual(80.0);
|
|
2397
|
+
|
|
2398
|
+
// 5. Exact IP match (+55 boost), reaches 90-100
|
|
2399
|
+
await inMemoryStore.set('subnet:10.0.0.0/24', {
|
|
2400
|
+
highScoreCount: 30,
|
|
2401
|
+
deviceIds: deviceIds,
|
|
2402
|
+
attackerIps: [{ ip: '10.0.0.25', lastSeen: Date.now(), score: 95 }]
|
|
2403
|
+
});
|
|
2404
|
+
({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
|
|
2405
|
+
expect(subnetScore).toBeGreaterThanOrEqual(90.0);
|
|
2406
|
+
});
|
|
2407
|
+
|
|
2408
|
+
it('getIpCommonPrefixLength should accurately calculate common prefix bits', () => {
|
|
2409
|
+
const { getIpCommonPrefixLength } = __internal;
|
|
2410
|
+
expect(getIpCommonPrefixLength('10.0.0.25', '10.0.0.25')).toBe(32);
|
|
2411
|
+
expect(getIpCommonPrefixLength('10.0.0.24', '10.0.0.25')).toBe(31);
|
|
2412
|
+
expect(getIpCommonPrefixLength('192.168.1.10', '192.168.2.10')).toBe(22);
|
|
2386
2413
|
});
|
|
2387
2414
|
});
|
|
2388
2415
|
|
|
@@ -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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
40
|
-
*
|
|
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}
|
|
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
|
-
*
|
|
50
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 :
|
|
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) {
|