@anonympins/fingerprint 0.6.1 → 0.6.3

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/src/js/library.js CHANGED
@@ -1646,17 +1646,24 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1646
1646
  high: 70 + secureRandom() * 20,
1647
1647
  },
1648
1648
  weights: {
1649
- historyScore: secureRandom(),
1650
- rotationScore: secureRandom(),
1651
- headerAnomalyScore: secureRandom(),
1652
- requestPatternScore: 0.5 + secureRandom(),
1653
- inconsistencyScore: secureRandom(),
1654
- honeypotScore: 1.0, // Garder le honeypot à 1.0 est une bonne pratique
1655
- behaviorScore: secureRandom(),
1656
- crossLayerInconsistencyScore: secureRandom(),
1657
- timeInconsistencyScore: secureRandom(),
1658
- tlsSpoofingScore: secureRandom(),
1659
- botScore: secureRandom(),
1649
+ historyScore: secureRandom(),
1650
+ rotationScore: secureRandom(),
1651
+ headerAnomalyScore: secureRandom(),
1652
+ requestPatternScore: 0.5 + secureRandom(),
1653
+ inconsistencyScore: secureRandom(),
1654
+ honeypotScore: 1.0, // Garder le honeypot à 1.0 est une bonne pratique
1655
+ behaviorScore: secureRandom(),
1656
+ crossLayerInconsistencyScore: secureRandom(),
1657
+ timeInconsistencyScore: secureRandom(),
1658
+ tlsSpoofingScore: secureRandom(),
1659
+ botScore: secureRandom(),
1660
+ subnetScore: secureRandom(),
1661
+ ipReputationScore: secureRandom(),
1662
+ botnetClusterScore: secureRandom(),
1663
+ tcpAnomalyScore: secureRandom(),
1664
+ quicAnomalyScore: secureRandom(),
1665
+ clickVarianceScore: secureRandom(),
1666
+ clientHintsInconsistencyScore: secureRandom()
1660
1667
  },
1661
1668
  patterns: {
1662
1669
  velocityThreshold: 100 + secureRandom() * 400,
@@ -48,8 +48,12 @@ FunctionRegistry['facility.calculateEnergy'] = (facilities, payload) => {
48
48
  * @param {Array<{x: number, y: number}>} path - Un tableau de points représentant le chemin.
49
49
  * @returns {number} La distance totale du chemin.
50
50
  */
51
- FunctionRegistry['tsp.calculateEnergy'] = (path) => {
52
- // Crée un tableau d'indices [0, 1, 2, ...] pour la fonction evaluatePathDistance.
51
+ FunctionRegistry['tsp.calculateEnergy'] = (path, payload) => {
52
+ const cities = payload?.cities || payload?.points || [];
53
+ if (path && typeof path[0] === 'number') {
54
+ return Optimization.Utils.evaluatePathDistance(cities, path);
55
+ }
56
+ // Fallback: Crée un tableau d'indices [0, 1, 2, ...] pour la fonction evaluatePathDistance.
53
57
  const indices = Array.from({ length: path.length }, (_, i) => i);
54
58
  return Optimization.Utils.evaluatePathDistance(path, indices);
55
59
  };
@@ -2405,7 +2405,7 @@ describe('Subnet Scoring (Node.js)', () => {
2405
2405
  deviceIds: ['d1', 'd2'], // count < 10, score += 0
2406
2406
  });
2407
2407
  ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2408
- expect(subnetScore).toBe(10);
2408
+ expect(subnetScore).toBe(100);
2409
2409
 
2410
2410
  // 3. Many devices and high scores
2411
2411
  const deviceIds = Array.from({ length: 20 }, (_, i) => `d${i}`);
@@ -2414,7 +2414,7 @@ describe('Subnet Scoring (Node.js)', () => {
2414
2414
  deviceIds: deviceIds, // count = 20. score += (20-10)*5 = 50
2415
2415
  });
2416
2416
  ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2417
- expect(subnetScore).toBe(90); // 40 + 50
2417
+ expect(subnetScore).toBe(100);
2418
2418
  });
2419
2419
  });
