@anonympins/fingerprint 0.3.6 → 0.3.7

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.
@@ -1,91 +1,91 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint;
6
-
7
- /**
8
- * Représente le contexte d'une requête HTTP, fournissant un accès unifié
9
- * aux informations nécessaires pour l'analyse de l'empreinte.
10
- * Cette classe est conçue pour être créée à partir d'un objet de requête
11
- * standard (ex: PSR-7, Symfony, Laravel).
12
- */
13
- class RequestContext
14
- {
15
- public string $clientIp;
16
- public string $path;
17
- /** @var array<string, string> */
18
- public array $headers;
19
- /** @var array<string, mixed> */
20
- public array $query;
21
- /** @var array<string, mixed>|object|null */
22
- public $body;
23
- /** @var array<string, string> */
24
- public array $cookies;
25
- public ?string $httpVersion;
26
- public int $requestTimestamp;
27
-
28
- /** @var ?array{type: string, name: string} */
29
- public ?array $graphqlOperation = null;
30
-
31
- /** @var ?array<string, mixed> */
32
- public ?array $newCookieForResponse = null;
33
-
34
- // Propriétés spécifiques qui peuvent être fournies par un proxy inverse
35
- public ?string $ja3;
36
- public ?string $ja4;
37
- public ?string $ja4s;
38
- public ?string $ja4h;
39
- public ?string $http2Fingerprint;
40
- public ?string $tcpFingerprint;
41
-
42
- /**
43
- * @param string $clientIp
44
- * @param string $path
45
- * @param array<string, string> $headers
46
- * @param array<string, mixed> $query
47
- * @param array<string, mixed>|object|null $body
48
- * @param array<string, string> $cookies
49
- * @param string|null $httpVersion
50
- * @param int|null $requestTimestamp
51
- */
52
- public function __construct(
53
- string $clientIp,
54
- string $path,
55
- array $headers,
56
- array $query,
57
- $body,
58
- array $cookies,
59
- ?string $httpVersion,
60
- ?int $requestTimestamp = null
61
- ) {
62
- $this->clientIp = $clientIp;
63
- $this->path = $path;
64
- // Normaliser les en-têtes en minuscules pour un accès cohérent
65
- $this->headers = array_change_key_case($headers, CASE_LOWER);
66
- $this->query = $query;
67
- $this->body = $body;
68
- $this->cookies = $cookies;
69
- $this->httpVersion = $httpVersion;
70
- $this->requestTimestamp = $requestTimestamp ?? (int)(microtime(true) * 1000);
71
-
72
- // Extraire les empreintes TLS/HTTP2/TCP si elles sont fournies par les en-têtes
73
- $this->ja3 = $this->headers['x-ja3-hash'] ?? null;
74
- $this->ja4 = $this->headers['x-ja4-hash'] ?? null;
75
- $this->ja4s = $this->headers['x-ja4s-hash'] ?? null;
76
- $this->ja4h = $this->headers['x-ja4h-hash'] ?? null;
77
- $this->http2Fingerprint = $this->headers['x-http2-fingerprint'] ?? null;
78
- $this->tcpFingerprint = $this->headers['x-tcp-fingerprint'] ?? null;
79
- }
80
- /**
81
- * Récupère la valeur d'un en-tête HTTP de manière insensible à la casse.
82
- *
83
- * @param string $name Le nom de l'en-tête.
84
- * @return string|null La valeur de l'en-tête ou null si non trouvé.
85
- */
86
-
87
- public function getHeader(string $name): ?string
88
- {
89
- return $this->headers[strtolower($name)] ?? null;
90
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint;
6
+
7
+ /**
8
+ * Représente le contexte d'une requête HTTP, fournissant un accès unifié
9
+ * aux informations nécessaires pour l'analyse de l'empreinte.
10
+ * Cette classe est conçue pour être créée à partir d'un objet de requête
11
+ * standard (ex: PSR-7, Symfony, Laravel).
12
+ */
13
+ class RequestContext
14
+ {
15
+ public string $clientIp;
16
+ public string $path;
17
+ /** @var array<string, string> */
18
+ public array $headers;
19
+ /** @var array<string, mixed> */
20
+ public array $query;
21
+ /** @var array<string, mixed>|object|null */
22
+ public $body;
23
+ /** @var array<string, string> */
24
+ public array $cookies;
25
+ public ?string $httpVersion;
26
+ public int $requestTimestamp;
27
+
28
+ /** @var ?array{type: string, name: string} */
29
+ public ?array $graphqlOperation = null;
30
+
31
+ /** @var ?array<string, mixed> */
32
+ public ?array $newCookieForResponse = null;
33
+
34
+ // Propriétés spécifiques qui peuvent être fournies par un proxy inverse
35
+ public ?string $ja3;
36
+ public ?string $ja4;
37
+ public ?string $ja4s;
38
+ public ?string $ja4h;
39
+ public ?string $http2Fingerprint;
40
+ public ?string $tcpFingerprint;
41
+
42
+ /**
43
+ * @param string $clientIp
44
+ * @param string $path
45
+ * @param array<string, string> $headers
46
+ * @param array<string, mixed> $query
47
+ * @param array<string, mixed>|object|null $body
48
+ * @param array<string, string> $cookies
49
+ * @param string|null $httpVersion
50
+ * @param int|null $requestTimestamp
51
+ */
52
+ public function __construct(
53
+ string $clientIp,
54
+ string $path,
55
+ array $headers,
56
+ array $query,
57
+ $body,
58
+ array $cookies,
59
+ ?string $httpVersion,
60
+ ?int $requestTimestamp = null
61
+ ) {
62
+ $this->clientIp = $clientIp;
63
+ $this->path = $path;
64
+ // Normaliser les en-têtes en minuscules pour un accès cohérent
65
+ $this->headers = array_change_key_case($headers, CASE_LOWER);
66
+ $this->query = $query;
67
+ $this->body = $body;
68
+ $this->cookies = $cookies;
69
+ $this->httpVersion = $httpVersion;
70
+ $this->requestTimestamp = $requestTimestamp ?? (int)(microtime(true) * 1000);
71
+
72
+ // Extraire les empreintes TLS/HTTP2/TCP si elles sont fournies par les en-têtes
73
+ $this->ja3 = $this->headers['x-ja3-hash'] ?? null;
74
+ $this->ja4 = $this->headers['x-ja4-hash'] ?? null;
75
+ $this->ja4s = $this->headers['x-ja4s-hash'] ?? null;
76
+ $this->ja4h = $this->headers['x-ja4h-hash'] ?? null;
77
+ $this->http2Fingerprint = $this->headers['x-http2-fingerprint'] ?? null;
78
+ $this->tcpFingerprint = $this->headers['x-tcp-fingerprint'] ?? null;
79
+ }
80
+ /**
81
+ * Récupère la valeur d'un en-tête HTTP de manière insensible à la casse.
82
+ *
83
+ * @param string $name Le nom de l'en-tête.
84
+ * @return string|null La valeur de l'en-tête ou null si non trouvé.
85
+ */
86
+
87
+ public function getHeader(string $name): ?string
88
+ {
89
+ return $this->headers[strtolower($name)] ?? null;
90
+ }
91
91
  }
@@ -0,0 +1,105 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Store;
6
+
7
+ /**
8
+ * Adaptateur de stockage MongoDB pour le moteur Fingerprint.
9
+ * Utilise la bibliothèque officielle mongodb/mongodb.
10
+ */
11
+ class MongoDbStore implements IStore
12
+ {
13
+ /**
14
+ * @var \MongoDB\Collection
15
+ */
16
+ private $collection;
17
+
18
+ /**
19
+ * @param \MongoDB\Collection $collection Collection dédiée au stockage des empreintes
20
+ */
21
+ public function __construct($collection)
22
+ {
23
+ $this->collection = $collection;
24
+ }
25
+
26
+ public function get(string $key)
27
+ {
28
+ $doc = $this->collection->findOne(['_id' => $key]);
29
+ if (!$doc) {
30
+ return null;
31
+ }
32
+
33
+ // Vérification d'expiration active pour bypasser le délai de 60s du démon de MongoDB
34
+ if (isset($doc['expiresAt'])) {
35
+ $expiresAt = $doc['expiresAt'];
36
+ if ($expiresAt instanceof \MongoDB\BSON\UTCDateTime) {
37
+ $expiresAtMs = $expiresAt->toDateTime()->getTimestamp() * 1000;
38
+ $nowMs = (int)(microtime(true) * 1000);
39
+ if ($expiresAtMs < $nowMs) {
40
+ $this->delete($key);
41
+ return null;
42
+ }
43
+ }
44
+ }
45
+
46
+ return isset($doc['value']) ? json_decode($doc['value'], true) : null;
47
+ }
48
+
49
+ public function set(string $key, $value, ?int $ttl = null): void
50
+ {
51
+ $doc = [
52
+ '_id' => $key,
53
+ 'value' => json_encode($value),
54
+ ];
55
+
56
+ if ($ttl !== null && $ttl > 0) {
57
+ $doc['expiresAt'] = new \MongoDB\BSON\UTCDateTime((time() + $ttl) * 1000);
58
+ }
59
+
60
+ $this->collection->replaceOne(
61
+ ['_id' => $key],
62
+ $doc,
63
+ ['upsert' => true]
64
+ );
65
+ }
66
+
67
+ public function has(string $key): bool
68
+ {
69
+ $doc = $this->collection->findOne(
70
+ ['_id' => $key],
71
+ ['projection' => ['expiresAt' => 1]]
72
+ );
73
+
74
+ if (!$doc) {
75
+ return false;
76
+ }
77
+
78
+ if (isset($doc['expiresAt']) && $doc['expiresAt'] instanceof \MongoDB\BSON\UTCDateTime) {
79
+ $expiresAtMs = $doc['expiresAt']->toDateTime()->getTimestamp() * 1000;
80
+ $nowMs = (int)(microtime(true) * 1000);
81
+ if ($expiresAtMs < $nowMs) {
82
+ $this->delete($key);
83
+ return false;
84
+ }
85
+ }
86
+
87
+ return true;
88
+ }
89
+
90
+ public function delete(string $key): void
91
+ {
92
+ $this->collection->deleteOne(['_id' => $key]);
93
+ }
94
+
95
+ /**
96
+ * Automatise la configuration de l'index TTL nécessaire dans MongoDB.
97
+ */
98
+ public function init(): void
99
+ {
100
+ $this->collection->createIndex(
101
+ ['expiresAt' => 1],
102
+ ['expireAfterSeconds' => 0]
103
+ );
104
+ }
105
+ }
@@ -0,0 +1,54 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Store;
6
+
7
+ /**
8
+ * Adaptateur de stockage Redis pour le moteur Fingerprint.
9
+ * Compatible avec phpredis et predis.
10
+ */
11
+ class RedisStore implements IStore
12
+ {
13
+ /**
14
+ * @var mixed Une instance de \Redis ou de \Predis\Client
15
+ */
16
+ private $redis;
17
+
18
+ /**
19
+ * @param mixed $redis Client Redis déjà configuré et connecté
20
+ */
21
+ public function __construct($redis)
22
+ {
23
+ $this->redis = $redis;
24
+ }
25
+
26
+ public function get(string $key)
27
+ {
28
+ $value = $this->redis->get($key);
29
+ if ($value === false || $value === null) {
30
+ return null;
31
+ }
32
+ return json_decode($value, true);
33
+ }
34
+
35
+ public function set(string $key, $value, ?int $ttl = null): void
36
+ {
37
+ $stringValue = json_encode($value);
38
+ if ($ttl !== null && $ttl > 0) {
39
+ $this->redis->setex($key, $ttl, $stringValue);
40
+ } else {
41
+ $this->redis->set($key, $stringValue);
42
+ }
43
+ }
44
+
45
+ public function has(string $key): bool
46
+ {
47
+ return (bool)$this->redis->exists($key);
48
+ }
49
+
50
+ public function delete(string $key): void
51
+ {
52
+ $this->redis->del($key);
53
+ }
54
+ }
@@ -4,9 +4,9 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint\Tests;
6
6
 
7
- use PHPUnit\Framework\TestCase;
8
7
  use Anonympins\Fingerprint\Challenge\ChallengeUtils;
9
8
  use Anonympins\Fingerprint\Utils\BigInt;
9
+ use PHPUnit\Framework\TestCase;
10
10
 
11
11
  class ChallengeUtilsTest extends TestCase
12
12
  {
@@ -4,8 +4,8 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint\Tests;
6
6
 
7
- use PHPUnit\Framework\TestCase;
8
7
  use Anonympins\Fingerprint\FingerprintBuilder;
8
+ use PHPUnit\Framework\TestCase;
9
9
 
10
10
  class FingerprintBuilderTest extends TestCase
11
11
  {
@@ -4,13 +4,13 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint\Tests;
6
6
 
7
- use PHPUnit\Framework\TestCase;
8
- use Anonympins\Fingerprint\FingerprintEngine;
7
+ use Anonympins\Fingerprint\Config\SecurityProfiles;
9
8
  use Anonympins\Fingerprint\FingerprintBuilder;
9
+ use Anonympins\Fingerprint\FingerprintEngine;
10
10
  use Anonympins\Fingerprint\RequestContext;
11
- use Anonympins\Fingerprint\Store\StoreManager;
12
11
  use Anonympins\Fingerprint\Store\InMemoryStore;
13
- use Anonympins\Fingerprint\Config\SecurityProfiles;
12
+ use Anonympins\Fingerprint\Store\StoreManager;
13
+ use PHPUnit\Framework\TestCase;
14
14
 
15
15
  class FingerprintEngineTest extends TestCase
16
16
  {
@@ -4,11 +4,11 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint\Tests;
6
6
 
7
- use PHPUnit\Framework\TestCase;
8
- use Anonympins\Fingerprint\Utils\RequestUtils;
9
- use Anonympins\Fingerprint\Store\StoreManager;
10
- use Anonympins\Fingerprint\Store\IStore;
11
7
  use Anonympins\Fingerprint\RequestContext;
8
+ use Anonympins\Fingerprint\Store\IStore;
9
+ use Anonympins\Fingerprint\Store\StoreManager;
10
+ use Anonympins\Fingerprint\Utils\RequestUtils;
11
+ use PHPUnit\Framework\TestCase;
12
12
 
13
13
  class IpReputationTest extends TestCase
14
14
  {
@@ -4,8 +4,8 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint\Tests;
6
6
 
7
- use PHPUnit\Framework\TestCase;
8
7
  use Anonympins\Fingerprint\Ja3AnomalyDetector;
8
+ use PHPUnit\Framework\TestCase;
9
9
 
10
10
  /**
11
11
  * Tests unitaires pour la classe Ja3AnomalyDetector.
@@ -0,0 +1,46 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Tests;
6
+
7
+ use Anonympins\Fingerprint\RequestContext;
8
+ use Anonympins\Fingerprint\DirectFingerprint;
9
+ use Anonympins\Fingerprint\Utils\MetricsManager;
10
+ use Anonympins\Fingerprint\Store\InMemoryStore;
11
+ use Anonympins\Fingerprint\Store\StoreManager;
12
+ use PHPUnit\Framework\TestCase;
13
+
14
+ class MetricsTest extends TestCase
15
+ {
16
+ protected function setUp(): void
17
+ {
18
+ parent::setUp();
19
+ StoreManager::configureStore(new InMemoryStore());
20
+ }
21
+
22
+ public function testIncrementCounterAndGetPrometheusMetrics(): void
23
+ {
24
+ MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
25
+ $metrics = MetricsManager::getPrometheusMetrics();
26
+
27
+ $this->assertStringContainsString('fingerprint_requests_total', $metrics);
28
+ $this->assertStringContainsString('status="passed"', $metrics);
29
+ }
30
+
31
+ public function testGetPrometheusMetricsViaDirectFingerprint(): void
32
+ {
33
+ $config = [
34
+ 'metricsAuthorizationCallback' => function (RequestContext $context) {
35
+ return true;
36
+ }
37
+ ];
38
+
39
+ $direct = new DirectFingerprint($config);
40
+ MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
41
+ $metrics = $direct->getPrometheusMetrics();
42
+
43
+ $this->assertNotNull($metrics);
44
+ $this->assertStringContainsString('fingerprint_requests_total', $metrics);
45
+ }
46
+ }
@@ -4,12 +4,12 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint\Tests;
6
6
 
7
- use PHPUnit\Framework\TestCase;
7
+ use Anonympins\Fingerprint\Config\SecurityProfiles;
8
8
  use Anonympins\Fingerprint\FingerprintEngine;
9
9
  use Anonympins\Fingerprint\ProblemManager;
10
10
  use Anonympins\Fingerprint\Store\InMemoryStore;
11
11
  use Anonympins\Fingerprint\Store\StoreManager;
12
- use Anonympins\Fingerprint\Config\SecurityProfiles;
12
+ use PHPUnit\Framework\TestCase;
13
13
 
14
14
  class PowTest extends TestCase
15
15
  {
@@ -4,11 +4,13 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint\Tests;
6
6
 
7
- use PHPUnit\Framework\TestCase;
7
+ use Anonympins\Fingerprint\Optimization\FunctionRegistry;
8
8
  use Anonympins\Fingerprint\ProblemManager;
9
- use Anonympins\Fingerprint\Store\IStore;
10
9
  use Anonympins\Fingerprint\Store\InMemoryStore;
11
- use Anonympins\Fingerprint\Optimization\FunctionRegistry; // Assurez-vous que cette classe est autoloadable
10
+ use Anonympins\Fingerprint\Store\IStore;
11
+ use PHPUnit\Framework\TestCase;
12
+
13
+ // Assurez-vous que cette classe est autoloadable
12
14
 
13
15
  class ProblemManagerTest extends TestCase
14
16
  {
@@ -4,9 +4,9 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint\Tests;
6
6
 
7
- use PHPUnit\Framework\TestCase;
8
7
  use Anonympins\Fingerprint\RequestContext;
9
8
  use Anonympins\Fingerprint\Utils\RequestUtils;
9
+ use PHPUnit\Framework\TestCase;
10
10
 
11
11
  class RequestUtilsTest extends TestCase
12
12
  {
@@ -0,0 +1,167 @@
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
+ * Réinitialise les compteurs enregistrés (utile pour l'isolation des tests).
73
+ */
74
+ public static function clearMetrics(): void
75
+ {
76
+ self::$counters = [];
77
+ self::$observations = [];
78
+ }
79
+
80
+ /**
81
+ * Génère les métriques au format Prometheus text/plain.
82
+ *
83
+ * @param array $securityConfig La configuration de sécurité active.
84
+ * @param array|null $lastBestSolution La dernière solution calculée par l'Auto-Tuner.
85
+ * @return string
86
+ */
87
+ public static function getPrometheusMetrics(array $securityConfig = [], ?array $lastBestSolution = null): string
88
+ {
89
+ $metrics = "";
90
+
91
+ if (empty(self::$counters)) {
92
+ $metrics .= "# HELP fingerprint_requests_total Total requests processed.\n";
93
+ $metrics .= "# TYPE fingerprint_requests_total counter\n";
94
+ $metrics .= "fingerprint_requests_total{status=\"passed\"} 1\n";
95
+ } else {
96
+ $grouped = [];
97
+ foreach (self::$counters as $c) {
98
+ $grouped[$c['name']][] = $c;
99
+ }
100
+ foreach ($grouped as $name => $instances) {
101
+ $metrics .= "# HELP {$name} Total requests processed.\n";
102
+ $metrics .= "# TYPE {$name} counter\n";
103
+ foreach ($instances as $instance) {
104
+ $metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
105
+ }
106
+ }
107
+ }
108
+
109
+ // Export des observations (Gauges)
110
+ if (!empty(self::$observations)) {
111
+ $groupedObs = [];
112
+ foreach (self::$observations as $obs) {
113
+ $groupedObs[$obs['name']][] = $obs;
114
+ }
115
+ foreach ($groupedObs as $name => $instances) {
116
+ $metrics .= "\n# HELP {$name} Value observation.\n";
117
+ $metrics .= "# TYPE {$name} gauge\n";
118
+ foreach ($instances as $instance) {
119
+ $metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
120
+ }
121
+ }
122
+ }
123
+
124
+ // 1. Export des poids actifs (Weights)
125
+ if (isset($securityConfig['weights']) && is_array($securityConfig['weights'])) {
126
+ $metrics .= "\n# HELP fingerprint_security_weight Active weight for each suspicion indicator.\n";
127
+ $metrics .= "# TYPE fingerprint_security_weight gauge\n";
128
+ foreach ($securityConfig['weights'] as $indicator => $weight) {
129
+ if (is_numeric($weight)) {
130
+ $metrics .= "fingerprint_security_weight{indicator=\"{$indicator}\"} {$weight}\n";
131
+ }
132
+ }
133
+ }
134
+
135
+ // 2. Export des seuils actifs (Thresholds)
136
+ if (isset($securityConfig['thresholds']) && is_array($securityConfig['thresholds'])) {
137
+ $metrics .= "\n# HELP fingerprint_security_threshold Active score threshold for each enforcement action level.\n";
138
+ $metrics .= "# TYPE fingerprint_security_threshold gauge\n";
139
+ foreach ($securityConfig['thresholds'] as $level => $threshold) {
140
+ if (is_numeric($threshold)) {
141
+ $metrics .= "fingerprint_security_threshold{level=\"{$level}\"} {$threshold}\n";
142
+ }
143
+ }
144
+ }
145
+
146
+ // 3. Récupération auto de la dernière solution d'auto-tuning depuis le cache (savePath) si non fournie
147
+ if ($lastBestSolution === null && isset($securityConfig['autotuning']['savePath'])) {
148
+ $savePath = $securityConfig['autotuning']['savePath'];
149
+ if (file_exists($savePath)) {
150
+ $savedData = json_decode(file_get_contents($savePath), true);
151
+ if (is_array($savedData) && isset($savedData['objectives'])) {
152
+ $lastBestSolution = $savedData;
153
+ }
154
+ }
155
+ }
156
+
157
+ // 4. Export des objectifs d'Auto-Tuning (Faux positifs & Faux négatifs calculés)
158
+ if ($lastBestSolution !== null && isset($lastBestSolution['objectives']) && is_array($lastBestSolution['objectives'])) {
159
+ $fpr = $lastBestSolution['objectives'][0] ?? 0.0;
160
+ $fnr = $lastBestSolution['objectives'][1] ?? 0.0;
161
+ $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";
162
+ $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";
163
+ }
164
+
165
+ return $metrics;
166
+ }
167
+ }