@anonympins/fingerprint 0.4.6 → 0.5.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.
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { describe, expect, it, vi } from 'vitest';
6
+ import { GpuPowSolver } from '../gpu_pow.solver.js';
7
+
8
+ describe('GpuPowSolver - Chaotic Logistic Map PoW', () => {
9
+ const seed = 'chaotic-pow-seed-test';
10
+ const iterations = 1000;
11
+
12
+ it('should hash seed deterministically to a float between 0 and 1', () => {
13
+ const f1 = GpuPowSolver._hashSeedToFloat(seed);
14
+ const f2 = GpuPowSolver._hashSeedToFloat(seed);
15
+ const f3 = GpuPowSolver._hashSeedToFloat('another-seed');
16
+
17
+ expect(f1).toBe(f2);
18
+ expect(f1).toBeGreaterThanOrEqual(0);
19
+ expect(f1).toBeLessThan(1);
20
+ expect(f1).not.toBe(f3);
21
+ });
22
+
23
+ it('should verify a mathematically correct trajectory solution', () => {
24
+ const numericSeed = GpuPowSolver._hashSeedToFloat(seed);
25
+ const r = 3.9999;
26
+ const rFloat = Math.fround(r);
27
+ const solutions = [];
28
+
29
+ for (let idx = 0; idx < 64; idx++) {
30
+ let x = Math.fround(numericSeed + idx * 0.015);
31
+ for (let i = 0; i < iterations; i++) {
32
+ x = Math.fround(rFloat * x * Math.fround(1.0 - x));
33
+ }
34
+ solutions.push(x.toFixed(6));
35
+ }
36
+
37
+ const validSolution = solutions.join(',');
38
+
39
+ // Verify the correct solution
40
+ expect(GpuPowSolver.verify(seed, iterations, validSolution)).toBe(true);
41
+ });
42
+
43
+ it('should reject invalid solutions', () => {
44
+ expect(GpuPowSolver.verify(seed, iterations, '')).toBe(false);
45
+ expect(GpuPowSolver.verify(seed, iterations, null)).toBe(false);
46
+ expect(GpuPowSolver.verify(seed, iterations, '1.0,2.0')).toBe(false); // Wrong length (should be 64)
47
+ });
48
+
49
+ it('should reject tampered solutions exceeding the threshold', () => {
50
+ const numericSeed = GpuPowSolver._hashSeedToFloat(seed);
51
+ const r = 3.9999;
52
+ const rFloat = Math.fround(r);
53
+ const solutions = [];
54
+
55
+ for (let idx = 0; idx < 64; idx++) {
56
+ let x = Math.fround(numericSeed + idx * 0.015);
57
+ for (let i = 0; i < iterations; i++) {
58
+ x = Math.fround(rFloat * x * Math.fround(1.0 - x));
59
+ }
60
+ solutions.push(x.toFixed(6));
61
+ }
62
+
63
+ // Tamper with the 12th channel (one of the default sample indices [0, 12, 35, 57])
64
+ const tamperedSolutions = [...solutions];
65
+ const originalVal = parseFloat(tamperedSolutions[12]);
66
+ tamperedSolutions[12] = (originalVal + 0.0002).toFixed(6); // exceeding 1e-4 tolerance
67
+
68
+ expect(GpuPowSolver.verify(seed, iterations, tamperedSolutions.join(','))).toBe(false);
69
+ });
70
+
71
+ it('should fallback to WebGL2 if WebGPU is not supported', async () => {
72
+ // Mock WebGPU as undefined
73
+ const originalGpu = global.navigator.gpu;
74
+ Object.defineProperty(global.navigator, 'gpu', {
75
+ value: undefined,
76
+ writable: true,
77
+ configurable: true
78
+ });
79
+
80
+ // Mock WebGL2 context
81
+ const mockGl = {
82
+ useProgram: vi.fn(),
83
+ createBuffer: vi.fn(),
84
+ bindBuffer: vi.fn(),
85
+ bufferData: vi.fn(),
86
+ enableVertexAttribArray: vi.fn(),
87
+ vertexAttribPointer: vi.fn(),
88
+ uniform1f: vi.fn(),
89
+ uniform1i: vi.fn(),
90
+ getUniformLocation: vi.fn(),
91
+ getAttribLocation: vi.fn(),
92
+ drawArrays: vi.fn(),
93
+ readPixels: vi.fn((x, y, w, h, format, type, pixels) => {
94
+ // Populate mock pixels with arbitrary valid floats
95
+ for (let i = 0; i < pixels.length; i++) {
96
+ pixels[i] = 0.5;
97
+ }
98
+ })
99
+ };
100
+
101
+ const mockCanvas = {
102
+ getContext: vi.fn().mockReturnValue(mockGl)
103
+ };
104
+
105
+ vi.spyOn(document, 'createElement').mockImplementation((tagName) => {
106
+ if (tagName === 'canvas') return mockCanvas;
107
+ return {};
108
+ });
109
+
110
+ vi.spyOn(GpuPowSolver, '_createProgram').mockReturnValue({});
111
+
112
+ const result = await GpuPowSolver.solve(seed, 100);
113
+
114
+ expect(result.platform).toBe('webgl2');
115
+ expect(result.solution.split(',').length).toBe(64);
116
+ vi.restoreAllMocks();
117
+ });
118
+ });
@@ -1,6 +1,7 @@
1
1
  import crypto from 'crypto';
