@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.
- package/CHANGELOG.md +59 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/public/anonympins-bot-mitigation-pow.zip +0 -0
- package/src/js/dynamic-wasm.js +218 -218
- package/src/js/fingerprint.client.js +1784 -1738
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +489 -99
- package/src/js/fingerprint.utils.js +252 -2
- package/src/js/pow.worker.js +2 -2
- package/src/js/tests/cross_parity.test.js +3 -3
- package/src/js/tests/dns-circuit-breaker.test.js +6 -6
- package/src/js/tests/dynamic-wasm.test.js +77 -77
- package/src/js/tests/fingerprint.builder.test.js +76 -78
- package/src/js/tests/fingerprint.client.init.test.js +78 -0
- package/src/js/tests/fingerprint.client.test.js +16 -17
- package/src/js/tests/fingerprint.test.js +37 -12
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
- package/src/js/tests/library.test.js +94 -95
- package/src/js/tests/obfuscation.test.js +8 -8
- package/src/js/tests/quicFingerprint.test.js +47 -0
- package/src/js/tests/tcpFingerprint.test.js +4 -4
- package/src/js/tests/threatIntel.test.js +40 -1
- package/src/js/upow-model-task.js +155 -112
- package/src/php/Challenge/ChallengeUtils.php +392 -86
- package/src/php/Config/SecurityProfiles.php +79 -61
- package/src/php/DirectFingerprint.php +55 -8
- package/src/php/FingerprintBuilder.php +13 -13
- package/src/php/FingerprintClient.php +6 -6
- package/src/php/FingerprintEngine.php +2122 -1690
- package/src/php/Ja3AnomalyDetector.php +22 -22
- package/src/php/Optimization/FunctionRegistry.php +4 -4
- package/src/php/Optimization/Optimization.php +6 -6
- package/src/php/RequestContext.php +11 -11
- package/src/php/Store/InMemoryStore.php +96 -66
- package/src/php/Store/MongoDbStore.php +2 -2
- package/src/php/Store/RedisStore.php +1 -1
- package/src/php/Store/StoreManager.php +48 -35
- package/src/php/Tests/ChallengeUtilsTest.php +31 -0
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintClientTest.php +70 -70
- package/src/php/Tests/PowTest.php +37 -39
- package/src/php/Tests/QuicFingerprintTest.php +115 -53
- package/src/php/Tests/ThreatIntelTest.php +34 -0
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/BlockList.php +13 -13
- package/src/php/Utils/Logger.php +19 -1
- package/src/php/Utils/MaliciousPatterns.php +6 -6
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +362 -1
- package/src/php/Utils/TLSClientHelloParser.php +385 -369
- package/src/php/WordPress/WpDbStore.php +7 -7
- package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1121 -0
- package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
- package/src/php/WordPress/package.php +80 -101
- package/src/php/WordPress/readme.txt +21 -5
- package/src/php/bin/auto-tune.php +1 -1
- package/public/fingerprint-anti-bot.zip +0 -0
|
@@ -1,79 +1,77 @@
|
|
|
1
|
-
import {describe, expect, it} from 'vitest';
|
|
2
|
-
import {FingerprintBuilder} from '../fingerprint.builder.js';
|
|
3
|
-
|
|
4
|
-
describe('FingerprintBuilder.compare', () => {
|
|
5
|
-
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
const realisticOriginalFp = new FingerprintBuilder()
|
|
9
|
-
.add('cvs', 'mock-canvas-data-v1')
|
|
10
|
-
.add('gpu', 'ANGLE (NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0)')
|
|
11
|
-
.add('hw', '16_8_0') // 16 cores, 8GB RAM, no touch
|
|
12
|
-
.add('os', 'Win32')
|
|
13
|
-
.add('ua', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36')
|
|
14
|
-
.add('ja3', '771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0')
|
|
15
|
-
//
|
|
16
|
-
.add('cookie_keys', '_ga,session,device_id')
|
|
17
|
-
.add('upgrade', '1')
|
|
18
|
-
.toString();
|
|
19
|
-
|
|
20
|
-
it('should return a similarity score of 1.0 for the same device with minor volatile changes', () => {
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.add('
|
|
27
|
-
.add('
|
|
28
|
-
.add('
|
|
29
|
-
.add('
|
|
30
|
-
.add('
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.add('
|
|
48
|
-
.add('
|
|
49
|
-
.add('
|
|
50
|
-
.add('
|
|
51
|
-
.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
expect(FingerprintBuilder.compare(realisticOriginalFp,
|
|
63
|
-
expect(FingerprintBuilder.compare(
|
|
64
|
-
expect(FingerprintBuilder.compare(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
expect(similarity).toBeLessThan(1);
|
|
78
|
-
});
|
|
1
|
+
import {describe, expect, it} from 'vitest';
|
|
2
|
+
import {FingerprintBuilder} from '../fingerprint.builder.js';
|
|
3
|
+
|
|
4
|
+
describe('FingerprintBuilder.compare', () => {
|
|
5
|
+
|
|
6
|
+
// Realistic fingerprint for a legitimate user (e.g. Chrome on Windows)
|
|
7
|
+
// Recorded during baseline initial visit.
|
|
8
|
+
const realisticOriginalFp = new FingerprintBuilder()
|
|
9
|
+
.add('cvs', 'mock-canvas-data-v1')
|
|
10
|
+
.add('gpu', 'ANGLE (NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0)')
|
|
11
|
+
.add('hw', '16_8_0') // 16 cores, 8GB RAM, no touch
|
|
12
|
+
.add('os', 'Win32')
|
|
13
|
+
.add('ua', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36')
|
|
14
|
+
.add('ja3', '771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0')
|
|
15
|
+
// Volatile headers that may legitimately change during challenge solving
|
|
16
|
+
.add('cookie_keys', '_ga,session,device_id')
|
|
17
|
+
.add('upgrade', '1')
|
|
18
|
+
.toString();
|
|
19
|
+
|
|
20
|
+
it('should return a similarity score of 1.0 for the same device with minor volatile changes', () => {
|
|
21
|
+
// Success scenario: user solves a challenge.
|
|
22
|
+
// Solver fingerprint is identical except for volatile headers
|
|
23
|
+
// (such as cookie presence or upgrade-insecure-requests) which compare ignores.
|
|
24
|
+
const realisticSolverFp = new FingerprintBuilder()
|
|
25
|
+
.add('cvs', 'mock-canvas-data-v1') // Identique
|
|
26
|
+
.add('gpu', 'ANGLE (NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0)') // Identique
|
|
27
|
+
.add('hw', '16_8_0') // Identique
|
|
28
|
+
.add('os', 'Win32') // Identique
|
|
29
|
+
.add('ua', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36') // Identique
|
|
30
|
+
.add('ja3', '771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0') // Identique
|
|
31
|
+
// 'cookie_keys' is omitted, simulating a request without cookies
|
|
32
|
+
// 'upgrade' header is also omitted
|
|
33
|
+
.toString();
|
|
34
|
+
|
|
35
|
+
const similarity = FingerprintBuilder.compare(realisticOriginalFp, realisticSolverFp);
|
|
36
|
+
|
|
37
|
+
// Similarity remains 1.0 because all differences reside in ignored volatile keys
|
|
38
|
+
expect(similarity).toBe(1.0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return a low similarity score for two completely different devices', () => {
|
|
42
|
+
// Failure scenario: attacker steals device_id cookie and attempts
|
|
43
|
+
// challenge resolution from a different machine (e.g. Linux VM with Firefox).
|
|
44
|
+
const differentSolverFp = new FingerprintBuilder()
|
|
45
|
+
.add('cvs', 'different-canvas-data') // Différent
|
|
46
|
+
.add('gpu', 'llvmpipe (LLVM 15.0.7, 256 bits)') // Différent (GPU de VM)
|
|
47
|
+
.add('hw', '8_4_0') // Différent
|
|
48
|
+
.add('os', 'Linux x86_64') // Différent
|
|
49
|
+
.add('ua', 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0') // Différent
|
|
50
|
+
.add('ja3', '771,49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,65281-11-10-35-16-5-13-51-45-43-27-23-17513,29-23-24,0') // Différent (JA3 de Firefox)
|
|
51
|
+
.toString();
|
|
52
|
+
|
|
53
|
+
const similarity = FingerprintBuilder.compare(realisticOriginalFp, differentSolverFp);
|
|
54
|
+
|
|
55
|
+
// Similarity should be very low (close to 0) since invariant signals differ
|
|
56
|
+
expect(similarity).toBeLessThan(0.1);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return 0 if one of the fingerprints is null or empty', () => {
|
|
60
|
+
expect(FingerprintBuilder.compare(realisticOriginalFp, null)).toBe(0);
|
|
61
|
+
expect(FingerprintBuilder.compare(null, realisticOriginalFp)).toBe(0);
|
|
62
|
+
expect(FingerprintBuilder.compare(realisticOriginalFp, '')).toBe(0);
|
|
63
|
+
expect(FingerprintBuilder.compare('', realisticOriginalFp)).toBe(0);
|
|
64
|
+
expect(FingerprintBuilder.compare(null, null)).toBe(0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle fingerprints with missing components gracefully', () => {
|
|
68
|
+
const partialFp = new FingerprintBuilder()
|
|
69
|
+
.add('ua', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36')
|
|
70
|
+
.add('os', 'Win32')
|
|
71
|
+
.toString(); // Missing GPU, canvas, etc.
|
|
72
|
+
|
|
73
|
+
const similarity = FingerprintBuilder.compare(realisticOriginalFp, partialFp);
|
|
74
|
+
expect(similarity).toBeGreaterThan(0);
|
|
75
|
+
expect(similarity).toBeLessThan(1);
|
|
76
|
+
});
|
|
79
77
|
});
|
|
@@ -21,6 +21,7 @@ if (typeof globalThis.crypto !== 'undefined') {
|
|
|
21
21
|
Object.defineProperty(dom.window, 'crypto', { value: globalThis.crypto, writable: true, configurable: true });
|
|
22
22
|
}
|
|
23
23
|
Object.defineProperty(global, 'CustomEvent', { value: dom.window.CustomEvent, writable: true, configurable: true });
|
|
24
|
+
Object.defineProperty(global, 'Event', { value: dom.window.Event, writable: true, configurable: true });
|
|
24
25
|
|
|
25
26
|
global.fetch = vi.fn(); // Mock global fetch
|
|
26
27
|
window.fetch = (...args) => global.fetch(...args);
|
|
@@ -174,4 +175,81 @@ describe('ClientLibrary.initializeClient', () => {
|
|
|
174
175
|
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Challenge retry limit reached (3)'));
|
|
175
176
|
});
|
|
176
177
|
|
|
178
|
+
it('should inject phantom traps within closed shadow root and fire trigger on interaction', () => {
|
|
179
|
+
const onTriggerSpy = vi.spyOn(ClientLibrary, 'onHoneypotTrigger');
|
|
180
|
+
let capturedShadow = null;
|
|
181
|
+
const origAttachShadow = dom.window.Element.prototype.attachShadow;
|
|
182
|
+
vi.spyOn(dom.window.Element.prototype, 'attachShadow').mockImplementation(function (init) {
|
|
183
|
+
capturedShadow = origAttachShadow.call(this, init);
|
|
184
|
+
return capturedShadow;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Ensure document.body exists
|
|
188
|
+
document.body.innerHTML = '';
|
|
189
|
+
|
|
190
|
+
ClientLibrary.injectPhantomTraps();
|
|
191
|
+
|
|
192
|
+
const host = document.body.lastElementChild;
|
|
193
|
+
expect(host).not.toBeNull();
|
|
194
|
+
expect(host.getAttribute('aria-hidden')).toBe('true');
|
|
195
|
+
// Under closed mode, host.shadowRoot is inaccessible (null) from external callers
|
|
196
|
+
expect(host.shadowRoot).toBeNull();
|
|
197
|
+
expect(capturedShadow).not.toBeNull();
|
|
198
|
+
|
|
199
|
+
// Dispatch events on attached phantom elements inside closed shadow root
|
|
200
|
+
const link = capturedShadow.querySelector('a');
|
|
201
|
+
expect(link).not.toBeNull();
|
|
202
|
+
link.dispatchEvent(new Event('focus'));
|
|
203
|
+
expect(onTriggerSpy).toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should inject trap links within closed shadow root and trigger on honeypot click', () => {
|
|
207
|
+
const onTriggerSpy = vi.spyOn(ClientLibrary, 'onHoneypotTrigger');
|
|
208
|
+
let capturedShadow = null;
|
|
209
|
+
const origAttachShadow = dom.window.Element.prototype.attachShadow;
|
|
210
|
+
vi.spyOn(dom.window.Element.prototype, 'attachShadow').mockImplementation(function (init) {
|
|
211
|
+
capturedShadow = origAttachShadow.call(this, init);
|
|
212
|
+
return capturedShadow;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
document.body.innerHTML = '';
|
|
216
|
+
|
|
217
|
+
ClientLibrary.injectTrapLinks(['/trap-url-1', '/trap-url-2']);
|
|
218
|
+
|
|
219
|
+
const host = document.body.lastElementChild;
|
|
220
|
+
expect(host).not.toBeNull();
|
|
221
|
+
expect(host.getAttribute('aria-hidden')).toBe('true');
|
|
222
|
+
expect(host.shadowRoot).toBeNull(); // Closed Shadow DOM verification
|
|
223
|
+
expect(capturedShadow).not.toBeNull();
|
|
224
|
+
|
|
225
|
+
const link = capturedShadow.querySelector('a');
|
|
226
|
+
expect(link).not.toBeNull();
|
|
227
|
+
link.dispatchEvent(new Event('click'));
|
|
228
|
+
expect(onTriggerSpy).toHaveBeenCalled();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should initialize honeypot form inputs within closed shadow root', () => {
|
|
232
|
+
const onTriggerSpy = vi.spyOn(ClientLibrary, 'onHoneypotTrigger');
|
|
233
|
+
let capturedShadow = null;
|
|
234
|
+
const origAttachShadow = dom.window.Element.prototype.attachShadow;
|
|
235
|
+
vi.spyOn(dom.window.Element.prototype, 'attachShadow').mockImplementation(function (init) {
|
|
236
|
+
capturedShadow = origAttachShadow.call(this, init);
|
|
237
|
+
return capturedShadow;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
document.body.innerHTML = '';
|
|
241
|
+
|
|
242
|
+
ClientLibrary.initializeHoneypots(['bot_field_trap']);
|
|
243
|
+
|
|
244
|
+
const host = document.body.lastElementChild;
|
|
245
|
+
expect(host).not.toBeNull();
|
|
246
|
+
expect(host.getAttribute('aria-hidden')).toBe('true');
|
|
247
|
+
expect(host.shadowRoot).toBeNull();
|
|
248
|
+
expect(capturedShadow).not.toBeNull();
|
|
249
|
+
|
|
250
|
+
const input = capturedShadow.querySelector('input');
|
|
251
|
+
expect(input).not.toBeNull();
|
|
252
|
+
input.dispatchEvent(new Event('input'));
|
|
253
|
+
expect(onTriggerSpy).toHaveBeenCalled();
|
|
254
|
+
});
|
|
177
255
|
});
|
|
@@ -7,12 +7,12 @@ import ClientLibrary from '../fingerprint.client.js';
|
|
|
7
7
|
|
|
8
8
|
describe('ClientLibrary WASM Integration', () => {
|
|
9
9
|
beforeEach(() => {
|
|
10
|
-
//
|
|
10
|
+
// Reset cache and spies before each test
|
|
11
11
|
ClientLibrary._resetCache();
|
|
12
12
|
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
13
13
|
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// Remove global mocks to prevent leakage across tests
|
|
16
16
|
delete window.createFingerprintModule;
|
|
17
17
|
});
|
|
18
18
|
|
|
@@ -21,28 +21,27 @@ describe('ClientLibrary WASM Integration', () => {
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('should successfully load WASM module and switch hasher', async () => {
|
|
24
|
-
// 1.
|
|
24
|
+
// 1. Mock functional WASM module
|
|
25
25
|
const mockWasmModule = {
|
|
26
|
-
_hash_string: vi.fn((str) => 99999), //
|
|
26
|
+
_hash_string: vi.fn((str) => 99999), // Distinct mock hash value
|
|
27
27
|
_malloc: vi.fn().mockReturnValue(0),
|
|
28
28
|
HEAPU8: new Uint8Array(4096)
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
// 2.
|
|
32
|
-
// On attache les fonctions de simulation à `window` car c'est ce que le code cherche
|
|
31
|
+
// 2. Mock script loading and module factory
|
|
33
32
|
window.createFingerprintModule = vi.fn().mockResolvedValue(mockWasmModule);
|
|
34
33
|
|
|
35
|
-
//
|
|
34
|
+
// Mock script element injection with direct onload callback
|
|
36
35
|
vi.spyOn(document.head, 'appendChild').mockImplementation((script) => {
|
|
37
|
-
//
|
|
36
|
+
// Simulate successful script load
|
|
38
37
|
script.onload();
|
|
39
38
|
return script;
|
|
40
39
|
});
|
|
41
40
|
|
|
42
|
-
// 3.
|
|
41
|
+
// 3. Initialize WASM module
|
|
43
42
|
await ClientLibrary.initializeWasm('/fake/path/to/fp.js');
|
|
44
43
|
|
|
45
|
-
// 4.
|
|
44
|
+
// 4. Verify hasher was switched
|
|
46
45
|
const wasmHash = ClientLibrary._hasher("test");
|
|
47
46
|
expect(wasmHash).toBe(99999);
|
|
48
47
|
expect(mockWasmModule._hash_string).toHaveBeenCalledWith(0);
|
|
@@ -50,16 +49,16 @@ describe('ClientLibrary WASM Integration', () => {
|
|
|
50
49
|
});
|
|
51
50
|
|
|
52
51
|
it('should gracefully fall back to JS hasher if WASM module fails to load', async () => {
|
|
53
|
-
// 1.
|
|
52
|
+
// 1. Simulate script loading failure
|
|
54
53
|
vi.spyOn(document.head, 'appendChild').mockImplementation((script) => {
|
|
55
54
|
script.onerror(new Error('Script loading failed'));
|
|
56
55
|
return script;
|
|
57
56
|
});
|
|
58
57
|
|
|
59
|
-
// 2.
|
|
58
|
+
// 2. Initialize WASM module
|
|
60
59
|
await ClientLibrary.initializeWasm('/fake/path/to/fp.js');
|
|
61
60
|
|
|
62
|
-
// 3.
|
|
61
|
+
// 3. Verify hasher falls back to JS implementation
|
|
63
62
|
const jsHash = ClientLibrary._hasher("test");
|
|
64
63
|
const originalJsHash = (await import('../fingerprint.builder.js')).cyrb53("test");
|
|
65
64
|
|
|
@@ -68,7 +67,7 @@ describe('ClientLibrary WASM Integration', () => {
|
|
|
68
67
|
});
|
|
69
68
|
|
|
70
69
|
it('should gracefully fall back if WASM module does not export _hash_string', async () => {
|
|
71
|
-
// 1.
|
|
70
|
+
// 1. Simulate malformed WASM module missing required export
|
|
72
71
|
const mockWasmModule = {};
|
|
73
72
|
window.createFingerprintModule = vi.fn().mockResolvedValue(mockWasmModule);
|
|
74
73
|
|
|
@@ -77,14 +76,14 @@ describe('ClientLibrary WASM Integration', () => {
|
|
|
77
76
|
return script;
|
|
78
77
|
});
|
|
79
78
|
|
|
80
|
-
// 2.
|
|
79
|
+
// 2. Initialize WASM module
|
|
81
80
|
await ClientLibrary.initializeWasm('/fake/path/to/fp.js');
|
|
82
81
|
|
|
83
|
-
// 3.
|
|
82
|
+
// 3. Verify fallback
|
|
84
83
|
const jsHash = ClientLibrary._hasher("test");
|
|
85
84
|
const originalJsHash = (await import('../fingerprint.builder.js')).cyrb53("test");
|
|
86
85
|
expect(jsHash).toBe(originalJsHash);
|
|
87
|
-
//
|
|
86
|
+
// Verify both generic error log and specific missing export cause
|
|
88
87
|
expect(console.warn).toHaveBeenCalledWith(
|
|
89
88
|
expect.stringContaining('WASM module failed to load'),
|
|
90
89
|
expect.objectContaining({ message: 'WASM module did not export _hash_string.' })
|
|
@@ -1621,18 +1621,6 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
1621
1621
|
expect(behaviorScore).toBe(0);
|
|
1622
1622
|
});
|
|
1623
1623
|
|
|
1624
|
-
it('should return a score of 0 when x-behavior-metrics is 1 (human verified client-side)', () => {
|
|
1625
|
-
const context = { headers: { 'x-behavior-metrics': '1' } };
|
|
1626
|
-
const { behaviorScore } = getBehaviorScore(context);
|
|
1627
|
-
expect(behaviorScore).toBe(0);
|
|
1628
|
-
});
|
|
1629
|
-
|
|
1630
|
-
it('should return a score of 100 when x-behavior-metrics is 0 (bot detected client-side)', () => {
|
|
1631
|
-
const context = { headers: { 'x-behavior-metrics': '0' } };
|
|
1632
|
-
const { behaviorScore } = getBehaviorScore(context);
|
|
1633
|
-
expect(behaviorScore).toBe(100);
|
|
1634
|
-
});
|
|
1635
|
-
|
|
1636
1624
|
it('should return a score of 100 for honeypot interaction', () => {
|
|
1637
1625
|
const metrics = { honeypotInteraction: true, mouseEntropy: 50, keystrokeLatency: 120 };
|
|
1638
1626
|
const context = { headers: { 'x-behavior-metrics': JSON.stringify(metrics) } };
|
|
@@ -3199,5 +3187,42 @@ describe('IP Registration and Filtering', () => {
|
|
|
3199
3187
|
expect(process.env.ED25519_PRIVATE_KEY).toBe(dummyKeys.privateKey);
|
|
3200
3188
|
expect(process.env.ED25519_PUBLIC_KEY).toBe(dummyKeys.publicKey);
|
|
3201
3189
|
});
|
|
3190
|
+
|
|
3191
|
+
it('should generate issuer PEM files directly via generateIssuerPemKeys', async () => {
|
|
3192
|
+
const fs = await import('node:fs');
|
|
3193
|
+
const os = await import('node:os');
|
|
3194
|
+
const path = await import('node:path');
|
|
3195
|
+
const tempDir = path.join(os.tmpdir(), `fp-issuer-keys-${Date.now()}`);
|
|
3196
|
+
|
|
3197
|
+
try {
|
|
3198
|
+
const keys = fingerprint.generateIssuerPemKeys(tempDir);
|
|
3199
|
+
expect(keys.privateKeyPath).toContain('issuer-private.pem');
|
|
3200
|
+
expect(keys.publicKeyPath).toContain('issuer-public.pem');
|
|
3201
|
+
expect(keys.privateKey).toContain('-----BEGIN PRIVATE KEY-----');
|
|
3202
|
+
expect(keys.publicKey).toContain('-----BEGIN PUBLIC KEY-----');
|
|
3203
|
+
expect(fs.existsSync(keys.privateKeyPath)).toBe(true);
|
|
3204
|
+
expect(fs.existsSync(keys.publicKeyPath)).toBe(true);
|
|
3205
|
+
} finally {
|
|
3206
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
3207
|
+
}
|
|
3208
|
+
});
|
|
3209
|
+
});
|
|
3210
|
+
|
|
3211
|
+
describe('Closed-Shadow DOM Honeypot Traps (Feature 19)', () => {
|
|
3212
|
+
it('should generate closed declarative shadow DOM trap container for trap URLs', () => {
|
|
3213
|
+
const trapUrls = ['/trap-1', '/trap-2'];
|
|
3214
|
+
const markup = __internal.generateClosedShadowTraps(trapUrls);
|
|
3215
|
+
|
|
3216
|
+
expect(markup).toContain('aria-hidden="true"');
|
|
3217
|
+
expect(markup).toContain('shadowrootmode="closed"');
|
|
3218
|
+
expect(markup).toContain('shadowroot="closed"');
|
|
3219
|
+
expect(markup).toContain('href="/trap-1"');
|
|
3220
|
+
expect(markup).toContain('href="/trap-2"');
|
|
3221
|
+
});
|
|
3222
|
+
|
|
3223
|
+
it('should return an empty string if trapUrls array is empty or invalid', () => {
|
|
3224
|
+
expect(__internal.generateClosedShadowTraps([])).toBe('');
|
|
3225
|
+
expect(__internal.generateClosedShadowTraps(null)).toBe('');
|
|
3226
|
+
});
|
|
3202
3227
|
});
|
|
3203
3228
|
});
|