@anonympins/fingerprint 0.7.4 → 0.7.5

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.
@@ -1,78 +1,78 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { DynamicWasmGenerator } from '../dynamic-wasm.js';
3
-
4
- // Helper function to decode ULEB128 (for verification)
5
- function decodeULEB128(bytes) {
6
- let result = 0;
7
- let shift = 0;
8
- for (const byte of bytes) {
9
- result |= (byte & 0x7f) << shift;
10
- if (!(byte & 0x80)) {
11
- break;
12
- }
13
- shift += 7;
14
- }
15
- return result;
16
- }
17
-
18
- describe('DynamicWasmGenerator', () => {
19
- it('should generate a valid WASM module buffer', () => {
20
- const constants = { seed: 123, multiplier: 33, adder: 7 };
21
- const wasmBuffer = DynamicWasmGenerator.generate(constants);
22
-
23
- // Basic check: ensure it's a Buffer and starts with WASM magic number
24
- expect(wasmBuffer).toBeInstanceOf(Buffer);
25
- expect(wasmBuffer.slice(0, 4).toString('hex')).toBe('0061736d'); // \0asm
26
- expect(wasmBuffer.slice(4, 8).toString('hex')).toBe('01000000'); // Version 1
27
-
28
- // Attempt to compile and instantiate the module to ensure validity
29
- let instance;
30
- try {
31
- const module = new WebAssembly.Module(wasmBuffer);
32
- instance = new WebAssembly.Instance(module, {});
33
- } catch (e) {
34
- // If compilation/instantiation fails, it's an invalid WASM module
35
- expect.fail(`Generated WASM module is invalid: ${e.message}`);
36
- }
37
-
38
- // Verify the exported hash function exists
39
- expect(instance.exports.hash).toBeInstanceOf(Function);
40
- expect(instance.exports.memory).toBeInstanceOf(WebAssembly.Memory);
41
- });
42
-
43
- it('should produce a consistent hash result with the JS fallback', () => {
44
- const constants = { seed: 42, multiplier: 1597334677, adder: 12345 };
45
- const testString = "hello world";
46
-
47
- const wasmBuffer = DynamicWasmGenerator.generate(constants);
48
- const module = new WebAssembly.Module(wasmBuffer);
49
- const instance = new WebAssembly.Instance(module, {});
50
- const exports = instance.exports;
51
- const memory = exports.memory;
52
-
53
- // Write string to WASM memory
54
- const encoder = new TextEncoder();
55
- const bytes = encoder.encode(testString);
56
- const view = new Uint8Array(memory.buffer);
57
- view.set(bytes, 0); // Assuming hash function expects string at address 0
58
-
59
- // Calculate hash using WASM
60
- const wasmHash = exports.hash(0, bytes.length);
61
-
62
- // Calculate hash using JS fallback
63
- const jsHash = DynamicWasmGenerator.hashJs(testString, constants);
64
-
65
- // The WASM and JS implementations should yield the same result
66
- expect(wasmHash).toBe(jsHash);
67
- });
68
-
69
- it('should generate different WASM modules for different constants', () => {
70
- const constants1 = { seed: 1, multiplier: 2, adder: 3 };
71
- const constants2 = { seed: 4, multiplier: 5, adder: 6 };
72
-
73
- const wasmBuffer1 = DynamicWasmGenerator.generate(constants1);
74
- const wasmBuffer2 = DynamicWasmGenerator.generate(constants2);
75
-
76
- expect(wasmBuffer1.toString('hex')).not.toBe(wasmBuffer2.toString('hex'));
77
- });
1
+ import { describe, expect, it } from 'vitest';
2
+ import { DynamicWasmGenerator } from '../dynamic-wasm.js';
3
+
4
+ // Helper function to decode ULEB128 (for verification)
5
+ function decodeULEB128(bytes) {
6
+ let result = 0;
7
+ let shift = 0;
8
+ for (const byte of bytes) {
9
+ result |= (byte & 0x7f) << shift;
10
+ if (!(byte & 0x80)) {
11
+ break;
12
+ }
13
+ shift += 7;
14
+ }
15
+ return result;
16
+ }
17
+
18
+ describe('DynamicWasmGenerator', () => {
19
+ it('should generate a valid WASM module buffer', () => {
20
+ const constants = { seed: 123, multiplier: 33, adder: 7 };
21
+ const wasmBuffer = DynamicWasmGenerator.generate(constants);
22
+
23
+ // Basic check: ensure it's a Buffer and starts with WASM magic number
24
+ expect(wasmBuffer).toBeInstanceOf(Buffer);
25
+ expect(wasmBuffer.slice(0, 4).toString('hex')).toBe('0061736d'); // \0asm
26
+ expect(wasmBuffer.slice(4, 8).toString('hex')).toBe('01000000'); // Version 1
27
+
28
+ // Attempt to compile and instantiate the module to ensure validity
29
+ let instance;
30
+ try {
31
+ const module = new WebAssembly.Module(wasmBuffer);
32
+ instance = new WebAssembly.Instance(module, {});
33
+ } catch (e) {
34
+ // If compilation/instantiation fails, it's an invalid WASM module
35
+ expect.fail(`Generated WASM module is invalid: ${e.message}`);
36
+ }
37
+
38
+ // Verify the exported hash function exists
39
+ expect(instance.exports.hash).toBeInstanceOf(Function);
40
+ expect(instance.exports.memory).toBeInstanceOf(WebAssembly.Memory);
41
+ });
42
+
43
+ it('should produce a consistent hash result with the JS fallback', () => {
44
+ const constants = { seed: 42, multiplier: 1597334677, adder: 12345 };
45
+ const testString = "hello world";
46
+
47
+ const wasmBuffer = DynamicWasmGenerator.generate(constants);
48
+ const module = new WebAssembly.Module(wasmBuffer);
49
+ const instance = new WebAssembly.Instance(module, {});
50
+ const exports = instance.exports;
51
+ const memory = exports.memory;
52
+
53
+ // Write string to WASM memory
54
+ const encoder = new TextEncoder();
55
+ const bytes = encoder.encode(testString);
56
+ const view = new Uint8Array(memory.buffer);
57
+ view.set(bytes, 0); // Assuming hash function expects string at address 0
58
+
59
+ // Calculate hash using WASM
60
+ const wasmHash = exports.hash(0, bytes.length);
61
+
62
+ // Calculate hash using JS fallback
63
+ const jsHash = DynamicWasmGenerator.hashJs(testString, constants);
64
+
65
+ // The WASM and JS implementations should yield the same result
66
+ expect(wasmHash).toBe(jsHash);
67
+ });
68
+
69
+ it('should generate different WASM modules for different constants', () => {
70
+ const constants1 = { seed: 1, multiplier: 2, adder: 3 };
71
+ const constants2 = { seed: 4, multiplier: 5, adder: 6 };
72
+
73
+ const wasmBuffer1 = DynamicWasmGenerator.generate(constants1);
74
+ const wasmBuffer2 = DynamicWasmGenerator.generate(constants2);
75
+
76
+ expect(wasmBuffer1.toString('hex')).not.toBe(wasmBuffer2.toString('hex'));
77
+ });
78
78
  });
@@ -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
  });