2
2
  import {getTlsSpoofingScore, parseJa3} from '../fingerprint.js';
3
3
  import {vi} from 'vitest';
4
+ import { verifyZkpProof, decodePolymorphicFingerprint, deepMerge, getHeaderSignature, parseJa3, modPow, hashNetwork, normalizeReferer, isPrivateIp, parseUserAgent } from "../../js/fingerprint.utils.js";
4
5
 
5
6
  describe('JA3 Anomaly Detector (Node.js)', () => {
6
7
 
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __internal } from '../fingerprint.js';
3
+
4
+ const { getQuicAnomalyScore } = __internal;
5
+
6
+ describe('QUIC / HTTP/3 Flow Profiling', () => {
7
+ it('should return 0.0 if no QUIC fingerprint is present', () => {
8
+ const context = { headers: {} };
9
+ const score = getQuicAnomalyScore(context);
10
+ expect(score.quicAnomalyScore).toBe(0.0);
11
+ });
12
+
13
+ it('should detect Chrome spoofed with non-default stream/priority values', () => {
14
+ // Chrome attend un initial_max_data (1) >= 1MB, initial_max_streams_bidi (4) == 100, et extensible Priority (contient u=)
15
+ const context = {
16
+ headers: {
17
+ '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',
18
+ 'x-quic-fp': '1;1=65536,4=50;i=0' // max_data trop bas, max_streams != 100, pas de priorité u=
19
+ }
20
+ };
21
+ const score = getQuicAnomalyScore(context);
22
+ expect(score.quicAnomalyScore).toBe(100.0);
23
+ });
24
+
25
+ it('should return 0.0 for a legitimate Chrome QUIC flow profile', () => {
26
+ const context = {
27
+ headers: {
28
+ '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',
29
+ 'x-quic-fp': '1;1=1572864,4=100;u=2,i'
30
+ }
31
+ };
32
+ const score = getQuicAnomalyScore(context);
33
+ expect(score.quicAnomalyScore).toBe(0.0);
34
+ });
35
+ });
@@ -25,6 +25,11 @@ class AutoTuner
25
25
 
26
26
  private int $minDataPoints;
27
27
  private int $maxDataPoints;
28
+ private ?int $maxAgeMs;
29
+ private bool $clearAfterTuning;
30
+ /** @var ?callable */
31
+ private $onCleanup;
32
+ private ?string $savePath;
28
33
 
29
34
  /**
30
35
  * @var ?array<string, mixed> La dernière meilleure solution trouvée par l'optimiseur.
@@ -42,6 +47,48 @@ class AutoTuner
42
47
  $this->trafficData = &$trafficData;
43
48
  $this->minDataPoints = $options['minDataPoints'] ?? 200;
44
49
  $this->maxDataPoints = $options['maxDataPoints'] ?? 10000;
50
+ $this->maxAgeMs = $options['maxAgeMs'] ?? null;
51
+ $this->clearAfterTuning = $options['clearAfterTuning'] ?? false;
52
+ $this->onCleanup = $options['onCleanup'] ?? null;
53
+ $this->savePath = $options['savePath'] ?? null;
54
+ }
55
+
56
+ /**
57
+ * Prunes old or excess traffic logs to prevent memory leaks.
58
+ */
59
+ private function pruneTrafficData(): void
60
+ {
61
+ $now = (int)(microtime(true) * 1000);
62
+ $removed = [];
63
+
64
+ // 1. Expire par temps (maxAgeMs)
65
+ if ($this->maxAgeMs !== null && $this->maxAgeMs > 0) {
66
+ $threshold = $now - $this->maxAgeMs;
67
+ foreach ($this->trafficData as $key => $log) {
68
+ $logTs = $log['timestamp'] ?? $log['requestTimestamp'] ?? $now;
69
+ if ($logTs < $threshold) {
70
+ $removed[] = $log;
71
+ unset($this->trafficData[$key]);
72
+ }
73
+ }
74
+ $this->trafficData = array_values($this->trafficData);
75
+ }
76
+
77
+ // 2. Politique de taille maximale (maxDataPoints)
78
+ if ($this->maxDataPoints > 0 && count($this->trafficData) > $this->maxDataPoints) {
79
+ $overflowCount = count($this->trafficData) - $this->maxDataPoints;
80
+ $spliced = array_splice($this->trafficData, 0, $overflowCount);
81
+ $removed = array_merge($removed, $spliced);
82
+ }
83
+
84
+ // 3. Invocation du callback onCleanup
85
+ if ($this->onCleanup !== null && is_callable($this->onCleanup) && !empty($removed)) {
86
+ try {
87
+ call_user_func($this->onCleanup, $removed);
88
+ } catch (\Throwable $e) {
89
+ error_log("[AutoTuning] Error in onCleanup callback: " . $e->getMessage());
90
+ }
91
+ }
45
92
  }
46
93
 
47
94
  /**
@@ -49,6 +96,8 @@ class AutoTuner
49
96
  */
50
97
  public function runOptimizationCycle(): void
51
98
  {
99
+ $this->pruneTrafficData();
100
+
52
101
  $sanitizedData = RequestUtils::sanitizeTrafficData($this->trafficData);
53
102
 
54
103
  $highConfidenceLogs = count(array_filter(
@@ -176,6 +225,28 @@ class AutoTuner
176
225
  echo "[AutoTuning] Nouveaux seuils : " . json_encode($this->securityConfig['thresholds']) . "\n";
177
226
  echo "[AutoTuning] Nouveaux poids : " . json_encode($this->securityConfig['weights']) . "\n";
178
227
  echo "[AutoTuning] Nouveaux patterns : " . json_encode($this->securityConfig['patterns']) . "\n";
228
+
229
+ // NOUVEAU: Sauvegarder la meilleure configuration si un chemin est fourni.
230
+ if ($this->savePath !== null) {
231
+ try {
232
+ file_put_contents($this->savePath, json_encode($bestSolution['solution'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
233
+ echo "[AutoTuning] Meilleure configuration sauvegardée dans : {$this->savePath}\n";
234
+ } catch (\Throwable $e) {
235
+ error_log("[AutoTuning] Erreur lors de la sauvegarde de la configuration optimisée : " . $e->getMessage());
236
+ }
237
+ }
238
+
239
+ if ($this->clearAfterTuning) {
240
+ $cleared = array_splice($this->trafficData, 0);
241
+ if ($this->onCleanup !== null && is_callable($this->onCleanup) && !empty($cleared)) {
242
+ try {
243
+ call_user_func($this->onCleanup, $cleared);
244
+ } catch (\Throwable $e) {
245
+ error_log("[AutoTuning] Error in onCleanup callback after clearing: " . $e->getMessage());
246
+ }
247
+ }
248
+ echo sprintf("[AutoTuning] Explicitly cleared %d processed traffic data points.\n", count($cleared));
249
+ }
179
250
  }
180
251
 
181
252
  /**