@anonympins/fingerprint 0.7.5 → 0.8.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +1 -1
  3. package/package.json +3 -2
  4. package/public/anonympins-bot-mitigation-pow.zip +0 -0
  5. package/src/js/asn-lookup.js +223 -0
  6. package/src/js/fingerprint.client.js +1784 -1772
  7. package/src/js/fingerprint.client.obfuscated.js +1 -1
  8. package/src/js/fingerprint.js +236 -40
  9. package/src/js/fingerprint.utils.js +252 -2
  10. package/src/js/pow.worker.js +2 -2
  11. package/src/js/tests/asn-lookup.test.js +57 -0
  12. package/src/js/tests/cross_parity.test.js +3 -3
  13. package/src/js/tests/dns-circuit-breaker.test.js +6 -6
  14. package/src/js/tests/fingerprint.builder.test.js +76 -78
  15. package/src/js/tests/fingerprint.client.test.js +16 -17
  16. package/src/js/tests/fingerprint.test.js +19 -12
  17. package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
  18. package/src/js/tests/library.test.js +94 -95
  19. package/src/js/tests/obfuscation.test.js +8 -8
  20. package/src/js/tests/quicFingerprint.test.js +47 -0
  21. package/src/js/tests/tcpFingerprint.test.js +4 -4
  22. package/src/js/upow-model-task.js +155 -112
  23. package/src/php/AsnLookupEngine.php +173 -0
  24. package/src/php/Challenge/ChallengeUtils.php +430 -117
  25. package/src/php/Config/SecurityProfiles.php +75 -62
  26. package/src/php/DirectFingerprint.php +80 -13
  27. package/src/php/FingerprintBuilder.php +13 -13
  28. package/src/php/FingerprintClient.php +6 -6
  29. package/src/php/FingerprintEngine.php +2141 -1837
  30. package/src/php/Ja3AnomalyDetector.php +22 -22
  31. package/src/php/NetworkProfile.php +68 -0
  32. package/src/php/Optimization/FunctionRegistry.php +4 -4
  33. package/src/php/Optimization/Optimization.php +6 -6
  34. package/src/php/ProblemManager.php +12 -2
  35. package/src/php/RequestContext.php +11 -11
  36. package/src/php/Store/InMemoryStore.php +96 -66
  37. package/src/php/Store/MongoDbStore.php +2 -2
  38. package/src/php/Store/RedisStore.php +1 -1
  39. package/src/php/Store/StoreManager.php +48 -35
  40. package/src/php/Tests/AsnLookupTest.php +61 -0
  41. package/src/php/Tests/ChallengeUtilsTest.php +31 -0
  42. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  43. package/src/php/Tests/FingerprintClientTest.php +70 -70
  44. package/src/php/Tests/FingerprintEngineTest.php +4 -2
  45. package/src/php/Tests/PowTest.php +37 -39
  46. package/src/php/Tests/QuicFingerprintTest.php +115 -53
  47. package/src/php/Tests/ThreatIntelTest.php +2 -2
  48. package/src/php/Utils/BigInt.php +202 -202
  49. package/src/php/Utils/BlockList.php +13 -13
  50. package/src/php/Utils/Env.php +7 -2
  51. package/src/php/Utils/Logger.php +19 -1
  52. package/src/php/Utils/MaliciousPatterns.php +6 -6
  53. package/src/php/Utils/MetricsManager.php +209 -209
  54. package/src/php/Utils/RequestUtils.php +348 -18
  55. package/src/php/Utils/TLSClientHelloParser.php +385 -369
  56. package/src/php/WordPress/WpDbStore.php +34 -7
  57. package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1222 -0
  58. package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
  59. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
  60. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
  61. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  62. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
  63. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  64. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
  65. package/src/php/WordPress/package.php +88 -99
  66. package/src/php/WordPress/readme.txt +28 -6
  67. package/src/php/bin/auto-tune.php +1 -1
  68. package/public/fingerprint-anti-bot.zip +0 -0
@@ -21,10 +21,15 @@ class Env
21
21
  }
22
22
  // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Environment and server variables may contain raw cryptographic keys.