2420
2420
 
@@ -2714,18 +2714,18 @@ describe('Botnet Cluster Scoring (Node.js)', () => {
2714
2714
  // 2. Ajout de 2 IPs uniques (total 3)
2715
2715
  await getBotnetClusterScore({ clientIp: '192.168.1.2' }, stableFpHash);
2716
2716
  scoreData = await getBotnetClusterScore({ clientIp: '192.168.1.3' }, stableFpHash);
2717
- expect(scoreData.botnetClusterScore).toBe(50.3);
2717
+ expect(scoreData.botnetClusterScore).toBe(30.2);
2718
2718
 
2719
2719
  // 3. Ajout de 2 IPs uniques (total 5)
2720
2720
  await getBotnetClusterScore({ clientIp: '192.168.1.4' }, stableFpHash);
2721
2721
  scoreData = await getBotnetClusterScore({ clientIp: '192.168.1.5' }, stableFpHash);
2722
- expect(scoreData.botnetClusterScore).toBe(75.3);
2722
+ expect(scoreData.botnetClusterScore).toBe(45.2);
2723
2723
 
2724
2724
  // 4. Ajout de 5 IPs uniques (total 10)
2725
2725
  for (let i = 6; i <= 10; i++) {
2726
2726
  scoreData = await getBotnetClusterScore({ clientIp: `192.168.1.${i}` }, stableFpHash);
2727
2727
  }
2728
- expect(scoreData.botnetClusterScore).toBe(95.7);
2728
+ expect(scoreData.botnetClusterScore).toBe(57.4);
2729
2729
  });
2730
2730
 
2731
2731
  it('should realistically group PS4 consoles with volatile differences (different IPs/cookies) under the same cluster score', async () => {
@@ -2759,15 +2759,15 @@ describe('Botnet Cluster Scoring (Node.js)', () => {
2759
2759
  if (i === 1) {
2760
2760
  expect(vector.botnetClusterScore).toBe(0);
2761
2761
  } else if (i === 2) {
2762
- expect(vector.botnetClusterScore).toBe(29.5);
2762
+ expect(vector.botnetClusterScore).toBe(17.7);
2763
2763
  } else if (i === 3) {
2764
- expect(vector.botnetClusterScore).toBe(50.3);
2764
+ expect(vector.botnetClusterScore).toBe(30.2);
2765
2765
  } else if (i === 4) {
2766
- expect(vector.botnetClusterScore).toBe(65);
2766
+ expect(vector.botnetClusterScore).toBe(39.0);
2767
2767
  } else if (i === 5) {
2768
- expect(vector.botnetClusterScore).toBe(75.3);
2768
+ expect(vector.botnetClusterScore).toBe(45.2);
2769
2769
  } else if (i === 10) {
2770
- expect(vector.botnetClusterScore).toBe(95.7);
2770
+ expect(vector.botnetClusterScore).toBe(57.4);
2771
2771
  }
2772
2772
  }
2773
2773
  });
@@ -2874,4 +2874,41 @@ describe('IP Registration and Filtering', () => {
2874
2874
  expect(storedData.ips.has(ip1)).toBe(true);
2875
2875
  expect(storedData.ips.has(ip2)).toBe(true);
2876
2876
  });
2877
+
2878
+ describe('Ed25519 Key Generation & Reopening', () => {
2879
+ beforeEach(() => {
2880
+ delete process.env.ED25519_PRIVATE_KEY;
2881
+ delete process.env.ED25519_PUBLIC_KEY;
2882
+ vi.restoreAllMocks();
2883
+ });
2884
+
2885
+ it('should generate new keys and save to disk if not present', async () => {
2886
+ const fs = await import('node:fs');
2887
+ const existsSpy = vi.spyOn(fs, 'existsSync').mockReturnValue(false);
2888
+ const writeSpy = vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
2889
+
2890
+ const config = { ed25519: 'auto', weights: {}, thresholds: {} };
2891
+ new FingerprintEngine(config);
2892
+
2893
+ expect(process.env.ED25519_PRIVATE_KEY).toBeDefined();
2894
+ expect(process.env.ED25519_PUBLIC_KEY).toBeDefined();
2895
+ expect(writeSpy).toHaveBeenCalled();
2896
+ });
2897
+
2898
+ it('should load keys from disk if they already exist', async () => {
2899
+ const fs = await import('node:fs');
2900
+ const dummyKeys = {
2901
+ privateKey: '-----BEGIN PRIVATE KEY-----\ndummy-private-key\n-----END PRIVATE KEY-----',
2902
+ publicKey: '-----BEGIN PUBLIC KEY-----\ndummy-public-key\n-----END PUBLIC KEY-----'
2903
+ };
2904
+ const existsSpy = vi.spyOn(fs, 'existsSync').mockReturnValue(true);
2905
+ const readSpy = vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(dummyKeys));
2906
+
2907
+ const config = { ed25519: 'auto', weights: {}, thresholds: {} };
2908
+ new FingerprintEngine(config);
2909
+
2910
+ expect(process.env.ED25519_PRIVATE_KEY).toBe(dummyKeys.privateKey);
2911
+ expect(process.env.ED25519_PUBLIC_KEY).toBe(dummyKeys.publicKey);
2912
+ });
2913
+ });
2877
2914
  });