@@ -3200,4 +3200,22 @@ describe('IP Registration and Filtering', () => {
3200
3200
  expect(process.env.ED25519_PUBLIC_KEY).toBe(dummyKeys.publicKey);
3201
3201
  });
3202
3202
  });
3203
+
3204
+ describe('Closed-Shadow DOM Honeypot Traps (Feature 19)', () => {
3205
+ it('should generate closed declarative shadow DOM trap container for trap URLs', () => {
3206
+ const trapUrls = ['/trap-1', '/trap-2'];
3207
+ const markup = __internal.generateClosedShadowTraps(trapUrls);
3208
+
3209
+ expect(markup).toContain('aria-hidden="true"');
3210
+ expect(markup).toContain('shadowrootmode="closed"');
3211
+ expect(markup).toContain('shadowroot="closed"');
3212
+ expect(markup).toContain('href="/trap-1"');
3213
+ expect(markup).toContain('href="/trap-2"');
3214
+ });
3215
+
3216
+ it('should return an empty string if trapUrls array is empty or invalid', () => {
3217
+ expect(__internal.generateClosedShadowTraps([])).toBe('');
3218
+ expect(__internal.generateClosedShadowTraps(null)).toBe('');
3219
+ });
3220
+ });
3203
3221
  });
@@ -1,4 +1,4 @@
1
- import { beforeEach, describe, expect, it } from 'vitest';
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import { __internal, configureStore } from '../fingerprint.js';
3
3
 
4
4
  describe('Threat Intelligence - Rationale-Driven Defensive Logic', () => {
@@ -73,4 +73,43 @@ describe('Threat Intelligence - Rationale-Driven Defensive Logic', () => {
73
73
  expect(vector.threatIntelScore).toBeGreaterThan(70.0);
74
74
  expect(vector.threatIntelScore).toBeLessThanOrEqual(95.0);
75
75
  });
76
+
77
+ it('should broadcast banned ZKP with differential privacy (perturbed timestamps and decoy injection)', async () => {
78
+ const calls = [];
79
+ const originalFetch = globalThis.fetch;
80
+ globalThis.fetch = vi.fn(async (url, init) => {
81
+ calls.push({ url, init });
82
+ return { ok: true, json: async () => ({ status: 'synchronized' }) };
83
+ });
84
+
85
+ const config = {
86
+ federatedPeers: ['https://peer1.example.com', 'https://peer2.example.com'],
87
+ federationSecret: 'test-secret-key-32-chars-long!!',
88
+ differentialPrivacy: {
89
+ enabled: true,
90
+ epsilon: 1.0,
91
+ dummyRate: 1.0 // Force decoy generation for deterministic testing
92
+ }
93
+ };
94
+
95
+ const realZkpY = 'deadbeef12345678';
96
+ await __internal.broadcastBannedZkp(realZkpY, config);
97
+
98
+ // Should broadcast to 2 peers with real key + 2 peers with decoy key
99
+ expect(calls.length).toBe(4);
100
+
101
+ const bodies = calls.map(c => JSON.parse(c.init.body));
102
+ const zkpYs = bodies.map(b => b.zkpY);
103
+
104
+ expect(zkpYs).toContain(realZkpY);
105
+ const decoyKey = zkpYs.find(k => k !== realZkpY);
106
+ expect(decoyKey).toBeDefined();
107
+ expect(decoyKey).not.toBe(realZkpY);
108
+
109
+ for (const call of calls) {
110
+ const timestamp = Number(call.init.headers['X-Federation-Timestamp']);
111
+ expect(timestamp).toBeGreaterThan(0);
112
+ }
113
+ globalThis.fetch = originalFetch;
114
+ });
76
115
  });