23
23
  if (isset($_ENV[$key]) && $_ENV[$key] !== '') {
24
- return $_ENV[$key];
24
+ $envVal = function_exists('wp_unslash') ? wp_unslash($_ENV[$key]) : $_ENV[$key];
25
+ return is_string($envVal) ? (function_exists('sanitize_textarea_field') ? sanitize_textarea_field($envVal) : $envVal) : $envVal;
25
26
  }
26
27
  if (isset($_SERVER[$key]) && $_SERVER[$key] !== '') {
27
- return $_SERVER[$key];
28
+ $serverVal = function_exists('wp_unslash') ? wp_unslash($_SERVER[$key]) : $_SERVER[$key];
29
+ if (is_string($serverVal)) {
30
+ return function_exists('sanitize_textarea_field') ? sanitize_textarea_field($serverVal) : $serverVal;
31
+ }
32
+ return $serverVal;
28
33
  }
29
34
  // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
30
35
  $val = getenv($key);
@@ -5,7 +5,7 @@ declare(strict_types=1);
5
5
  namespace Anonympins\Fingerprint\Utils;
6
6
 
7
7
  /**
8
- * Un simple wrapper de logger pour passer les données à une fonction de rappel.
8
+ * A simple logger wrapper to forward data to a callback function.
9
9
  */
10
10
  class Logger
