@anonympins/fingerprint 0.5.0 → 0.5.2
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 +31 -0
- package/README.md +118 -115
- package/package.json +1 -1
- package/src/js/dynamic-wasm.js +52 -13
- package/src/js/fingerprint.builder.js +169 -168
- package/src/js/fingerprint.client.js +224 -24
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +382 -63
- package/src/js/fingerprint.utils.js +213 -183
- package/src/js/gpu_pow.solver.js +312 -0
- package/src/js/library.js +16 -13
- package/src/js/pow.solver.inline.js +138 -27
- package/src/js/pow.solver.js +141 -28
- package/src/js/tests/fingerprint.client.init.test.js +5 -2
- package/src/js/tests/fingerprint.engine.test.js +370 -370
- package/src/js/tests/fingerprint.test.js +150 -3
- package/src/js/tests/gpu_pow.test.js +199 -0
- package/src/js/tests/pow.solver.test.js +4 -4
- package/src/js/tests/quicFingerprint.test.js +35 -0
- package/src/php/Challenge/ChallengeUtils.php +158 -27
- package/src/php/Config/SecurityProfiles.php +9 -0
- package/src/php/FingerprintEngine.php +124 -2
- package/src/php/RequestContext.php +2 -0
- package/src/php/Tests/ChallengeUtilsTest.php +77 -0
- package/src/php/Tests/QuicFingerprintTest.php +54 -0
- package/src/php/Tests/RequestUtilsTest.php +43 -0
- package/src/php/Utils/RequestUtils.php +120 -0
|
@@ -33,6 +33,7 @@ const {
|
|
|
33
33
|
getCompositeDeviceHash,
|
|
34
34
|
} = fingerprint;
|
|
35
35
|
const { store, getRequestPatternScore, getDeviceHash } = __internal;
|
|
36
|
+
const { getRenderingAnomalyScore } = __internal;
|
|
36
37
|
let { getBehaviorScore, getClickVarianceScore } = __internal;
|
|
37
38
|
// Mock the entire dns module
|
|
38
39
|
vi.mock('node:dns/promises');
|
|
@@ -748,7 +749,7 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
748
749
|
pow_type: 'cpu_mem',
|
|
749
750
|
pow_nonce: nonce,
|
|
750
751
|
pow_solution_cpu: String(cpuSolution),
|
|
751
|
-
pow_solution_mem:
|
|
752
|
+
pow_solution_mem: JSON.stringify(memSolution),
|
|
752
753
|
pow_fp: solverFingerprint
|
|
753
754
|
},
|
|
754
755
|
headers: { 'user-agent': userAgent, 'x-device-fingerprint': solverFingerprint },
|
|
@@ -842,7 +843,7 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
842
843
|
pow_type: 'cpu_mem',
|
|
843
844
|
pow_nonce: nonce,
|
|
844
845
|
pow_solution_cpu: String(cpuSolution),
|
|
845
|
-
pow_solution_mem:
|
|
846
|
+
pow_solution_mem: JSON.stringify(memSolution),
|
|
846
847
|
pow_fp: solverFingerprint // The client submits its fingerprint.
|
|
847
848
|
},
|
|
848
849
|
headers: { 'user-agent': userAgent, 'x-device-fingerprint': solverFingerprint }, rawHeaders: ['User-Agent', userAgent], httpVersion: '1.1'
|
|
@@ -1492,6 +1493,50 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
1492
1493
|
});
|
|
1493
1494
|
});
|
|
1494
1495
|
|
|
1496
|
+
describe('getRenderingAnomalyScore', () => {
|
|
1497
|
+
it('should return 0 if rendering metrics are missing', () => {
|
|
1498
|
+
const context = { headers: {} };
|
|
1499
|
+
const { renderingAnomalyScore } = getRenderingAnomalyScore(context);
|
|
1500
|
+
expect(renderingAnomalyScore).toBe(0);
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
it('should return 100 if offscreen canvas spoofing is detected', () => {
|
|
1504
|
+
const context = {
|
|
1505
|
+
headers: {
|
|
1506
|
+
'x-behavior-metrics': JSON.stringify({
|
|
1507
|
+
rendering: { fps: 60, jitter: 0.1, offscreenAnom: true }
|
|
1508
|
+
})
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
const { renderingAnomalyScore } = getRenderingAnomalyScore(context);
|
|
1512
|
+
expect(renderingAnomalyScore).toBe(100);
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
it('should return a high score if jitter is very high', () => {
|
|
1516
|
+
const context = {
|
|
1517
|
+
headers: {
|
|
1518
|
+
'x-behavior-metrics': JSON.stringify({
|
|
1519
|
+
rendering: { fps: 60, jitter: 12.5, offscreenAnom: false }
|
|
1520
|
+
})
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
const { renderingAnomalyScore } = getRenderingAnomalyScore(context);
|
|
1524
|
+
expect(renderingAnomalyScore).toBe(65); // (12.5 - 6.0) * 10 = 65
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1527
|
+
it('should return a high score if FPS is abnormal', () => {
|
|
1528
|
+
const context = {
|
|
1529
|
+
headers: {
|
|
1530
|
+
'x-behavior-metrics': JSON.stringify({
|
|
1531
|
+
rendering: { fps: 300, jitter: 1.0, offscreenAnom: false }
|
|
1532
|
+
})
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
const { renderingAnomalyScore } = getRenderingAnomalyScore(context);
|
|
1536
|
+
expect(renderingAnomalyScore).toBe(50);
|
|
1537
|
+
});
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1495
1540
|
describe('getBehaviorScore', () => {
|
|
1496
1541
|
// La fonction est privée, on la récupère via l'export __internal
|
|
1497
1542
|
// FIX: Correctly assign the function before tests run.
|
|
@@ -1542,6 +1587,24 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
1542
1587
|
});
|
|
1543
1588
|
});
|
|
1544
1589
|
|
|
1590
|
+
it('should return a high score for keystroke dynamics with very low dwell/flight variance (bot emulation)', () => {
|
|
1591
|
+
const metrics = {
|
|
1592
|
+
honeypotInteraction: false,
|
|
1593
|
+
keystrokeLatency: 100.0,
|
|
1594
|
+
keystrokeDwellTimes: [50, 50, 50, 50, 50], // stdDev = 0
|
|
1595
|
+
keystrokeFlightTimes: [
|
|
1596
|
+
{ digraph: 'ab', time: 100 },
|
|
1597
|
+
{ digraph: 'bc', time: 100 },
|
|
1598
|
+
{ digraph: 'cd', time: 100 },
|
|
1599
|
+
{ digraph: 'de', time: 100 },
|
|
1600
|
+
{ digraph: 'ef', time: 100 }
|
|
1601
|
+
] // stdDev = 0
|
|
1602
|
+
};
|
|
1603
|
+
const context = { headers: { 'x-behavior-metrics': JSON.stringify(metrics) } };
|
|
1604
|
+
const { behaviorScore } = getBehaviorScore(context);
|
|
1605
|
+
expect(behaviorScore).toBeGreaterThanOrEqual(70); // 35 (stdDevDwell) + 35 (stdDevFlight) = 70
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1545
1608
|
describe('getClickVarianceScore', () => {
|
|
1546
1609
|
// FIX: Correctly assign the function before tests run.
|
|
1547
1610
|
beforeEach(() => {
|
|
@@ -2059,7 +2122,7 @@ describe('Challenge Page Generation Security (XSS)', () => {
|
|
|
2059
2122
|
});
|
|
2060
2123
|
|
|
2061
2124
|
it('should escape the path parameter in generateCombinedPoWChallengePage to prevent XSS', () => {
|
|
2062
|
-
const maliciousPath = `test.com";\nconsole.log("pwned");//`;
|
|
2125
|
+
const maliciousPath = `test.com";\nconsole.log("pwned");//`; // Original malicious path
|
|
2063
2126
|
const challengeDetails = { nonce: 'test-nonce', target: '0000', path: maliciousPath };
|
|
2064
2127
|
|
|
2065
2128
|
const html = generateCombinedPoWChallengePage(challengeDetails, 16, '127.0.0.1', 'secret', {}, '');
|
|
@@ -2658,3 +2721,87 @@ describe('Botnet Cluster Scoring (Node.js)', () => {
|
|
|
2658
2721
|
const isDiffIpValid = await fingerprint.isTicketValid('192.168.1.1', ticket, 'device-123', 'hash-abc', false);
|
|
2659
2722
|
expect(isDiffIpValid).toBe(false);
|
|
2660
2723
|
});
|
|
2724
|
+
|
|
2725
|
+
describe('IP Registration and Filtering', () => {
|
|
2726
|
+
const localInMemoryStore = {
|
|
2727
|
+
_map: new Map(),
|
|
2728
|
+
async get(key) { return this._map.get(key); },
|
|
2729
|
+
async set(key, value) { this._map.set(key, value); },
|
|
2730
|
+
async has(key) { return this._map.has(key); },
|
|
2731
|
+
async delete(key) { this._map.delete(key); },
|
|
2732
|
+
clear() { this._map.clear(); }
|
|
2733
|
+
};
|
|
2734
|
+
|
|
2735
|
+
beforeEach(() => {
|
|
2736
|
+
localInMemoryStore.clear();
|
|
2737
|
+
configureStore(localInMemoryStore);
|
|
2738
|
+
vi.restoreAllMocks();
|
|
2739
|
+
vi.spyOn(fingerprint.__internal, 'getTlsFingerprint').mockReturnValue({
|
|
2740
|
+
ja3: 'mock-ja3', ja4: 'mock-ja4'
|
|
2741
|
+
});
|
|
2742
|
+
});
|
|
2743
|
+
|
|
2744
|
+
it('should immediately allow requests from IPs in the static allowlist', async () => {
|
|
2745
|
+
const whitelistedIp = '198.51.100.42';
|
|
2746
|
+
const config = {
|
|
2747
|
+
weights: { historyScore: 1.0 },
|
|
2748
|
+
thresholds: { low: 20, block: 95 },
|
|
2749
|
+
whitelist: [
|
|
2750
|
+
{ type: 'allowlist', entries: [whitelistedIp] }
|
|
2751
|
+
]
|
|
2752
|
+
};
|
|
2753
|
+
const engine = new FingerprintEngine(config);
|
|
2754
|
+
|
|
2755
|
+
const req = {
|
|
2756
|
+
clientIp: whitelistedIp,
|
|
2757
|
+
path: '/',
|
|
2758
|
+
cookies: {},
|
|
2759
|
+
query: {},
|
|
2760
|
+
headers: { 'user-agent': 'test-ua' },
|
|
2761
|
+
rawHeaders: ['User-Agent', 'test-ua'],
|
|
2762
|
+
httpVersion: '1.1'
|
|
2763
|
+
};
|
|
2764
|
+
|
|
2765
|
+
const decision = await engine.processRequest(req);
|
|
2766
|
+
expect(decision.action).toBe('next');
|
|
2767
|
+
expect(decision.vector.whitelisted).toBe(100);
|
|
2768
|
+
expect(decision.vector.type).toBe('allowlist');
|
|
2769
|
+
});
|
|
2770
|
+
|
|
2771
|
+
it('should register distinct client IPs for a device in the data store', async () => {
|
|
2772
|
+
const ip1 = '192.168.1.100';
|
|
2773
|
+
const ip2 = '192.168.1.101';
|
|
2774
|
+
const deviceId = 'test-ip-reg-device';
|
|
2775
|
+
|
|
2776
|
+
await localInMemoryStore.set(`device:${deviceId}`, {
|
|
2777
|
+
initialDeviceHash: 'some-hash',
|
|
2778
|
+
ips: new Set([ip1]),
|
|
2779
|
+
lastUpdate: Date.now(),
|
|
2780
|
+
lastFpHash: 'some-hash',
|
|
2781
|
+
lastChangeTimestamp: 0,
|
|
2782
|
+
rapidChangeCount: 0,
|
|
2783
|
+
});
|
|
2784
|
+
|
|
2785
|
+
const req = {
|
|
2786
|
+
clientIp: ip2,
|
|
2787
|
+
path: '/',
|
|
2788
|
+
cookies: { device_id: deviceId },
|
|
2789
|
+
query: {},
|
|
2790
|
+
headers: { 'user-agent': 'test-ua' },
|
|
2791
|
+
rawHeaders: ['User-Agent', 'test-ua'],
|
|
2792
|
+
httpVersion: '1.1'
|
|
2793
|
+
};
|
|
2794
|
+
|
|
2795
|
+
const engine = new FingerprintEngine({
|
|
2796
|
+
weights: { historyScore: 1.0 },
|
|
2797
|
+
thresholds: { low: 20, block: 95 }
|
|
2798
|
+
});
|
|
2799
|
+
|
|
2800
|
+
await engine.processRequest(req);
|
|
2801
|
+
|
|
2802
|
+
const storedData = await localInMemoryStore.get(`device:${deviceId}`);
|
|
2803
|
+
expect(storedData.ips).toBeInstanceOf(Set);
|
|
2804
|
+
expect(storedData.ips.has(ip1)).toBe(true);
|
|
2805
|
+
expect(storedData.ips.has(ip2)).toBe(true);
|
|
2806
|
+
});
|
|
2807
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
|
|
119
|
+
it('should fallback to WebGL1 if both WebGPU and WebGL2 are not supported', async () => {
|
|
120
|
+
// Mock WebGPU as undefined
|
|
121
|
+
Object.defineProperty(global.navigator, 'gpu', {
|
|
122
|
+
value: undefined,
|
|
123
|
+
writable: true,
|
|
124
|
+
configurable: true
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Mock WebGL1 (webgl) context with float extensions enabled
|
|
128
|
+
const mockGl1 = {
|
|
129
|
+
FRAMEBUFFER: 36160,
|
|
130
|
+
FRAMEBUFFER_COMPLETE: 36053,
|
|
131
|
+
COLOR_ATTACHMENT0: 36064,
|
|
132
|
+
TEXTURE_2D: 3553,
|
|
133
|
+
RGBA: 6408,
|
|
134
|
+
FLOAT: 5126,
|
|
135
|
+
TRIANGLES: 4,
|
|
136
|
+
CLAMP_TO_EDGE: 33071,
|
|
137
|
+
TEXTURE_MAG_FILTER: 10240,
|
|
138
|
+
TEXTURE_MIN_FILTER: 10241,
|
|
139
|
+
NEAREST: 9728,
|
|
140
|
+
TEXTURE_WRAP_S: 10242,
|
|
141
|
+
TEXTURE_WRAP_T: 10243,
|
|
142
|
+
useProgram: vi.fn(),
|
|
143
|
+
createBuffer: vi.fn(),
|
|
144
|
+
bindBuffer: vi.fn(),
|
|
145
|
+
bufferData: vi.fn(),
|
|
146
|
+
enableVertexAttribArray: vi.fn(),
|
|
147
|
+
vertexAttribPointer: vi.fn(),
|
|
148
|
+
uniform1f: vi.fn(),
|
|
149
|
+
uniform1i: vi.fn(),
|
|
150
|
+
getUniformLocation: vi.fn(),
|
|
151
|
+
getAttribLocation: vi.fn(),
|
|
152
|
+
drawArrays: vi.fn(),
|
|
153
|
+
getExtension: vi.fn().mockImplementation((ext) => {
|
|
154
|
+
if (ext === 'OES_texture_float' || ext === 'WEBGL_color_buffer_float') {
|
|
155
|
+
return {};
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}),
|
|
159
|
+
createTexture: vi.fn(),
|
|
160
|
+
bindTexture: vi.fn(),
|
|
161
|
+
texImage2D: vi.fn(),
|
|
162
|
+
texParameteri: vi.fn(),
|
|
163
|
+
createFramebuffer: vi.fn(),
|
|
164
|
+
bindFramebuffer: vi.fn(),
|
|
165
|
+
framebufferTexture2D: vi.fn(),
|
|
166
|
+
checkFramebufferStatus: vi.fn().mockReturnValue(36053), // gl.FRAMEBUFFER_COMPLETE
|
|
167
|
+
viewport: vi.fn(),
|
|
168
|
+
readPixels: vi.fn((x, y, w, h, format, type, pixels) => {
|
|
169
|
+
for (let i = 0; i < pixels.length; i++) {
|
|
170
|
+
pixels[i] = 0.75;
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const mockCanvas = {
|
|
176
|
+
width: 8,
|
|
177
|
+
height: 8,
|
|
178
|
+
getContext: vi.fn().mockImplementation((contextId) => {
|
|
179
|
+
if (contextId === 'webgl2') return null;
|
|
180
|
+
if (contextId === 'webgl' || contextId === 'experimental-webgl') return mockGl1;
|
|
181
|
+
return null;
|
|
182
|
+
})
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
vi.spyOn(document, 'createElement').mockImplementation((tagName) => {
|
|
186
|
+
if (tagName === 'canvas') return mockCanvas;
|
|
187
|
+
return {};
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
vi.spyOn(GpuPowSolver, '_createProgram').mockReturnValue({});
|
|
191
|
+
|
|
192
|
+
const result = await GpuPowSolver.solve(seed, 100);
|
|
193
|
+
|
|
194
|
+
expect(result.platform).toBe('webgl1');
|
|
195
|
+
expect(result.solution.split(',').length).toBe(64);
|
|
196
|
+
expect(result.solution.split(',')[0]).toBe('0.750000');
|
|
197
|
+
vi.restoreAllMocks();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -63,8 +63,8 @@ describe('Proof-of-Work Solvers', () => {
|
|
|
63
63
|
const solution1 = await solveMemory(seed, difficulty);
|
|
64
64
|
const solution2 = await solveMemory(seed, difficulty);
|
|
65
65
|
|
|
66
|
-
expect(solution1).
|
|
67
|
-
expect(solution1).not.
|
|
66
|
+
expect(solution1).toStrictEqual(solution2);
|
|
67
|
+
expect(solution1).not.toStrictEqual(await solveMemory('different-seed', difficulty));
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
|
|
@@ -106,7 +106,7 @@ describe('Proof-of-Work Solvers', () => {
|
|
|
106
106
|
|
|
107
107
|
const solutions = await solveChallenge(challenge, ''); // Le fingerprint est passé en 2e arg
|
|
108
108
|
expect(solutions.rawSolution).toHaveProperty('cpu', expect.any(Number));
|
|
109
|
-
expect(solutions.rawSolution).toHaveProperty('mem', expect.any(
|
|
109
|
+
expect(solutions.rawSolution).toHaveProperty('mem', expect.any(Object));
|
|
110
110
|
}, 20000);
|
|
111
111
|
|
|
112
112
|
it('should solve a "cpu_mem_inline" challenge for a browser', async () => {
|
|
@@ -122,7 +122,7 @@ describe('Proof-of-Work Solvers', () => {
|
|
|
122
122
|
|
|
123
123
|
const solutions = await solveChallenge(challenge, '');
|
|
124
124
|
expect(solutions.rawSolution).toHaveProperty('cpu', expect.any(Number));
|
|
125
|
-
expect(solutions.rawSolution).toHaveProperty('mem', expect.any(
|
|
125
|
+
expect(solutions.rawSolution).toHaveProperty('mem', expect.any(Object));
|
|
126
126
|
}, 20000);
|
|
127
127
|
|
|
128
128
|
it('should solve a "tsp" challenge', async () => {
|
|
@@ -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
|
+
});
|