@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.
- package/CHANGELOG.md +43 -0
- package/README.md +56 -11
- package/package.json +2 -1
- package/src/js/fingerprint.client.js +360 -23
- package/src/js/fingerprint.js +508 -200
- package/src/js/fingerprint.utils.js +184 -0
- package/src/js/gpu_pow.solver.js +217 -0
- package/src/js/library.js +1 -1
- package/src/js/pow.solver.inline.js +12 -4
- package/src/js/pow.solver.js +12 -4
- package/src/js/tests/fingerprint.client.init.test.js +5 -2
- package/src/js/tests/fingerprint.isMalicious.test.js +234 -116
- package/src/js/tests/fingerprint.test.js +195 -1
- package/src/js/tests/gpu_pow.test.js +118 -0
- package/src/js/tests/ja3AnomalyDetector.test.js +1 -0
- package/src/js/tests/quicFingerprint.test.js +35 -0
- package/src/php/AutoTuner.php +71 -0
- package/src/php/Challenge/ChallengeUtils.php +344 -9
- package/src/php/Config/SecurityProfiles.php +9 -0
- package/src/php/FingerprintEngine.php +61 -2
- package/src/php/RequestContext.php +2 -0
- package/src/php/Tests/ChallengeUtilsTest.php +285 -81
- package/src/php/Tests/MaliciousPatternsTest.php +104 -0
- package/src/php/Tests/QuicFingerprintTest.php +54 -0
- package/src/php/Tests/RequestUtilsTest.php +43 -0
- package/src/php/Utils/BigInt.php +202 -144
- package/src/php/Utils/MaliciousPatterns.php +74 -58
- package/src/php/Utils/RequestUtils.php +120 -0
|
@@ -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
|
+
});
|
package/src/php/AutoTuner.php
CHANGED
|
@@ -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
|
/**
|