11
11
  {
@@ -14,11 +14,29 @@ class Logger
14
14
  */
15
15
  private $callback;
16
16
 
17
+ /**
18
+ * Creates a new Logger instance with the given callback.
19
+ * The callback will be invoked with a single log entry array argument
20
+ * each time `log()` is called.
21
+ *
22
+ * @param callable $callback The callback used to process log entries.
23
+ */
17
24
  public function __construct(callable $callback)
18
25
  {
19
26
  $this->callback = $callback;
20
27
  }
21
28
 
29
+ /**
30
+ * Emits a log entry by merging the provided data with a type and a
31
+ * millisecond-precision timestamp, then forwarding it to the callback.
32
+ * Note: the `$level` parameter is currently accepted but not included
33
+ * in the emitted log entry.
34
+ *
35
+ * @param string $level The log level (e.g. "info", "warning", "error").
36
+ * @param string $type The log entry type/category.
37
+ * @param array $data Additional data to include in the log entry.
38
+ * @return void
39
+ */
22
40
  public function log(string $level, string $type, array $data): void
23
41
  {
24
42
  $logEntry = array_merge($data, [
@@ -5,12 +5,12 @@ declare(strict_types=1);
5
5
  namespace Anonympins\Fingerprint\Utils;
6
6
 
7
7
  /**
8
- * Fournit des fonctions pour détecter les patterns d'injection malveillants.
8
+ * Provides utilities to detect malicious injection patterns.
9
9
  */
10
10
  class MaliciousPatterns
11
11
  {
12
12
  /**
13
- * @var array<string, string> Map des patterns malveillants regroupés par type.
13
+ * @var array<string, string> Map of malicious patterns grouped by threat category.
14
14
  */
15
15
  private const INJECTION_PATTERNS = [
16
16
  // SQL/NoSQL injections, including time-based attacks
@@ -44,10 +44,10 @@ class MaliciousPatterns
44
44
  ];
45
45
 
46
46
  /**
47
- * Vérifie si une chaîne de caractères contient des patterns d'injection connus.
48
- * @param string $str La chaîne à vérifier.
49
- * @param array<string> $typesToDetect Les types d'injections à détecter (par défaut, tous).
50
- * @return bool True si un pattern malveillant est détecté, false sinon.
47
+ * Checks whether a string matches known injection patterns.
48
+ * @param string $str The input string to inspect.
49
+ * @param array<string> $typesToDetect Specific injection categories to detect (defaults to all).
50
+ * @return bool True if a malicious pattern is detected, false otherwise.
51
51
  */
52
52
  public static function isMalicious(string $str, array $typesToDetect = []): bool
53
53
  {
@@ -1,210 +1,210 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Utils;
6
-
7
- class MetricsManager
8
- {
9
- /** @var array Stockage temporaire des compteurs Prometheus */
10
- private static array $counters = [];
11
-
12
- /** @var array Stockage temporaire des observations Prometheus */
13
- private static array $observations = [];
14
-
15
- /**
16
- * Incrémente un compteur Prometheus.
17
- *
18
- * @param string $name Nom du compteur.
19
- * @param array $labels Libellés/Labels associés.
20
- */
21
- public static function incrementCounter(string $name, array $labels = []): void
22
- {
23
- if (strpos($name, 'fingerprint_') !== 0) {
24
- $name = 'fingerprint_' . $name;
25
- }
26
- ksort($labels);
27
- $labelPairs = [];
28
- foreach ($labels as $k => $v) {
29
- $labelPairs[] = "{$k}=\"{$v}\"";
30
- }
31
- $labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
32
- $key = $name . $labelsStr;
33
-
34
- if (!isset(self::$counters[$key])) {
35
- self::$counters[$key] = [
36
- 'name' => $name,
37
- 'labelsStr' => $labelsStr,
38
- 'value' => 0
39
- ];
40
- }
41
- self::$counters[$key]['value']++;
42
- }
43
-
44
- /**
45
- * Enregistre une observation de valeur (ex: temps d'exécution, score).
46
- *
47
- * @param string $name Nom de la métrique.
48
- * @param float $value Valeur observée.
49
- * @param array $labels Libellés/Labels associés.
50
- */
51
- public static function observeValue(string $name, float $value, array $labels = []): void
52
- {
53
- if (strpos($name, 'fingerprint_') !== 0) {
54
- $name = 'fingerprint_' . $name;
55
- }
56
- ksort($labels);
57
- $labelPairs = [];
58
- foreach ($labels as $k => $v) {
59
- $labelPairs[] = "{$k}=\"{$v}\"";
60
- }
61
- $labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
62
- $key = $name . $labelsStr;
63
-
64
- self::$observations[$key] = [
65
- 'name' => $name,
66
- 'labelsStr' => $labelsStr,
67
- 'value' => $value
68
- ];
69
- }
70
-
71
- /**
72
- * Helper method to retrieve a specific metric's values by name.
73
- *
74
- * @param string $name Name of the metric.
75
- * @param array $securityConfig The security configuration.
76
- * @return array Associative array of labels to values.
77
- */
78
- public static function getMetric(string $name, array $securityConfig = []): array
79
- {
80
- $result = [];
81
- $queryName = str_starts_with($name, 'fingerprint_') ? $name : 'fingerprint_' . $name;
82
-
83
- if ($queryName === 'fingerprint_security_weight') {
84
- $weights = $securityConfig['weights'] ?? [];
85
- foreach ($weights as $indicator => $weight) {
86
- if (is_numeric($weight)) {
87
- $result[$indicator] = (float)$weight;
88
- }
89
- }
90
- } elseif ($queryName === 'fingerprint_security_threshold') {
91
- $thresholds = $securityConfig['thresholds'] ?? [];
92
- foreach ($thresholds as $level => $threshold) {
93
- if (is_numeric($threshold)) {
94
- $result[$level] = (float)$threshold;
95
- }
96
- }
97
- } else {
98
- foreach (self::$counters as $counter) {
99
- if ($counter['name'] === $queryName || $counter['name'] === $name) {
100
- $labelStr = trim($counter['labelsStr'], '{}');
101
- $result[$labelStr !== '' ? $labelStr : 'value'] = $counter['value'];
102
- }
103
- }
104
- foreach (self::$observations as $obs) {
105
- if ($obs['name'] === $queryName || $obs['name'] === $name) {
106
- $labelStr = trim($obs['labelsStr'], '{}');
107
- $result[$labelStr !== '' ? $labelStr : 'value'] = $obs['value'];
108
- }
109
- }
110
- }
111
- return $result;
112
- }
113
-
114
- /**
115
- * Réinitialise les compteurs enregistrés (utile pour l'isolation des tests).
116
- */
117
- public static function clearMetrics(): void
118
- {
119
- self::$counters = [];
120
- self::$observations = [];
121
- }
122
-
123
- /**
124
- * Génère les métriques au format Prometheus text/plain.
125
- *
126
- * @param array $securityConfig La configuration de sécurité active.
127
- * @param array|null $lastBestSolution La dernière solution calculée par l'Auto-Tuner.
128
- * @return string
129
- */
130
- public static function getPrometheusMetrics(array $securityConfig = [], ?array $lastBestSolution = null): string
131
- {
132
- $metrics = "";
133
-
134
- if (empty(self::$counters)) {
135
- $metrics .= "# HELP fingerprint_requests_total Total requests processed.\n";
136
- $metrics .= "# TYPE fingerprint_requests_total counter\n";
137
- $metrics .= "fingerprint_requests_total{status=\"passed\"} 1\n";
138
- } else {
139
- $grouped = [];
140
- foreach (self::$counters as $c) {
141
- $grouped[$c['name']][] = $c;
142
- }
143
- foreach ($grouped as $name => $instances) {
144
- $metrics .= "# HELP {$name} Total requests processed.\n";
145
- $metrics .= "# TYPE {$name} counter\n";
146
- foreach ($instances as $instance) {
147
- $metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
148
- }
149
- }
150
- }
151
-
152
- // Export des observations (Gauges)
153
- if (!empty(self::$observations)) {
154
- $groupedObs = [];
155
- foreach (self::$observations as $obs) {
156
- $groupedObs[$obs['name']][] = $obs;
157
- }
158
- foreach ($groupedObs as $name => $instances) {
159
- $metrics .= "\n# HELP {$name} Value observation.\n";
160
- $metrics .= "# TYPE {$name} gauge\n";
161
- foreach ($instances as $instance) {
162
- $metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
163
- }
164
- }
165
- }
166
-
167
- // 1. Export des poids actifs (Weights)
168
- if (isset($securityConfig['weights']) && is_array($securityConfig['weights'])) {
169
- $metrics .= "\n# HELP fingerprint_security_weight Active weight for each suspicion indicator.\n";
170
- $metrics .= "# TYPE fingerprint_security_weight gauge\n";
171
- foreach ($securityConfig['weights'] as $indicator => $weight) {
172
- if (is_numeric($weight)) {
173
- $metrics .= "fingerprint_security_weight{indicator=\"{$indicator}\"} {$weight}\n";
174
- }
175
- }
176
- }
177
-
178
- // 2. Export des seuils actifs (Thresholds)
179
- if (isset($securityConfig['thresholds']) && is_array($securityConfig['thresholds'])) {
180
- $metrics .= "\n# HELP fingerprint_security_threshold Active score threshold for each enforcement action level.\n";
181
- $metrics .= "# TYPE fingerprint_security_threshold gauge\n";
182
- foreach ($securityConfig['thresholds'] as $level => $threshold) {
183
- if (is_numeric($threshold)) {
184
- $metrics .= "fingerprint_security_threshold{level=\"{$level}\"} {$threshold}\n";
185
- }
186
- }
187
- }
188
-
189
- // 3. Récupération auto de la dernière solution d'auto-tuning depuis le cache (savePath) si non fournie
190
- if ($lastBestSolution === null && isset($securityConfig['autotuning']['savePath'])) {
191
- $savePath = $securityConfig['autotuning']['savePath'];
192
- if (file_exists($savePath)) {
193
- $savedData = json_decode(file_get_contents($savePath), true);
194
- if (is_array($savedData) && isset($savedData['objectives'])) {
195
- $lastBestSolution = $savedData;
196
- }
197
- }
198
- }
199
-
200
- // 4. Export des objectifs d'Auto-Tuning (Faux positifs & Faux négatifs calculés)
201
- if ($lastBestSolution !== null && isset($lastBestSolution['objectives']) && is_array($lastBestSolution['objectives'])) {
202
- $fpr = $lastBestSolution['objectives'][0] ?? 0.0;
203
- $fnr = $lastBestSolution['objectives'][1] ?? 0.0;
204
- $metrics .= "\n# HELP fingerprint_autotuning_false_positive_rate Current false positive rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_positive_rate gauge\nfingerprint_autotuning_false_positive_rate {$fpr}\n";
205
- $metrics .= "\n# HELP fingerprint_autotuning_false_negative_rate Current false negative rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_negative_rate gauge\nfingerprint_autotuning_false_negative_rate {$fnr}\n";
206
- }
207
-
208
- return $metrics;
209
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Utils;
6
+
7
+ class MetricsManager
8
+ {
9
+ /** @var array In-memory storage for Prometheus counters */
10
+ private static array $counters = [];
11
+
12
+ /** @var array In-memory storage for Prometheus gauge observations */
13
+ private static array $observations = [];
14
+
15
+ /**
16
+ * Increments a Prometheus counter.
17
+ *
18
+ * @param string $name Counter name.
19
+ * @param array $labels Associated key-value labels.
20
+ */
21
+ public static function incrementCounter(string $name, array $labels = []): void
22
+ {
23
+ if (strpos($name, 'fingerprint_') !== 0) {
24
+ $name = 'fingerprint_' . $name;
25
+ }
26
+ ksort($labels);
27
+ $labelPairs = [];
28
+ foreach ($labels as $k => $v) {
29
+ $labelPairs[] = "{$k}=\"{$v}\"";
30
+ }
31
+ $labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
32
+ $key = $name . $labelsStr;
33
+
34
+ if (!isset(self::$counters[$key])) {
35
+ self::$counters[$key] = [
36
+ 'name' => $name,
37
+ 'labelsStr' => $labelsStr,
38
+ 'value' => 0
39
+ ];
40
+ }
41
+ self::$counters[$key]['value']++;
42
+ }
43
+
44
+ /**
45
+ * Observes a gauge value (e.g. execution time, score).
46
+ *
47
+ * @param string $name Metric name.
48
+ * @param float $value Observed value.
49
+ * @param array $labels Associated key-value labels.
50
+ */
51
+ public static function observeValue(string $name, float $value, array $labels = []): void
52
+ {
53
+ if (strpos($name, 'fingerprint_') !== 0) {
54
+ $name = 'fingerprint_' . $name;
55
+ }
56
+ ksort($labels);
57
+ $labelPairs = [];
58
+ foreach ($labels as $k => $v) {
59
+ $labelPairs[] = "{$k}=\"{$v}\"";
60
+ }
61
+ $labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
62
+ $key = $name . $labelsStr;
63
+
64
+ self::$observations[$key] = [
65
+ 'name' => $name,
66
+ 'labelsStr' => $labelsStr,
67
+ 'value' => $value
68
+ ];
69
+ }
70
+
71
+ /**
72
+ * Helper method to retrieve a specific metric's values by name.
73
+ *
74
+ * @param string $name Name of the metric.
75
+ * @param array $securityConfig The security configuration.
76
+ * @return array Associative array of labels to values.
77
+ */
78
+ public static function getMetric(string $name, array $securityConfig = []): array
79
+ {
80
+ $result = [];
81
+ $queryName = str_starts_with($name, 'fingerprint_') ? $name : 'fingerprint_' . $name;
82
+
83
+ if ($queryName === 'fingerprint_security_weight') {
84
+ $weights = $securityConfig['weights'] ?? [];
85
+ foreach ($weights as $indicator => $weight) {
86
+ if (is_numeric($weight)) {
87
+ $result[$indicator] = (float)$weight;
88
+ }
89
+ }
90
+ } elseif ($queryName === 'fingerprint_security_threshold') {
91
+ $thresholds = $securityConfig['thresholds'] ?? [];
92
+ foreach ($thresholds as $level => $threshold) {
93
+ if (is_numeric($threshold)) {
94
+ $result[$level] = (float)$threshold;
95
+ }
96
+ }
97
+ } else {
98
+ foreach (self::$counters as $counter) {
99
+ if ($counter['name'] === $queryName || $counter['name'] === $name) {
100
+ $labelStr = trim($counter['labelsStr'], '{}');
101
+ $result[$labelStr !== '' ? $labelStr : 'value'] = $counter['value'];
102
+ }
103
+ }
104
+ foreach (self::$observations as $obs) {
105
+ if ($obs['name'] === $queryName || $obs['name'] === $name) {
106
+ $labelStr = trim($obs['labelsStr'], '{}');
107
+ $result[$labelStr !== '' ? $labelStr : 'value'] = $obs['value'];
108
+ }
109
+ }
110
+ }
111
+ return $result;
112
+ }
113
+
114
+ /**
115
+ * Resets recorded metrics (useful for test isolation).
116
+ */
117
+ public static function clearMetrics(): void
118
+ {
119
+ self::$counters = [];
120
+ self::$observations = [];
121
+ }
122
+
123
+ /**
124
+ * Exports metrics in Prometheus text/plain format.
125
+ *
126
+ * @param array $securityConfig Active security configuration.
127
+ * @param array|null $lastBestSolution Latest solution computed by the Auto-Tuner.
128
+ * @return string
129
+ */
130
+ public static function getPrometheusMetrics(array $securityConfig = [], ?array $lastBestSolution = null): string
131
+ {
132
+ $metrics = "";
133
+
134
+ if (empty(self::$counters)) {
135
+ $metrics .= "# HELP fingerprint_requests_total Total requests processed.\n";
136
+ $metrics .= "# TYPE fingerprint_requests_total counter\n";
137
+ $metrics .= "fingerprint_requests_total{status=\"passed\"} 1\n";
138
+ } else {
139
+ $grouped = [];
140
+ foreach (self::$counters as $c) {
141
+ $grouped[$c['name']][] = $c;
142
+ }
143
+ foreach ($grouped as $name => $instances) {
144
+ $metrics .= "# HELP {$name} Total requests processed.\n";
145
+ $metrics .= "# TYPE {$name} counter\n";
146
+ foreach ($instances as $instance) {
147
+ $metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
148
+ }
149
+ }
150
+ }
151
+
152
+ // Export observations (Gauges)
153
+ if (!empty(self::$observations)) {
154
+ $groupedObs = [];
155
+ foreach (self::$observations as $obs) {
156
+ $groupedObs[$obs['name']][] = $obs;
157
+ }
158
+ foreach ($groupedObs as $name => $instances) {
159
+ $metrics .= "\n# HELP {$name} Value observation.\n";
160
+ $metrics .= "# TYPE {$name} gauge\n";
161
+ foreach ($instances as $instance) {
162
+ $metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
163
+ }
164
+ }
165
+ }
166
+
167
+ // 1. Export active weights
168
+ if (isset($securityConfig['weights']) && is_array($securityConfig['weights'])) {
169
+ $metrics .= "\n# HELP fingerprint_security_weight Active weight for each suspicion indicator.\n";
170
+ $metrics .= "# TYPE fingerprint_security_weight gauge\n";
171
+ foreach ($securityConfig['weights'] as $indicator => $weight) {
172
+ if (is_numeric($weight)) {
173
+ $metrics .= "fingerprint_security_weight{indicator=\"{$indicator}\"} {$weight}\n";
174
+ }
175
+ }
176
+ }
177
+
178
+ // 2. Export active thresholds
179
+ if (isset($securityConfig['thresholds']) && is_array($securityConfig['thresholds'])) {
180
+ $metrics .= "\n# HELP fingerprint_security_threshold Active score threshold for each enforcement action level.\n";
181
+ $metrics .= "# TYPE fingerprint_security_threshold gauge\n";
182
+ foreach ($securityConfig['thresholds'] as $level => $threshold) {
183
+ if (is_numeric($threshold)) {
184
+ $metrics .= "fingerprint_security_threshold{level=\"{$level}\"} {$threshold}\n";
185
+ }
186
+ }
187
+ }
188
+
189
+ // 3. Auto-load latest auto-tuning solution from cache/file if not provided
190
+ if ($lastBestSolution === null && isset($securityConfig['autotuning']['savePath'])) {
191
+ $savePath = $securityConfig['autotuning']['savePath'];
192
+ if (file_exists($savePath)) {
193
+ $savedData = json_decode(file_get_contents($savePath), true);
194
+ if (is_array($savedData) && isset($savedData['objectives'])) {
195
+ $lastBestSolution = $savedData;
196
+ }
197
+ }
198
+ }
199
+
200
+ // 4. Export auto-tuning objectives (calculated FPR and FNR)
201
+ if ($lastBestSolution !== null && isset($lastBestSolution['objectives']) && is_array($lastBestSolution['objectives'])) {
202
+ $fpr = $lastBestSolution['objectives'][0] ?? 0.0;
203
+ $fnr = $lastBestSolution['objectives'][1] ?? 0.0;
204
+ $metrics .= "\n# HELP fingerprint_autotuning_false_positive_rate Current false positive rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_positive_rate gauge\nfingerprint_autotuning_false_positive_rate {$fpr}\n";
205
+ $metrics .= "\n# HELP fingerprint_autotuning_false_negative_rate Current false negative rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_negative_rate gauge\nfingerprint_autotuning_false_negative_rate {$fnr}\n";
206
+ }
207
+
208
+ return $metrics;
209
+ }
210
210
  }