@@ -1,104 +1,158 @@
1
- import { describe, expect, it, vi, beforeEach } from 'vitest';
2
- import { handleMetricsRequest, __internal } from '../fingerprint.js';
3
-
4
- describe('Metrics & Authorization Callback Integration', () => {
5
- let req, res;
6
-
7
- beforeEach(() => {
8
- req = {
9
- ip: '127.0.0.1',
10
- path: '/metrics',
11
- headers: { 'user-agent': 'prometheus' },
12
- query: {},
13
- body: {},
14
- cookies: {},
15
- httpVersion: '1.1'
16
- };
17
- res = {
18
- status: vi.fn().mockReturnThis(),
19
- send: vi.fn().mockReturnThis(),
20
- set: vi.fn().mockReturnThis(),
21
- redirect: vi.fn().mockReturnThis()
22
- };
23
- });
24
- it('should return metrics with 200 Content-Type if authorized', async () => {
25
- const config = {
26
- metricsAuthorizationCallback: () => true
27
- };
28
- await handleMetricsRequest(req, res, config);
29
-
30
- expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
31
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_requests_total'));
32
- });
33
-
34
- it('should include current weights and thresholds in prometheus metrics output', async () => {
35
- const config = {
36
- metricsAuthorizationCallback: () => true,
37
- weights: { historyScore: 0.35, rotationScore: 0.65 },
38
- thresholds: { low: 22, block: 92 }
39
- };
40
- await handleMetricsRequest(req, res, config);
41
-
42
- expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
43
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_weight{indicator="historyScore"} 0.35'));
44
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_weight{indicator="rotationScore"} 0.65'));
45
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_threshold{level="low"} 22'));
46
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_threshold{level="block"} 92'));
47
- });
48
-
49
- it('should include auto-tuning performance metrics if a best solution is available', async () => {
50
- __internal.setLastBestSolution({
51
- solution: {},
52
- objectives: [0.0123, 0.0456]
53
- });
54
-
55
- const config = {
56
- metricsAuthorizationCallback: () => true
57
- };
58
- await handleMetricsRequest(req, res, config);
59
-
60
- expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
61
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_autotuning_false_positive_rate 0.0123'));
62
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_autotuning_false_negative_rate 0.0456'));
63
-
64
- // Cleanup
65
- __internal.setLastBestSolution(null);
66
- });
67
-
68
- it('should return 403 if authorization callback returns false', async () => {
69
- const config = {
70
- metricsAuthorizationCallback: () => false
71
- };
72
- await handleMetricsRequest(req, res, config);
73
-
74
- expect(res.status).toHaveBeenCalledWith(403);
75
- expect(res.send).toHaveBeenCalledWith('Access to metrics denied.');
76
- });
77
-
78
- it('should handle custom block action returned by authorization callback', async () => {
79
- const config = {
80
- metricsAuthorizationCallback: () => ({
81
- action: 'block',
82
- status: 401,
83
- body: 'Custom unauthorized message'
84
- })
85
- };
86
- await handleMetricsRequest(req, res, config);
87
-
88
- expect(res.status).toHaveBeenCalledWith(401);
89
- expect(res.send).toHaveBeenCalledWith('Custom unauthorized message');
90
- });
91
-
92
- it('should handle custom redirect action returned by authorization callback', async () => {
93
- const config = {
94
- metricsAuthorizationCallback: () => ({
95
- action: 'redirect',
96
- status: 302,
97
- path: '/forbidden'
98
- })
99
- };
100
- await handleMetricsRequest(req, res, config);
101
-
102
- expect(res.redirect).toHaveBeenCalledWith(302, '/forbidden');
103
- });
1
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
2
+ import { handleMetricsRequest, getMetric, incrementCounter, observeValue, __internal } from '../fingerprint.js';
3
+
4
+ describe('Metrics & Authorization Callback Integration', () => {
5
+ let req, res;
6
+
7
+ beforeEach(() => {
8
+ req = {
9
+ ip: '127.0.0.1',
10
+ path: '/metrics',
11
+ headers: { 'user-agent': 'prometheus' },
12
+ query: {},
13
+ body: {},
14
+ cookies: {},
15
+ httpVersion: '1.1'
16
+ };
17
+ res = {
18
+ status: vi.fn().mockReturnThis(),
19
+ send: vi.fn().mockReturnThis(),
20
+ set: vi.fn().mockReturnThis(),
21
+ redirect: vi.fn().mockReturnThis()
22
+ };
23
+ });
24
+ it('should return metrics with 200 Content-Type if authorized', async () => {
25
+ const config = {
26
+ metricsAuthorizationCallback: () => true
27
+ };
28
+ await handleMetricsRequest(req, res, config);
29
+
30
+ expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
31
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_requests_total'));
32
+ });
33
+
34
+ it('should include current weights and thresholds in prometheus metrics output', async () => {
35
+ const config = {
36
+ metricsAuthorizationCallback: () => true,
37
+ weights: { historyScore: 0.35, rotationScore: 0.65 },
38
+ thresholds: { low: 22, block: 92 }
39
+ };
40
+ await handleMetricsRequest(req, res, config);
41
+
42
+ expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
43
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_weight{indicator="historyScore"} 0.35'));
44
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_weight{indicator="rotationScore"} 0.65'));
45
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_threshold{level="low"} 22'));
46
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_threshold{level="block"} 92'));
47
+ });
48
+
49
+ it('should include auto-tuning performance metrics if a best solution is available', async () => {
50
+ __internal.setLastBestSolution({
51
+ solution: {},
52
+ objectives: [0.0123, 0.0456]
53
+ });
54
+
55
+ const config = {
56
+ metricsAuthorizationCallback: () => true
57
+ };
58
+ await handleMetricsRequest(req, res, config);
59
+
60
+ expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
61
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_autotuning_false_positive_rate 0.0123'));
62
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_autotuning_false_negative_rate 0.0456'));
63
+
64
+ // Cleanup
65
+ __internal.setLastBestSolution(null);
66
+ });
67
+
68
+ it('should return 403 if authorization callback returns false', async () => {
69
+ const config = {
70
+ metricsAuthorizationCallback: () => false
71
+ };
72
+ await handleMetricsRequest(req, res, config);
73
+
74
+ expect(res.status).toHaveBeenCalledWith(403);
75
+ expect(res.send).toHaveBeenCalledWith('Access to metrics denied.');
76
+ });
77
+
78
+ it('should handle custom block action returned by authorization callback', async () => {
79
+ const config = {
80
+ metricsAuthorizationCallback: () => ({
81
+ action: 'block',
82
+ status: 401,
83
+ body: 'Custom unauthorized message'
84
+ })
85
+ };
86
+ await handleMetricsRequest(req, res, config);
87
+
88
+ expect(res.status).toHaveBeenCalledWith(401);
89
+ expect(res.send).toHaveBeenCalledWith('Custom unauthorized message');
90
+ });
91
+
92
+ it('should handle custom redirect action returned by authorization callback', async () => {
93
+ const config = {
94
+ metricsAuthorizationCallback: () => ({
95
+ action: 'redirect',
96
+ status: 302,
97
+ path: '/forbidden'
98
+ })
99
+ };
100
+ await handleMetricsRequest(req, res, config);
101
+
102
+ expect(res.redirect).toHaveBeenCalledWith(302, '/forbidden');
103
+ });
104
+ });
105
+
106
+ describe('getMetric Unit Tests', () => {
107
+ it('should retrieve security weights from config', () => {
108
+ const config = {
109
+ weights: { historyScore: 0.5, rotationScore: 0.2, someInvalidWeight: 'not-a-number' }
110
+ };
111
+ const weights = getMetric('security_weight', config);
112
+ expect(weights.get('historyScore')).toBe(0.5);
113
+ expect(weights.get('rotationScore')).toBe(0.2);
114
+ expect(weights.has('someInvalidWeight')).toBe(false);
115
+ });
116
+
117
+ it('should retrieve security thresholds from config', () => {
118
+ const config = {
119
+ thresholds: { low: 10, medium: 30, high: 70, block: 90 }
120
+ };
121
+ const thresholds = getMetric('security_threshold', config);
122
+ expect(thresholds.get('low')).toBe(10);
123
+ expect(thresholds.get('medium')).toBe(30);
124
+ expect(thresholds.get('high')).toBe(70);
125
+ expect(thresholds.get('block')).toBe(90);
126
+ });
127
+
128
+ it('should retrieve dynamic counters', () => {
129
+ incrementCounter('requests_total', { status: 'passed' });
130
+ incrementCounter('requests_total', { status: 'blocked' });
131
+
132
+ const metric = getMetric('requests_total');
133
+ expect(metric.get('status="passed"')).toBeGreaterThanOrEqual(1);
134
+ expect(metric.get('status="blocked"')).toBeGreaterThanOrEqual(1);
135
+ });
136
+
137
+ it('should retrieve dynamic observations', () => {
138
+ observeValue('suspicion_score', 45.5, { action: 'passed' });
139
+
140
+ const metric = getMetric('suspicion_score');
141
+ expect(metric.get('action="passed"')).toBe(45.5);
142
+ });
143
+
144
+ it('should retrieve autotuning false positive and negative rates', () => {
145
+ __internal.setLastBestSolution({
146
+ solution: {},
147
+ objectives: [0.015, 0.045]
148
+ });
149
+
150
+ const fpr = getMetric('autotuning_false_positive_rate');
151
+ const fnr = getMetric('autotuning_false_negative_rate');
152
+
153
+ expect(fpr.get('fpr')).toBe(0.015);
154
+ expect(fnr.get('fnr')).toBe(0.045);
155
+
156
+ __internal.setLastBestSolution(null);
157
+ });
104
158
  });
@@ -8,6 +8,7 @@ use Anonympins\Fingerprint\Store\StoreManager;
8
8
  use Anonympins\Fingerprint\FingerprintBuilder;
9
9
  use Anonympins\Fingerprint\Utils\BigInt;
10
10
  use Anonympins\Fingerprint\Utils\RequestUtils;
11
+ use Anonympins\Fingerprint\Utils\Env;
11
12
 
12
13
  /**
13
14
  * Classe utilitaire pour la génération et la vérification des challenges Proof-of-Work.
@@ -397,8 +398,9 @@ class ChallengeUtils
397
398
  */
398
399
  public static function getPowSecret(): string
399
400
  {
400
- $secret = $_ENV['POW_SECRET'] ?? getenv('POW_SECRET');
401
- if (!$secret && ($_ENV['APP_ENV'] ?? getenv('APP_ENV')) === 'production') {
401
+ $secret = Env::get('POW_SECRET');
402
+ $appEnv = Env::get('APP_ENV');
403
+ if (!$secret && $appEnv === 'production') {
402
404
  throw new \RuntimeException('POW_SECRET environment variable is not set. This is required for production.');
403
405
  }
404
406
  return $secret ?: "fallback-dev-secret-32-chars-minimum";
@@ -411,7 +413,7 @@ class ChallengeUtils
411
413
  */
412
414
  public static function generateStatelessTicket(array $payload): string
413
415
  {
414
- $ed25519Key = $_ENV['ED25519_PRIVATE_KEY'] ?? getenv('ED25519_PRIVATE_KEY');
416
+ $ed25519Key = Env::get('ED25519_PRIVATE_KEY');
415
417
  if ($ed25519Key) {
416
418
  try {
417
419
  $serialized = json_encode($payload);
@@ -457,7 +459,7 @@ class ChallengeUtils
457
459
  $payloadJson = $base64UrlDecode($parts[1]);
458
460
  $signature = $base64UrlDecode($parts[2]);
459
461
 
460
- $ed25519PubKey = $_ENV['ED25519_PUBLIC_KEY'] ?? getenv('ED25519_PUBLIC_KEY');
462
+ $ed25519PubKey = Env::get('ED25519_PUBLIC_KEY');
461
463
  if (!$ed25519PubKey) {
462
464
  error_log("[ChallengeUtils] ED25519_PUBLIC_KEY is not defined in environment.");
463
465
  return null;
@@ -43,7 +43,7 @@ class SecurityProfiles
43
43
  'tcpAnomalyScore' => 0.8, // NEW: Anomalie de pile TCP/IP
44
44
  'quicAnomalyScore' => 0.8, // NEW: Anomalie QUIC
45
45
  'renderingAnomalyScore' => 0.8, // NEW: Anomalie de rendu
46
-
46
+ 'ipReputationScore' => 0.5, // NOUVEAU: Poids pour la réputation IP
47
47
  ],
48
48
  'thresholds' => ['low' => 20, 'medium' => 45, 'high' => 75, 'block' => 95],
49
49
  'patterns' => [
@@ -177,13 +177,14 @@ class SecurityProfiles
177
177
  'honeypotScore' => 1.0, // Crucial for comment spam
178
178
  'crossLayerInconsistencyScore' => 0.4,
179
179
  'timeInconsistencyScore' => 0.8,
180
- 'tlsSpoofingScore' => 0.6,
180
+ 'tlsSpoofingScore' => 0.6, // Moins critique pour les blogs
181
181
  'botScore' => 0.8,
182
182
  'cookieDroppingScore' => 0.7, // Moins critique, mais toujours un signal
183
183
  'threatIntelScore' => 0.3, // Lower priority for a blog
184
184
  'clientHintsInconsistencyScore' => 0.5,
185
185
  'clickVarianceScore' => 0.5, // Moderate weight for click variance
186
186
  'subnetScore' => 0.4, // Utile contre le spam de commentaires coordonné
187
+ 'ipReputationScore' => 0.3, // NOUVEAU: Poids pour la réputation IP
187
188
  'botnetClusterScore' => 0.5, // NOUVEAU: Poids pour le clustering botnet
188
189
  'tcpAnomalyScore' => 0.5, // NEW: Anomalie de pile TCP/IP
189
190
  'quicAnomalyScore' => 0.5, // NEW: Anomalie QUIC
@@ -11,6 +11,7 @@
11
11
  use Anonympins\Fingerprint\Utils\Logger;
12
12
  use Anonympins\Fingerprint\Utils\MetricsManager;
13
13
  use Anonympins\Fingerprint\Utils\RequestUtils;
14
+ use Anonympins\Fingerprint\Utils\Env;
14
15
 
15
16
  // Correction de l'import
16
17
 
@@ -164,14 +165,56 @@
164
165
 
165
166
  public function __construct(array $securityConfig)
166
167
  {
167
- $this->isProduction = ($_ENV['APP_ENV'] ?? getenv('APP_ENV')) === 'production';
168
+ $this->isProduction = Env::get('APP_ENV') === 'production';
168
169
 
169
170
  // Dynamically bind Ed25519 keys if passed via config
170
171
  if (isset($securityConfig['ed25519_private_key'])) {
171
- $_ENV['ED25519_PRIVATE_KEY'] = $securityConfig['ed25519_private_key'];
172
+ Env::set('ED25519_PRIVATE_KEY', $securityConfig['ed25519_private_key']);
172
173
  }
173
174
  if (isset($securityConfig['ed25519_public_key'])) {
174
- $_ENV['ED25519_PUBLIC_KEY'] = $securityConfig['ed25519_public_key'];
175
+ Env::set('ED25519_PUBLIC_KEY', $securityConfig['ed25519_public_key']);
176
+ }
177
+
178
+ // Génération et persistance automatique de la paire de clés Ed25519
179
+ $useAsymmetric = ($securityConfig['useAsymmetricTickets'] ?? false) === true || ($securityConfig['ed25519'] ?? '') === 'auto';
180
+ $envPrivate = Env::get('ED25519_PRIVATE_KEY');
181
+ if ($useAsymmetric && ($envPrivate === null || $envPrivate === '')) {
182
+ $configDir = dirname(__DIR__, 1) . '/config';
183
+ $persistentKeyPath = $configDir . '/ed25519_key.json';
184
+
185
+ if (file_exists($persistentKeyPath)) {
186
+ try {
187
+ $keys = json_decode(file_get_contents($persistentKeyPath), true);
188
+ if (isset($keys['privateKey'], $keys['publicKey'])) {
189
+ Env::set('ED25519_PRIVATE_KEY', $keys['privateKey']);
190
+ Env::set('ED25519_PUBLIC_KEY', $keys['publicKey']);
191
+ }
192
+ } catch (\Throwable $e) {
193
+ error_log('[Fingerprint] Failed to load persistent Ed25519 keys: ' . $e->getMessage());
194
+ }
195
+ } else {
196
+ try {
197
+ if (defined('OPENSSL_KEYTYPE_ED25519')) {
198
+ $pkey = openssl_pkey_new(["private_key_type" => OPENSSL_KEYTYPE_ED25519]);
199
+ if ($pkey && openssl_pkey_export($pkey, $privateKeyPem)) {
200
+ $details = openssl_pkey_get_details($pkey); // @phpstan-ignore-line
201
+ $publicKeyPem = $details['key']; // @phpstan-ignore-line
202
+ Env::set('ED25519_PRIVATE_KEY', $privateKeyPem);
203
+ Env::set('ED25519_PUBLIC_KEY', $publicKeyPem);
204
+ // Also update $_ENV and $_SERVER for consistency
205
+ if (!is_dir($configDir)) {
206
+ mkdir($configDir, 0777, true);
207
+ }
208
+ file_put_contents($persistentKeyPath, json_encode([
209
+ 'privateKey' => $privateKeyPem,
210
+ 'publicKey' => $publicKeyPem
211
+ ], JSON_PRETTY_PRINT));
212
+ }
213
+ }
214
+ } catch (\Throwable $e) {
215
+ error_log('[Fingerprint] Native Ed25519 key generation failed: ' . $e->getMessage());
216
+ }
217
+ }
175
218
  }
176
219
 
177
220
  // Auto-load optimized config if autotuning savePath is specified
@@ -222,7 +265,8 @@
222
265
  'honeypot', 'threatIntel', 'whitelist', 'isStaticResource', 'isApiRequest', 'logger', 'probationaryTtl',
223
266
  'autotuning', 'enableUsefulWork', 'usefulWorkConfigPath', 'challengeNewDevices', 'graphql_operation_allowlist', 'dryRun',
224
267
  'similarityThreshold', 'summary', 'description',
225
- 'ed25519_private_key', 'ed25519_public_key'
268
+ 'ed25519_private_key', 'ed25519_public_key',
269
+ 'wasm', 'enableProofOfSpace', 'pospace', 'federatedPeers', 'federationSecret'
226
270
  ];
227
271
 
228
272
  if (empty($config['weights'])) {
@@ -483,6 +527,9 @@
483
527
  */
484
528
  private function resolveRequestIdentity(RequestContext $context, array &$suspicionVector): array
485
529
  {
530
+ if ($context->resolvedIdentity !== null) {
531
+ return $context->resolvedIdentity;
532
+ }
486
533
  $this->log('Resolving request identity', ['clientIp' => $context->clientIp, 'cookies' => $context->cookies]);
487
534
  $store = StoreManager::getStore();
488
535
  $existingDeviceId = $context->cookies['device_id'] ?? null; // @phpstan-ignore-line
@@ -564,11 +611,12 @@
564
611
  $store->set("tls-session:{$tlsSessionId}", $deviceId, 3600); // 1h cache duration
565
612
  }
566
613
 
567
- return [
614
+ $context->resolvedIdentity = [
568
615
  'deviceId' => $deviceId,
569
616
  'deviceData' => $deviceData,
570
617
  'newCookie' => $newCookie,
571
618
  ];
619
+ return $context->resolvedIdentity;
572
620
  }
573
621
 
574
622
  /**
@@ -891,22 +939,11 @@
891
939
  $context->graphqlOperation = $gqlInfo;
892
940
  }
893
941
  }
894
-
895
-
896
- $this->log('Processing request', ['clientIp' => $context->clientIp, 'path' => $context->path]);
897
-
898
- // Parse GraphQL query if applicable
899
- if ($context->path === '/graphql' && !empty($context->body)) {
900
- $gqlInfo = RequestUtils::parseGraphQLQuery(is_array($context->body) ? $context->body : []);
901
- if ($gqlInfo) {
902
- $context->graphqlOperation = $gqlInfo;
903
- }
904
- }
905
942
 
906
943
  // 1. Vérifier les listes blanches
907
944
  if ($this->checkAllowlists($context)) {
945
+ MetricsManager::incrementCounter('requests_total', ['status' => 'whitelisted']);
908
946
  return ['action' => 'next', 'score' => 0.0, 'vector' => ['whitelisted' => 100.0]];
909
- MetricsManager::incrementCounter('requests_total', ['status' => 'whitelisted']);
910
947
  }
911
948
 
912
949
  // Initialiser le vecteur de suspicion
@@ -1234,7 +1271,7 @@
1234
1271
 
1235
1272
  if ($shouldUseUsefulWork) {
1236
1273
  $this->log('Issuing a useful work challenge', ['finalScore' => $finalScore]);
1237
- $defaultPath = dirname(__DIR__, 2) . '/config/problems.config.json';
1274
+ $defaultPath = dirname(__DIR__, 1) . '/config/problems.config.json';
1238
1275
  $configPath = $this->securityConfig['usefulWorkConfigPath'] ?? (file_exists($defaultPath) ? $defaultPath : null);
1239
1276
  $problemManager = ProblemManager::getInstance($configPath, $store);
1240
1277
  $work = $problemManager->dispatchWork($finalScore);
@@ -1437,7 +1474,7 @@
1437
1474
  $signature = '';
1438
1475
  $isAsymmetric = false;
1439
1476
 
1440
- $privateKey = $_ENV['ED25519_PRIVATE_KEY'] ?? getenv('ED25519_PRIVATE_KEY');
1477
+ $privateKey = Env::get('ED25519_PRIVATE_KEY');
1441
1478
  if ($privateKey) {
1442
1479
  try {
1443
1480
  $cleanKey = str_replace('\n', "\n", $privateKey);