@@ -605,6 +605,11 @@ class ChallengeUtils
605
605
 
606
606
  if (!$expiry || (int)floor(microtime(true) * 1000) > (int)$expiry) {
607
607
  return false;
608
+ }
609
+ if (!empty($ticketData['greenlist']) || str_starts_with((string)$storedDeviceHash, 'webauthn:greenlist:')) {
610
+ if (empty($deviceId) || $deviceId === $storedDeviceId) {
611
+ return true;
612
+ }
608
613
  }
609
614
  if ($storedDeviceHash && str_starts_with($storedDeviceHash, 'zkp:')) {
610
615
  $expectedY = explode(':', $storedDeviceHash, 2)[1] ?? '';
@@ -644,6 +649,11 @@ class ChallengeUtils
644
649
  if (!$expiry || (int)floor(microtime(true) * 1000) > (int)$expiry) {
645
650
  $store->delete("ticket:{$ticket}");
646
651
  return false;
652
+ }
653
+ if (!empty($ticketData['greenlist']) || str_starts_with((string)$storedDeviceHash, 'webauthn:greenlist:')) {
654
+ if (empty($deviceId) || $deviceId === $storedDeviceId) {
655
+ return true;
656
+ }
647
657
  }
648
658
  if ($storedDeviceHash && str_starts_with($storedDeviceHash, 'zkp:')) {
649
659
  $expectedY = explode(':', $storedDeviceHash, 2)[1] ?? '';
@@ -43,6 +43,7 @@ class SecurityProfiles
43
43
  'tcpAnomalyScore' => 0.8,
44
44
  'protocolAnomalyScore' => 0.8, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
45
45
  'renderingAnomalyScore' => 0.8, // NEW: Anomalie de rendu
46
+ 'mtuAnomalyScore' => 0.9, // NOUVEAU: Poids pour l'anomalie MTU/fragmentation
46
47
  'ipReputationScore' => 0.5, // NOUVEAU: Poids pour la réputation IP
47
48
  'virtualizationScore' => 0.8,
48
49
  ],
@@ -97,6 +98,7 @@ class SecurityProfiles
97
98
  'tcpAnomalyScore' => 1.0, // NEW: Anomalie de pile TCP/IP
98
99
  'protocolAnomalyScore' => 1.0, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
99
100
  'renderingAnomalyScore' => 1.0, // NEW: Anomalie de rendu
101
+ 'mtuAnomalyScore' => 0.9,
100
102
 
101
103
  ],
102
104
  'thresholds' => ['low' => 10, 'medium' => 35, 'high' => 65, 'block' => 90],
@@ -149,6 +151,7 @@ class SecurityProfiles
149
151
  'botnetClusterScore' => 0.7, // NOUVEAU: Poids pour le clustering botnet
150
152
  'tcpAnomalyScore' => 0.8, // NEW: Anomalie de pile TCP/IP
151
153
  'protocolAnomalyScore' => 0.8, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
154
+ 'mtuAnomalyScore' => 0.9,
152
155
 
153
156
  ],
154
157
  'thresholds' => ['low' => 25, 'medium' => 50, 'high' => 80, 'block' => 95],
@@ -205,6 +208,7 @@ class SecurityProfiles
205
208
  'tcpAnomalyScore' => 0.5, // NEW: Anomalie de pile TCP/IP
206
209
  'protocolAnomalyScore' => 0.5, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
207
210
  'renderingAnomalyScore' => 0.5, // NEW: Anomalie de rendu
211
+ 'mtuAnomalyScore' => 0.9,
208
212
 
209
213
  ],
210
214
  'thresholds' => ['low' => 25, 'medium' => 55, 'high' => 80, 'block' => 95],
@@ -259,6 +263,7 @@ class SecurityProfiles
259
263
  'tcpAnomalyScore' => 0.9, // NEW: Anomalie de pile TCP/IP
260
264
  'protocolAnomalyScore' => 0.9, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
261
265
  'renderingAnomalyScore' => 0.9, // NEW: Anomalie de rendu
266
+ 'mtuAnomalyScore' => 0.9,
262
267
 
263
268
  ],
264
269
  'thresholds' => ['low' => 15, 'medium' => 40, 'high' => 70, 'block' => 90],