@anonympins/fingerprint 0.7.2 → 0.7.4
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 +36 -9
- package/README.md +1 -1
- package/package.json +109 -107
- package/public/fingerprint-anti-bot.zip +0 -0
- package/src/js/fingerprint.client.js +252 -45
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +265 -185
- package/src/js/fingerprint.utils.js +32 -5
- package/src/js/fingerprint.worker.js +81 -0
- package/src/js/library.js +6 -2
- package/src/js/pow.solver.js +2 -3
- package/src/js/problem-manager.js +113 -113
- package/src/js/tests/fingerprint.client.init.test.js +30 -0
- package/src/js/tests/fingerprint.test.js +136 -4
- package/src/js/tests/threatIntel.test.js +76 -0
- package/src/php/AutoTuner.php +36 -18
- package/src/php/Challenge/ChallengeUtils.php +121 -240
- package/src/php/DirectFingerprint.php +16 -4
- package/src/php/FingerprintClient.php +17 -19
- package/src/php/FingerprintEngine.php +77 -39
- package/src/php/Ja3AnomalyDetector.php +20 -12
- package/src/php/Optimization/Optimization.php +5 -1
- package/src/php/Optimization/OptimizationOperators.php +5 -1
- package/src/php/Optimization/ProblemInitializers.php +16 -4
- package/src/php/ProblemManager.php +20 -14
- package/src/php/Tests/ChallengeUtilsTest.php +469 -453
- package/src/php/Tests/FingerprintEngineTest.php +6 -1
- package/src/php/Tests/RequestUtilsTest.php +1 -1
- package/src/php/Tests/ThreatIntelTest.php +64 -0
- package/src/php/Utils/Env.php +51 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/RequestUtils.php +231 -132
- package/src/php/WordPress/WpDbStore.php +4 -0
- package/src/php/WordPress/{fingerprint-wordpress.php → fingerprint-anti-bot.php} +131 -126
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +1 -1
- package/src/php/WordPress/package.php +6 -3
- package/src/php/WordPress/readme.txt +36 -0
- package/public/fingerprint-wordpress.zip +0 -0
|
@@ -906,8 +906,76 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
906
906
|
expect(req.fingerprint.score).toBeGreaterThanOrEqual(blockThreshold);
|
|
907
907
|
});
|
|
908
908
|
|
|
909
|
-
test('should redirect after a valid COMBINED (CPU+Mem) PoW solution is provided', async (
|
|
909
|
+
test('should redirect after a valid COMBINED (CPU+Mem) PoW solution is provided', async () => {
|
|
910
|
+
const ip = '127.0.0.1';
|
|
911
|
+
const originalPath = '/protected/combined';
|
|
912
|
+
const solverFingerprint = 'fp-combined-solution';
|
|
913
|
+
const userAgent = 'test-ua-combined';
|
|
914
|
+
|
|
915
|
+
vi.spyOn(__internal, 'getSuspicionVector').mockResolvedValue({
|
|
916
|
+
historyScore: 35,
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
const securityConfigWithLowDiff = {
|
|
920
|
+
...securityConfig,
|
|
921
|
+
cpu: { minDifficultyBits: 4, ...securityConfig.cpu },
|
|
922
|
+
};
|
|
923
|
+
const middleware = powMiddleware(securityConfigWithLowDiff);
|
|
924
|
+
|
|
925
|
+
// 1. Déclencher le challenge
|
|
926
|
+
const req1 = {
|
|
927
|
+
path: originalPath, ip, cookies: {}, query: {},
|
|
928
|
+
headers: { 'user-agent': userAgent, 'x-device-fingerprint': solverFingerprint },
|
|
929
|
+
rawHeaders: ['User-Agent', userAgent], httpVersion: '1.1'
|
|
930
|
+
};
|
|
931
|
+
let challengeBody;
|
|
932
|
+
const res1 = {
|
|
933
|
+
status: () => res1,
|
|
934
|
+
send: (body) => { challengeBody = body; },
|
|
935
|
+
cookie: vi.fn()
|
|
936
|
+
};
|
|
937
|
+
req1.fingerprint = {};
|
|
938
|
+
await middleware(req1, res1, vi.fn());
|
|
939
|
+
|
|
940
|
+
expect(challengeBody).toContain('Enhanced Verification');
|
|
941
|
+
|
|
942
|
+
// 2. Résoudre le challenge
|
|
943
|
+
const nonce = challengeBody.match(/const nonce = "([^"]+)"/)[1];
|
|
944
|
+
const clientSecret = challengeBody.match(/const clientSecret = "([^"]+)"/)[1];
|
|
945
|
+
const cpuTargetHex = challengeBody.match(/const cpuTarget = BigInt\("0x" \+ "([^"]+)"\);/)[1];
|
|
946
|
+
const memDifficulty = parseInt(challengeBody.match(/const memDifficulty = (\d+)/)[1], 10);
|
|
947
|
+
|
|
948
|
+
const baseBlock = new TextEncoder().encode(`${nonce}:${clientSecret}:${solverFingerprint}:${ip}::`);
|
|
949
|
+
const cpuSolution = await solveCpuTargetInline(baseBlock, cpuTargetHex, () => {});
|
|
950
|
+
const memSolution = await solveMemory(`:${nonce}:${clientSecret}`, memDifficulty);
|
|
951
|
+
|
|
952
|
+
// 3. Soumettre la solution
|
|
953
|
+
const req2 = {
|
|
954
|
+
path: originalPath, ip, cookies: {},
|
|
955
|
+
query: {
|
|
956
|
+
pow_type: 'cpu_mem',
|
|
957
|
+
pow_nonce: nonce,
|
|
958
|
+
pow_solution_cpu: String(cpuSolution),
|
|
959
|
+
pow_solution_mem: JSON.stringify(memSolution),
|
|
960
|
+
pow_fp: solverFingerprint
|
|
961
|
+
},
|
|
962
|
+
headers: { 'user-agent': userAgent, 'x-device-fingerprint': solverFingerprint },
|
|
963
|
+
rawHeaders: ['User-Agent', userAgent], httpVersion: '1.1'
|
|
964
|
+
};
|
|
965
|
+
let capturedCookie;
|
|
966
|
+
const res2 = {
|
|
967
|
+
cookie: (name, value, options) => { capturedCookie = { name, value, options }; },
|
|
968
|
+
redirect: vi.fn(),
|
|
969
|
+
status: vi.fn(() => res2),
|
|
970
|
+
send: vi.fn(),
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
req2.fingerprint = {};
|
|
974
|
+
await middleware(req2, res2, vi.fn());
|
|
910
975
|
|
|
976
|
+
expect(res2.redirect).toHaveBeenCalledWith(originalPath);
|
|
977
|
+
expect(capturedCookie).toBeDefined();
|
|
978
|
+
expect(capturedCookie.name).toBe('pow_clearance');
|
|
911
979
|
});
|
|
912
980
|
});
|
|
913
981
|
|
|
@@ -1553,6 +1621,18 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
1553
1621
|
expect(behaviorScore).toBe(0);
|
|
1554
1622
|
});
|
|
1555
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
|
+
|
|
1556
1636
|
it('should return a score of 100 for honeypot interaction', () => {
|
|
1557
1637
|
const metrics = { honeypotInteraction: true, mouseEntropy: 50, keystrokeLatency: 120 };
|
|
1558
1638
|
const context = { headers: { 'x-behavior-metrics': JSON.stringify(metrics) } };
|
|
@@ -1560,11 +1640,11 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
1560
1640
|
expect(behaviorScore).toBe(100);
|
|
1561
1641
|
});
|
|
1562
1642
|
|
|
1563
|
-
it('should return a score of
|
|
1643
|
+
it('should return a score of 5 for no mouse or keyboard activity', () => {
|
|
1564
1644
|
const metrics = { honeypotInteraction: false, mouseMovementsHistory: [], keystrokeLatency: 0 };
|
|
1565
1645
|
const context = { headers: { 'x-behavior-metrics': JSON.stringify(metrics) } };
|
|
1566
1646
|
const { behaviorScore } = getBehaviorScore(context);
|
|
1567
|
-
expect(behaviorScore).toBe(
|
|
1647
|
+
expect(behaviorScore).toBe(5);
|
|
1568
1648
|
});
|
|
1569
1649
|
|
|
1570
1650
|
it('should return a score of 0 for normal user activity', () => {
|
|
@@ -1759,7 +1839,7 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
1759
1839
|
};
|
|
1760
1840
|
|
|
1761
1841
|
const isVerified = await engine._verifyWhitelistedBot({ ...requestContext, fingerprint: {} });
|
|
1762
|
-
expect(isVerified).
|
|
1842
|
+
expect(isVerified).toBeNull();
|
|
1763
1843
|
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('[Fingerprint] Invalid regex in whitelist rule'));
|
|
1764
1844
|
consoleErrorSpy.mockRestore();
|
|
1765
1845
|
});
|
|
@@ -2394,6 +2474,58 @@ describe('getClientHintsInconsistencyScore', () => {
|
|
|
2394
2474
|
});
|
|
2395
2475
|
});
|
|
2396
2476
|
|
|
2477
|
+
describe('getProtocolAnomalyScore (HTTP/2 Frame Analysis)', () => {
|
|
2478
|
+
const { getProtocolAnomalyScore } = __internal;
|
|
2479
|
+
|
|
2480
|
+
test('should penalize spoofed Chromium with no PRIORITY frames', () => {
|
|
2481
|
+
const context = {
|
|
2482
|
+
headers: {
|
|
2483
|
+
'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',
|
|
2484
|
+
'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:256|m,a,s,p|p:0,w:4,c:1'
|
|
2485
|
+
},
|
|
2486
|
+
httpVersion: '2.0'
|
|
2487
|
+
};
|
|
2488
|
+
const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
|
|
2489
|
+
expect(protocolAnomalyScore).toBe(25.0);
|
|
2490
|
+
});
|
|
2491
|
+
|
|
2492
|
+
test('should penalize spoofed Chromium with too few WINDOW_UPDATE frames', () => {
|
|
2493
|
+
const context = {
|
|
2494
|
+
headers: {
|
|
2495
|
+
'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',
|
|
2496
|
+
'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:256|m,a,s,p|p:3,w:1,c:1'
|
|
2497
|
+
},
|
|
2498
|
+
httpVersion: '2.0'
|
|
2499
|
+
};
|
|
2500
|
+
const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
|
|
2501
|
+
expect(protocolAnomalyScore).toBe(20.0);
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
test('should penalize spoofed Firefox with too many PRIORITY frames', () => {
|
|
2505
|
+
const context = {
|
|
2506
|
+
headers: {
|
|
2507
|
+
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',
|
|
2508
|
+
'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:256|m,s,p,a|p:3,w:2,c:0'
|
|
2509
|
+
},
|
|
2510
|
+
httpVersion: '2.0'
|
|
2511
|
+
};
|
|
2512
|
+
const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
|
|
2513
|
+
expect(protocolAnomalyScore).toBe(20.0);
|
|
2514
|
+
});
|
|
2515
|
+
|
|
2516
|
+
test('should not penalize a legitimate Chromium fingerprint', () => {
|
|
2517
|
+
const context = {
|
|
2518
|
+
headers: {
|
|
2519
|
+
'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',
|
|
2520
|
+
'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:256|m,a,s,p|p:3,w:4,c:1'
|
|
2521
|
+
},
|
|
2522
|
+
httpVersion: '2.0'
|
|
2523
|
+
};
|
|
2524
|
+
const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
|
|
2525
|
+
expect(protocolAnomalyScore).toBe(0.0);
|
|
2526
|
+
});
|
|
2527
|
+
});
|
|
2528
|
+
|
|
2397
2529
|
describe('Subnet Scoring (Node.js)', () => {
|
|
2398
2530
|
const inMemoryStore = {
|
|
2399
2531
|
_map: new Map(),
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { __internal, configureStore } from '../fingerprint.js';
|
|
3
|
+
|
|
4
|
+
describe('Threat Intelligence - Rationale-Driven Defensive Logic', () => {
|
|
5
|
+
const inMemoryStore = {
|
|
6
|
+
_map: new Map(),
|
|
7
|
+
async get(key) { return this._map.get(key); },
|
|
8
|
+
async set(key, value) { this._map.set(key, value); },
|
|
9
|
+
async has(key) { return this._map.has(key); },
|
|
10
|
+
async delete(key) { this._map.delete(key); },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
inMemoryStore._map.clear();
|
|
15
|
+
configureStore(inMemoryStore);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should assign a deterministic score of 100 with audit trace for banned ZKP public keys', async () => {
|
|
19
|
+
await inMemoryStore.set('banned-zkp-y:banned-key-999', true);
|
|
20
|
+
|
|
21
|
+
const context = { headers: {} };
|
|
22
|
+
const result = await __internal.getSuspicionVector(context, {
|
|
23
|
+
weights: { threatIntelScore: 1.0 },
|
|
24
|
+
thresholds: { low: 20 }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Direct check via internal function with banned key
|
|
28
|
+
const threatResult = await __internal.store.has('banned-zkp-y:banned-key-999');
|
|
29
|
+
expect(threatResult).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return score 0 for consistent local connections without proxy delay', async () => {
|
|
33
|
+
const reqTime = 1700000000000;
|
|
34
|
+
const context = {
|
|
35
|
+
requestTimestamp: reqTime,
|
|
36
|
+
headers: {
|
|
37
|
+
'x-tcp-rtt': '15',
|
|
38
|
+
'x-behavior-metrics': JSON.stringify({ clientTimestamp: reqTime - 25 })
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const vector = await __internal.getSuspicionVector(context, { weights: { threatIntelScore: 1.0 } });
|
|
43
|
+
expect(vector.threatIntelScore).toBe(0.0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should ignore mild JS event loop jitter and network lag without triggering false positives', async () => {
|
|
47
|
+
const reqTime = 1700000000000;
|
|
48
|
+
// App latency 65ms with a 20ms RTT falls well within the 60ms jitter tolerance window
|
|
49
|
+
const context = {
|
|
50
|
+
requestTimestamp: reqTime,
|
|
51
|
+
headers: {
|
|
52
|
+
'x-tcp-rtt': '20',
|
|
53
|
+
'x-behavior-metrics': JSON.stringify({ clientTimestamp: reqTime - 65 })
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const vector = await __internal.getSuspicionVector(context, { weights: { threatIntelScore: 1.0 } });
|
|
58
|
+
expect(vector.threatIntelScore).toBe(0.0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should smoothly calculate elevated suspicion when residential proxy tunneling is detected', async () => {
|
|
62
|
+
const reqTime = 1700000000000;
|
|
63
|
+
// Edge socket is 10ms (datacenter gateway), but application payload arrives with 280ms latency (WAN proxy hop)
|
|
64
|
+
const context = {
|
|
65
|
+
requestTimestamp: reqTime,
|
|
66
|
+
headers: {
|
|
67
|
+
'x-tcp-rtt': '10',
|
|
68
|
+
'x-behavior-metrics': JSON.stringify({ clientTimestamp: reqTime - 280 })
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const vector = await __internal.getSuspicionVector(context, { weights: { threatIntelScore: 1.0 } });
|
|
73
|
+
expect(vector.threatIntelScore).toBeGreaterThan(70.0);
|
|
74
|
+
expect(vector.threatIntelScore).toBeLessThanOrEqual(95.0);
|
|
75
|
+
});
|
|
76
|
+
});
|
package/src/php/AutoTuner.php
CHANGED
|
@@ -129,7 +129,8 @@ class AutoTuner
|
|
|
129
129
|
try {
|
|
130
130
|
call_user_func($this->onCleanup, $removed);
|
|
131
131
|
} catch (\Throwable $e) {
|
|
132
|
-
|
|
132
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
|
|
133
|
+
$this->logError("[AutoTuning] Error in onCleanup callback: " . $e->getMessage());
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
}
|
|
@@ -155,24 +156,24 @@ class AutoTuner
|
|
|
155
156
|
|
|
156
157
|
if (count($sanitizedData) < $this->minDataPoints || !$hasEnoughSignal) {
|
|
157
158
|
if (count($sanitizedData) < $this->minDataPoints) {
|
|
158
|
-
|
|
159
|
+
$this->logMessage(sprintf("[AutoTuning] Postponed: %d/%d data points collected.\n", count($sanitizedData), $this->minDataPoints));
|
|
159
160
|
} else {
|
|
160
|
-
|
|
161
|
+
$this->logMessage(sprintf("[AutoTuning] Postponed: Insufficient confidence signals (Ratio: %.2f%% < %.2f%% and count: %d < %d).\n", $highConfidenceRatio * 100, $minConfidenceRatio * 100, $highConfidenceLogs, $minHighConfidenceCount));
|
|
161
162
|
}
|
|
162
163
|
return;
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
if (count($this->trafficData) > $this->maxDataPoints) {
|
|
166
|
-
|
|
167
|
+
$this->logMessage(sprintf("[AutoTuning] Traffic log reached %d entries (max: %d). Truncating oldest logs.\n", count($this->trafficData), $this->maxDataPoints));
|
|
167
168
|
$this->trafficData = array_slice($this->trafficData, count($this->trafficData) - $this->maxDataPoints);
|
|
168
169
|
}
|
|
169
170
|
|
|
170
|
-
|
|
171
|
+
$this->logMessage(sprintf("[AutoTuning] Starting complete optimization cycle with %d sanitized data points.\n", count($sanitizedData)));
|
|
171
172
|
|
|
172
173
|
$paretoFront = OptimizationOperators::solveFullSecurityTuning(['trafficData' => $sanitizedData, 'currentConfig' => $this->securityConfig], []);
|
|
173
174
|
|
|
174
175
|
if (empty($paretoFront)) {
|
|
175
|
-
|
|
176
|
+
$this->logMessage("[AutoTuning] Optimization returned no solutions.\n");
|
|
176
177
|
return;
|
|
177
178
|
}
|
|
178
179
|
|
|
@@ -192,7 +193,7 @@ class AutoTuner
|
|
|
192
193
|
|
|
193
194
|
$filteredFront = array_filter($paretoFront, fn ($p) => $isValidSecurityConfig($p['solution']));
|
|
194
195
|
if (empty($filteredFront)) {
|
|
195
|
-
|
|
196
|
+
$this->logMessage("[AutoTuning] Warning: All Pareto solutions violated sanity guardrails. Restoring raw front.\n");
|
|
196
197
|
$filteredFront = $paretoFront;
|
|
197
198
|
} else {
|
|
198
199
|
$filteredFront = array_values($filteredFront);
|
|
@@ -277,7 +278,8 @@ class AutoTuner
|
|
|
277
278
|
$validationTolerance = $this->securityConfig['autotuning']['validationTolerance'] ?? $this->validationTolerance;
|
|
278
279
|
|
|
279
280
|
if ($proposedFPR > $currentFPR + $validationTolerance || $proposedFNR > $currentFNR + $validationTolerance) {
|
|
280
|
-
|
|
281
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system alert
|
|
282
|
+
$this->logError(sprintf(
|
|
281
283
|
"[AutoTuning] [SECURITY ALERT] Proposed configuration rejected due to instability/poisoning risk! Proposed FPR: %.4f (Current: %.4f), Proposed FNR: %.4f (Current: %.4f)",
|
|
282
284
|
$proposedFPR, $currentFPR, $proposedFNR, $currentFNR
|
|
283
285
|
));
|
|
@@ -300,22 +302,23 @@ class AutoTuner
|
|
|
300
302
|
|
|
301
303
|
self::$lastBestSolution = $bestSolution;
|
|
302
304
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
+
$this->logMessage("[AutoTuning] New optimized security configuration applied.\n");
|
|
306
|
+
$this->logMessage("[AutoTuning] Objectives achieved: " . json_encode([
|
|
305
307
|
'falsePositiveRate' => round($bestSolution['objectives'][0], 4),
|
|
306
308
|
'falseNegativeRate' => round($bestSolution['objectives'][1], 4)
|
|
307
|
-
]) . "\n";
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
]) . "\n");
|
|
310
|
+
$this->logMessage("[AutoTuning] New thresholds: " . json_encode($this->securityConfig['thresholds']) . "\n");
|
|
311
|
+
$this->logMessage("[AutoTuning] New weights: " . json_encode($this->securityConfig['weights']) . "\n");
|
|
312
|
+
$this->logMessage("[AutoTuning] New patterns: " . json_encode($this->securityConfig['patterns']) . "\n");
|
|
311
313
|
|
|
312
314
|
// Persist best configuration if savePath is configured
|
|
313
315
|
if ($this->savePath !== null) {
|
|
314
316
|
try {
|
|
315
317
|
file_put_contents($this->savePath, json_encode($bestSolution['solution'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
316
|
-
|
|
318
|
+
$this->logMessage("[AutoTuning] Best configuration saved to: {$this->savePath}\n");
|
|
317
319
|
} catch (\Throwable $e) {
|
|
318
|
-
|
|
320
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
|
|
321
|
+
$this->logError("[AutoTuning] Error saving optimized configuration: " . $e->getMessage());
|
|
319
322
|
}
|
|
320
323
|
}
|
|
321
324
|
|
|
@@ -325,13 +328,28 @@ class AutoTuner
|
|
|
325
328
|
try {
|
|
326
329
|
call_user_func($this->onCleanup, $cleared);
|
|
327
330
|
} catch (\Throwable $e) {
|
|
328
|
-
|
|
331
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
|
|
332
|
+
$this->logError("[AutoTuning] Error in onCleanup callback after clearing: " . $e->getMessage());
|
|
329
333
|
}
|
|
330
334
|
}
|
|
331
|
-
|
|
335
|
+
$this->logMessage(sprintf("[AutoTuning] Explicitly cleared %d processed traffic data points.\n", count($cleared)));
|
|
332
336
|
}
|
|
333
337
|
}
|
|
334
338
|
|
|
339
|
+
private function logMessage(string $message): void
|
|
340
|
+
{
|
|
341
|
+
if (PHP_SAPI === 'cli') {
|
|
342
|
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CLI message output
|
|
343
|
+
echo $message;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private function logError(string $message): void
|
|
348
|
+
{
|
|
349
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
|
|
350
|
+
error_log($message);
|
|
351
|
+
}
|
|
352
|
+
|
|
335
353
|
/**
|
|
336
354
|
* Returns the latest best solution found by the auto-tuner.
|
|
337
355
|
* @return array<string, mixed>|null
|