@anonympins/fingerprint 0.6.3 → 0.6.5

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.
@@ -53,26 +53,57 @@ export class GpuPowSolver {
53
53
  }
54
54
  }
55
55
 
56
+ static deriveSampleIndices(clientIp, secret) {
57
+ const timeWindow = Math.floor(Date.now() / (1000 * 60 * 5));
58
+ const message = `${clientIp}:${timeWindow}`;
59
+ let hmacBytes;
60
+ if (typeof window === 'undefined') {
61
+ try {
62
+ const crypto = require('node:crypto');
63
+ const hmac = crypto.createHmac('sha256', secret || 'gpu-pow-salt').update(message).digest();
64
+ hmacBytes = new Uint8Array(hmac);
65
+ } catch (e) {
66
+ hmacBytes = new TextEncoder().encode(message);
67
+ }
68
+ } else {
69
+ hmacBytes = new TextEncoder().encode(message);
70
+ }
71
+
72
+ const indices = [];
73
+ for (let i = 0; i < 4; i++) {
74
+ const offset = (i * 2) % hmacBytes.length;
75
+ const value = (hmacBytes[offset] << 8) | hmacBytes[(offset + 1) % hmacBytes.length];
76
+ let idx = value % 64;
77
+ while (indices.includes(idx)) {
78
+ idx = (idx + 1) % 64;
79
+ }
80
+ indices.push(idx);
81
+ }
82
+ return indices;
83
+ }
84
+
56
85
  /**
57
86
  * Verifies a GPU PoW solution.
58
87
  * To prevent server-side DoS, it verifies a sample of the 64 channels.
59
88
  * @param {string} seed - The challenge seed.
60
89
  * @param {number} iterations - Number of iterations.
61
90
  * @param {string} solution - The comma-separated solution string.
62
- * @param {Array<number>} [sampleIndices=[0, 12, 35, 57]] - Indices to verify.
91
+ * @param {string} [clientIp='127.0.0.1'] - Client IP address.
92
+ * @param {string} [secret='gpu-pow-salt'] - Session secret key.
63
93
  * @returns {boolean} True if the solution is valid.
64
94
  */
65
- static verify(seed, iterations, solution, sampleIndices = [0, 12, 35, 57]) {
95
+ static verify(seed, iterations, solution, clientIp = '127.0.0.1', secret = 'gpu-pow-salt') {
66
96
  if (!solution || typeof solution !== 'string') return false;
67
97
  const values = solution.split(',');
68
98
  if (values.length !== 64) return false;
69
99
 
100
+ const sampleIndices = this.deriveSampleIndices(clientIp, secret);
70
101
  const numericSeed = this._hashSeedToFloat(seed);
71
102
  const r = 3.9999;
72
103
 
73
104
  for (const idx of sampleIndices) {
74
105
  if (idx < 0 || idx >= 64) return false;
75
- let x = Math.fround(numericSeed + idx * 0.015);
106
+ let x = Math.fround((numericSeed + idx * 0.015) % 1);
76
107
  const rFloat = Math.fround(r);
77
108
  for (let i = 0; i < iterations; i++) {
78
109
  x = Math.fround(rFloat * x * Math.fround(1.0 - x));
package/src/js/library.js CHANGED
@@ -16,10 +16,10 @@ import os from "node:os";
16
16
  import crypto from "node:crypto";
17
17
 
18
18
  /**
19
- * Génère un nombre flottant aléatoire cryptographiquement sûr entre 0 (inclus) et 1 (exclus).
19
+ * Génère un nombre flottant aléatoire entre 0 (inclus) et 1 (exclus).
20
20
  * @returns {number}
21
21
  */
22
- const secureRandom = () => crypto.randomBytes(4).readUInt32LE(0) / 0x100000000;
22
+ const random = () => Math.random();
23
23
 
24
24
  const Optimization = {
25
25
  // eslint-disable-line no-unused-vars
@@ -62,7 +62,7 @@ const Optimization = {
62
62
  );
63
63
 
64
64
  // Décide si on se déplace vers la nouvelle solution.
65
- if (newEnergy < currentEnergy || secureRandom() < acceptanceProbability) {
65
+ if (newEnergy < currentEnergy || random() < acceptanceProbability) {
66
66
  currentSolution = newSolution;
67
67
  currentEnergy = newEnergy;
68
68
  }
@@ -142,7 +142,7 @@ const Optimization = {
142
142
 
143
143
  let offspringChromosome;
144
144
  // 4. Croisement
145
- if (secureRandom() < crossoverRate) {
145
+ if (random() < crossoverRate) {
146
146
  offspringChromosome = crossover(
147
147
  parent1.chromosome,
148
148
  parent2.chromosome,
@@ -152,7 +152,7 @@ const Optimization = {
152
152
  }
153
153
 
154
154
  // 5. Mutation
155
- if (secureRandom() < mutationRate) {
155
+ if (random() < mutationRate) {
156
156
  offspringChromosome = mutate(offspringChromosome);
157
157
  }
158
158
 
@@ -524,7 +524,7 @@ Optimization.geneticAlgorithmMultiObjective = function (
524
524
  const parent1 = population[crypto.randomInt(0, population.length)];
525
525
  const parent2 = population[crypto.randomInt(0, population.length)];
526
526
  let childIndividual = crossover(parent1.individual, parent2.individual);
527
- if (secureRandom() < mutationRate) {
527
+ if (random() < mutationRate) {
528
528
  childIndividual = mutate(childIndividual, currentConfig); // mutate doit maintenant utiliser currentConfig
529
529
  }
530
530
  const child = { individual: childIndividual };
@@ -668,7 +668,7 @@ Optimization.cmaes = function(fitnessFunction, initialSolution, initialStepSize,
668
668
  }
669
669
 
670
670
  for (let i = 0; i < populationSize; i++) {
671
- const z = Array.from({ length: n }, () => secureRandom() * 2 - 1); // Vecteur normal standard
671
+ const z = Array.from({ length: n }, () => random() * 2 - 1); // Vecteur normal standard
672
672
  const y = Array(n).fill(0); // z transformé par L
673
673
  for (let r = 0; r < n; r++) {
674
674
  for (let c = 0; c < n; c++) {
@@ -926,7 +926,7 @@ Optimization.Operators.solveTSP = (cities, options = {}) => {
926
926
 
927
927
  // Solution initiale : un chemin aléatoire.
928
928
  const initialPath = Array.from({ length: cities.length }, (_, i) => i).sort(
929
- () => secureRandom() - 0.5,
929
+ () => random() - 0.5,
930
930
  );
931
931
 
932
932
  // Paramètres par défaut pour le TSP, pouvant être surchargés par `options`.
@@ -968,14 +968,14 @@ Optimization.Operators.solvePortfolio = (
968
968
 
969
969
  // Fonctions spécifiques au problème pour l'AG, maintenant encapsulées.
970
970
  const createIndividual = () =>
971
- Array.from({ length: assets.length }, () => secureRandom());
971
+ Array.from({ length: assets.length }, () => random());
972
972
 
973
973
  const crossover = (p1, p2) => p1.map((w1, i) => (w1 + p2[i]) / 2); // Moyenne des poids
974
974
 
975
975
  const mutate = (p) => {
976
976
  const newP = [...p];
977
977
  const i = crypto.randomInt(0, newP.length);
978
- newP[i] += (secureRandom() - 0.5) * 0.2; // Mutation douce
978
+ newP[i] += (random() - 0.5) * 0.2; // Mutation douce
979
979
  newP[i] = Math.max(0, newP[i]); // Les poids ne peuvent être négatifs
980
980
  return newP;
981
981
  };
@@ -1109,8 +1109,8 @@ Optimization.Operators.solveFacilityLocation = (
1109
1109
  const facilityNeighbor = (facilities) => {
1110
1110
  const newFacilities = facilities.map((f) => ({ ...f }));
1111
1111
  const i = crypto.randomInt(0, numFacilities);
1112
- const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
1113
- const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
1112
+ const moveX = (random() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
1113
+ const moveY = (random() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
1114
1114
 
1115
1115
  newFacilities[i].x = Math.max(
1116
1116
  bounds.minX,
@@ -1126,8 +1126,8 @@ Optimization.Operators.solveFacilityLocation = (
1126
1126
 
1127
1127
  // Solution initiale : place les infrastructures au hasard sur la carte.
1128
1128
  const initialFacilities = Array.from({ length: numFacilities }, () => ({
1129
- x: bounds.minX + secureRandom() * (bounds.maxX - bounds.minX),
1130
- y: bounds.minY + secureRandom() * (bounds.maxY - bounds.minY),
1129
+ x: bounds.minX + random() * (bounds.maxX - bounds.minX),
1130
+ y: bounds.minY + random() * (bounds.maxY - bounds.minY),
1131
1131
  }));
1132
1132
 
1133
1133
  const saOptions = {
@@ -1244,7 +1244,7 @@ Optimization.Operators.solveOptimalCPC = (context, options = {}) => {
1244
1244
  // Un "individu" est simplement une valeur de CPC.
1245
1245
  const createIndividual = () => {
1246
1246
  // Le CPC peut varier, par exemple, entre 0.5 et 5 PRIM'S.
1247
- return 0.5 + secureRandom() * 4.5;
1247
+ return 0.5 + random() * 4.5;
1248
1248
  };
1249
1249
 
1250
1250
  // Croisement : moyenne des CPC des parents.
@@ -1254,7 +1254,7 @@ Optimization.Operators.solveOptimalCPC = (context, options = {}) => {
1254
1254
 
1255
1255
  // Mutation : légère variation aléatoire du CPC.
1256
1256
  const mutate = (cpc) => {
1257
- const newCpc = cpc + (secureRandom() - 0.5) * 0.5;
1257
+ const newCpc = cpc + (random() - 0.5) * 0.5;
1258
1258
  return Math.max(0.1, newCpc); // Assurer un CPC minimum.
1259
1259
  };
1260
1260
 
@@ -1501,9 +1501,9 @@ Optimization.Operators.solveFraudDetection = (context, options = {}) => {
1501
1501
 
1502
1502
  // Un "individu" est un tableau de 4 seuils : [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents]
1503
1503
  const createIndividual = () => {
1504
- const minTimeToClick = 100 + secureRandom() * 4900; // entre 100ms et 5s
1505
- const maxClickVariance = 1 + secureRandom() * 9999; // entre 1 et 10000
1506
- const minMouseEntropy = secureRandom() * 0.5; // entre 0 et 0.5
1504
+ const minTimeToClick = 100 + random() * 4900; // entre 100ms et 5s
1505
+ const maxClickVariance = 1 + random() * 9999; // entre 1 et 10000
1506
+ const minMouseEntropy = random() * 0.5; // entre 0 et 0.5
1507
1507
  const minScrollEvents = crypto.randomInt(0, 10); // entre 0 et 10
1508
1508
  return [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents];
1509
1509
  };
@@ -1551,23 +1551,106 @@ Optimization.Operators.solveFraudDetection = (context, options = {}) => {
1551
1551
  * @param {Array<object>} config.trafficData - Données de trafic collectées.
1552
1552
  * @returns {function(object): number[]} Une fonction de fitness qui prend une configuration complète et retourne les scores [taux de faux positifs, taux de faux négatifs].
1553
1553
  */
1554
- Optimization.Operators.createFullSecurityConfigEvaluator = ({ trafficData }) => {
1555
- // Un "individu" est un objet de configuration complet
1556
- // { thresholds: { low, medium, high }, weights: { historyScore, ... }, patterns: { velocityThreshold, ... } }
1554
+ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1555
+ const trafficData = context.trafficData || [];
1556
+ const currentConfig = context.currentConfig || null;
1557
+
1558
+
1559
+ const THREAT_PROFILES = {
1560
+ account_takeover: {
1561
+ importance: 10.0,
1562
+ ux_vs_security_ratio: 0.1, // 10% FPR / 90% FNR (Priorité sécurité maximale)
1563
+ target_threshold: 'block',
1564
+ indicators: ['requestPatternScore', 'behaviorScore', 'timeInconsistencyScore', 'clickVarianceScore']
1565
+ },
1566
+ active_exploitation: {
1567
+ importance: 8.0,
1568
+ ux_vs_security_ratio: 0.2, // 20% FPR / 80% FNR (Sécurité prioritaire)
1569
+ target_threshold: 'block',
1570
+ indicators: ['honeypotScore', 'headerAnomalyScore']
1571
+ },
1572
+ mass_scraping: {
1573
+ importance: 3.0,
1574
+ ux_vs_security_ratio: 0.8, // 80% FPR / 20% FNR (UX prioritaire)
1575
+ target_threshold: 'low',
1576
+ indicators: ['requestPatternScore', 'renderingAnomalyScore', 'clientHintsInconsistencyScore']
1577
+ },
1578
+ distributed_botnets: {
1579
+ importance: 6.0,
1580
+ ux_vs_security_ratio: 0.5, // Équilibré
1581
+ target_threshold: 'high',
1582
+ indicators: ['subnetScore', 'botnetClusterScore', 'ipReputationScore', 'tlsSpoofingScore']
1583
+ },
1584
+ basic_automation: {
1585
+ importance: 5.0,
1586
+ ux_vs_security_ratio: 0.4, // 40% FPR / 60% FNR
1587
+ target_threshold: 'medium',
1588
+ indicators: ['botScore', 'tlsSpoofingScore', 'tcpAnomalyScore']
1589
+ }
1590
+ };
1591
+
1592
+ // Ancres immuables (Baseline Anchors) pour forcer le calibrage d'échelle de suspicion
1593
+ const STATIC_ANCHORS = [
1594
+ {
1595
+ type: 'request_passed',
1596
+ weight: 15.0,
1597
+ vector: {
1598
+ historyScore: 0, rotationScore: 0, headerAnomalyScore: 0, requestPatternScore: 0,
1599
+ inconsistencyScore: 0, behaviorScore: 0, honeypotScore: 0, botScore: 0,
1600
+ crossLayerInconsistencyScore: 0, timeInconsistencyScore: 0, tlsSpoofingScore: 0,
1601
+ clickVarianceScore: 0, clientHintsInconsistencyScore: 0, subnetScore: 0,
1602
+ ipReputationScore: 0, botnetClusterScore: 0, tcpAnomalyScore: 0,
1603
+ quicAnomalyScore: 0, renderingAnomalyScore: 0, threatIntelScore: 0
1604
+ }
1605
+ },
1606
+ {
1607
+ type: 'request_blocked',
1608
+ weight: 15.0,
1609
+ vector: {
1610
+ historyScore: 100, rotationScore: 100, headerAnomalyScore: 80, requestPatternScore: 100,
1611
+ inconsistencyScore: 100, behaviorScore: 0, honeypotScore: 100, botScore: 100,
1612
+ crossLayerInconsistencyScore: 80, timeInconsistencyScore: 100, tlsSpoofingScore: 100,
1613
+ clickVarianceScore: 100, clientHintsInconsistencyScore: 90, subnetScore: 100,
1614
+ ipReputationScore: 100, botnetClusterScore: 100, tcpAnomalyScore: 100,
1615
+ quicAnomalyScore: 100, renderingAnomalyScore: 100, threatIntelScore: 100
1616
+ }
1617
+ },
1618
+ {
1619
+ type: 'request_passed', // Profil humain sain avec du bruit (doit rester sous le seuil d'alerte, ex: < 20)
1620
+ weight: 10.0,
1621
+ vector: {
1622
+ historyScore: 15, rotationScore: 10, headerAnomalyScore: 20, requestPatternScore: 15,
1623
+ inconsistencyScore: 10, behaviorScore: 15, honeypotScore: 0, botScore: 0,
1624
+ crossLayerInconsistencyScore: 10, timeInconsistencyScore: 10, tlsSpoofingScore: 10,
1625
+ clickVarianceScore: 0, clientHintsInconsistencyScore: 10, subnetScore: 5,
1626
+ ipReputationScore: 5, botnetClusterScore: 5, tcpAnomalyScore: 10,
1627
+ quicAnomalyScore: 10, renderingAnomalyScore: 5, threatIntelScore: 0
1628
+ }
1629
+ },
1630
+ {
1631
+ type: 'challenge_issued', // Profil robot furtif moyen (doit être challengé, ex: > 35)
1632
+ weight: 10.0,
1633
+ vector: {
1634
+ historyScore: 20, rotationScore: 20, headerAnomalyScore: 20, requestPatternScore: 40,
1635
+ inconsistencyScore: 30, behaviorScore: 30, honeypotScore: 0, botScore: 0,
1636
+ crossLayerInconsistencyScore: 20, timeInconsistencyScore: 20, tlsSpoofingScore: 30,
1637
+ clickVarianceScore: 20, clientHintsInconsistencyScore: 20, subnetScore: 15,
1638
+ ipReputationScore: 15, botnetClusterScore: 20, tcpAnomalyScore: 30,
1639
+ quicAnomalyScore: 20, renderingAnomalyScore: 20, threatIntelScore: 0
1640
+ }
1641
+ }
1642
+ ];
1557
1643
 
1558
1644
  return function fullConfigFitness(config) {
1559
- let falsePositives = 0; // Humains légitimes challengés ou bloqués
1560
- let falseNegatives = 0; // Bots qui sont passés
1561
- let totalHumans = 0;
1562
- let totalBots = 0;
1563
- let totalChallenges = 0;
1564
- let totalChallengeCost = 0; // NOUVEAU: Coût cumulé des challenges
1565
-
1566
- // Simule le calcul du score pour chaque point de données avec la configuration testée
1645
+ const threatStats = {};
1646
+ for (const threatName in THREAT_PROFILES) {
1647
+ threatStats[threatName] = { fp: 0, fn: 0, totalHumans: 0, totalBots: 0 };
1648
+ }
1649
+
1650
+ let maxHumanScore = 0;
1651
+ let minBotScore = 100;
1652
+
1567
1653
  const calculateScore = (log) => {
1568
- // Cette fonction interne devrait répliquer la logique de `getSuspicionVector`
1569
- // en utilisant les poids et les paramètres de `config`.
1570
- // Pour cet exemple, nous utilisons une version simplifiée.
1571
1654
  let score = 0;
1572
1655
  for (const key in config.weights) {
1573
1656
  score += (log.vector?.[key] || 0) * config.weights[key];
@@ -1575,57 +1658,131 @@ Optimization.Operators.createFullSecurityConfigEvaluator = ({ trafficData }) =>
1575
1658
  return score;
1576
1659
  };
1577
1660
 
1578
- // Pondération de la confiance : un challenge résolu est un signal humain plus fort.
1579
1661
  const confidenceWeights = {
1580
- request_passed: 0.7,
1581
- challenge_issued: 1.0,
1582
- request_blocked: 1.0,
1583
- challenge_solved: 1.5, // Signal "humain" de très haute confiance
1584
- trap_triggered: 2.0, // Signal "bot" de confiance absolue
1662
+ request_passed: 0.7,
1663
+ challenge_issued: 1.0,
1664
+ request_blocked: 1.0,
1665
+ challenge_solved: 1.5,
1666
+ trap_triggered: 2.0,
1585
1667
  };
1586
1668
 
1669
+ // 1. Évaluation sur les données de trafic réelles
1587
1670
  for (const log of trafficData) {
1588
- const confidence = confidenceWeights[log.type] || 1.0;
1589
-
1590
- // On se base sur le comportement observé pour déterminer la nature "réelle" de la requête
1591
- const isLikelyBot = log.type === 'challenge_issued' || log.type === 'request_blocked';
1671
+ const weight = log.weight || 1.0;
1672
+ const confidence = (confidenceWeights[log.type] || 1.0) * weight;
1673
+ const isLikelyBot = log.type === 'challenge_issued' || log.type === 'request_blocked' || log.type === 'trap_triggered';
1592
1674
  const isLikelyHuman = log.type === 'request_passed' || log.type === 'challenge_solved';
1593
1675
 
1676
+ const score = calculateScore(log);
1677
+
1594
1678
  if (isLikelyBot) {
1595
- totalBots++;
1596
- const score = calculateScore(log);
1597
- // Faux négatif : un bot qui aurait dû être challengé mais ne l'a pas été
1598
- if (score < config.thresholds.low) {
1599
- falseNegatives += confidence;
1600
- } else {
1601
- totalChallenges += confidence; // Un bot correctement challengé
1602
- // Calculer le coût de ce challenge (proportionnel à la difficulté)
1603
- const suspicionFactor = (score - config.thresholds.low) / (config.thresholds.high - config.thresholds.low);
1604
- totalChallengeCost += Math.min(1, Math.max(0, suspicionFactor)) * confidence;
1605
- }
1679
+ minBotScore = Math.min(minBotScore, score);
1606
1680
  } else if (isLikelyHuman) {
1607
- totalHumans++;
1608
- const score = calculateScore(log);
1609
- // Faux positif : un humain qui a été challengé inutilement
1610
- if (score >= config.thresholds.low) {
1611
- falsePositives += confidence;
1612
- totalChallenges += confidence; // Un humain incorrectement challengé
1613
- const suspicionFactor = (score - config.thresholds.low) / (config.thresholds.high - config.thresholds.low);
1614
- totalChallengeCost += Math.min(1, Math.max(0, suspicionFactor)) * confidence;
1681
+ maxHumanScore = Math.max(maxHumanScore, score);
1682
+ }
1683
+
1684
+ for (const threatName in THREAT_PROFILES) {
1685
+ const profile = THREAT_PROFILES[threatName];
1686
+ let threatActivity = 0.0;
1687
+ for (const indicator of profile.indicators) {
1688
+ threatActivity += (log.vector?.[indicator] || 0.0);
1689
+ }
1690
+ if (threatActivity <= 0.0) {
1691
+ continue;
1692
+ }
1693
+ const effectiveConfidence = confidence * (threatActivity / 100.0);
1694
+ const targetThreshold = config.thresholds[profile.target_threshold] || 20;
1695
+
1696
+ if (isLikelyBot) {
1697
+ threatStats[threatName].totalBots += effectiveConfidence;
1698
+ if (score < targetThreshold) {
1699
+ threatStats[threatName].fn += effectiveConfidence;
1700
+ }
1701
+ } else if (isLikelyHuman) {
1702
+ threatStats[threatName].totalHumans += effectiveConfidence;
1703
+ if (score >= targetThreshold) {
1704
+ threatStats[threatName].fp += effectiveConfidence;
1705
+ }
1615
1706
  }
1616
1707
  }
1708
+
1617
1709
  }
1710
+ // 2. Évaluation sur les ancres immuables pour fixer l'échelle
1711
+ for (const anchor of STATIC_ANCHORS) {
1712
+ const score = calculateScore(anchor);
1713
+ const weight = anchor.weight;
1714
+ const isBot = anchor.type === 'request_blocked' || anchor.type === 'challenge_issued';
1715
+
1716
+ if (isBot){
1717
+ minBotScore = Math.min(minBotScore, score);
1718
+ } else {
1719
+ maxHumanScore = Math.max(maxHumanScore, score);
1720
+ }
1618
1721
 
1619
- const falsePositiveRate = totalHumans > 0 ? falsePositives / totalHumans : 0;
1620
- const falseNegativeRate = totalBots > 0 ? falseNegatives / totalBots : 0;
1621
- // NOUVEAU: Taux de challenge global
1622
- const challengeRate = (totalHumans + totalBots) > 0 ? totalChallenges / (totalHumans + totalBots) : 0;
1623
- // NOUVEAU: Coût moyen par challenge émis
1624
- const averageChallengeCost = totalChallenges > 0 ? totalChallengeCost / totalChallenges : 0;
1722
+ for (const threatName in THREAT_PROFILES) {
1723
+ const profile = THREAT_PROFILES[threatName];
1724
+ let threatActivity = 0.0;
1725
+ for (const indicator of profile.indicators) {
1726
+ threatActivity += (anchor.vector?.[indicator] || 0.0);
1727
+ }
1728
+ if (threatActivity <= 0.0) {
1729
+ continue;
1730
+ }
1731
+ const effectiveWeight = weight * (threatActivity / 100.0);
1732
+ const targetThreshold = config.thresholds[profile.target_threshold] || 20;
1733
+
1734
+ if (isBot) {
1735
+ threatStats[threatName].totalBots += effectiveWeight;
1736
+ if (score < targetThreshold) {
1737
+ threatStats[threatName].fn += effectiveWeight * 10; // Pénalité punitive forte
1738
+ }
1739
+ } else {
1740
+ threatStats[threatName].totalHumans += effectiveWeight;
1741
+ if (score >= targetThreshold) {
1742
+ threatStats[threatName].fp += effectiveWeight * 10; // Pénalité punitive forte
1743
+ }
1744
+ }
1745
+ }
1746
+ }
1747
+ let weightedFpr = 0;
1748
+ let weightedFnr = 0;
1749
+ let totalImportance = 0;
1750
+
1751
+ for (const threatName in THREAT_PROFILES) {
1752
+ totalImportance += THREAT_PROFILES[threatName].importance;
1753
+ }
1625
1754
 
1626
- // L'algorithme doit minimiser ces deux objectifs
1627
- // On a maintenant 4 objectifs à minimiser !
1628
- return [falsePositiveRate, falseNegativeRate, challengeRate, averageChallengeCost];
1755
+ for (const threatName in THREAT_PROFILES) {
1756
+ const profile = THREAT_PROFILES[threatName];
1757
+ const stats = threatStats[threatName];
1758
+
1759
+ const fpr = stats.totalHumans > 0 ? stats.fp / stats.totalHumans : 0;
1760
+ const fnr = stats.totalBots > 0 ? stats.fn / stats.totalBots : 0;
1761
+
1762
+ const importanceWeight = profile.importance / totalImportance;
1763
+ const uxRatio = profile.ux_vs_security_ratio;
1764
+ const secRatio = 1.0 - uxRatio;
1765
+
1766
+ weightedFpr += fpr * importanceWeight * uxRatio;
1767
+ weightedFnr += fnr * importanceWeight * secRatio;
1768
+ }
1769
+
1770
+ // 3. Pénalité de dérive d'échelle L2 (Régularisation par rapport au point d'origine)
1771
+ let regularizationPenalty = 0;
1772
+ if (currentConfig && currentConfig.weights) {
1773
+ for (const key in config.weights) {
1774
+ const originalVal = currentConfig.weights[key] || 0;
1775
+ regularizationPenalty += Math.pow(config.weights[key] - originalVal, 2);
1776
+ }
1777
+ }
1778
+
1779
+ // 4. Maximisation de la marge de séparation (SVM-like Margin Loss)
1780
+ const marginOverlap = Math.max(0, maxHumanScore - minBotScore);
1781
+ const marginPenalty = marginOverlap / 100;
1782
+
1783
+ const obj1 = weightedFpr + (regularizationPenalty * 0.05);
1784
+ const obj2 = weightedFnr + marginPenalty;
1785
+ return [obj1, obj2];
1629
1786
  };
1630
1787
  };
1631
1788
 
@@ -1637,47 +1794,56 @@ Optimization.Operators.createFullSecurityConfigEvaluator = ({ trafficData }) =>
1637
1794
  */ // eslint-disable-line max-len
1638
1795
  Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1639
1796
  const fitnessFunction = Optimization.Operators.createFullSecurityConfigEvaluator(context);
1797
+ const currentConfig = context.currentConfig || {};
1798
+ const baseWeights = currentConfig.weights || {
1799
+ historyScore: 0.3,
1800
+ rotationScore: 0.5,
1801
+ headerAnomalyScore: 0.1,
1802
+ requestPatternScore: 0.6,
1803
+ inconsistencyScore: 0.8,
1804
+ honeypotScore: 1.0,
1805
+ behaviorScore: 0.7,
1806
+ crossLayerInconsistencyScore: 0.4,
1807
+ timeInconsistencyScore: 0.9,
1808
+ tlsSpoofingScore: 0.8,
1809
+ botScore: 1.0,
1810
+ subnetScore: 0.4,
1811
+ ipReputationScore: 0.5,
1812
+ botnetClusterScore: 0.6,
1813
+ tcpAnomalyScore: 0.8,
1814
+ quicAnomalyScore: 0.8,
1815
+ renderingAnomalyScore: 0.8,
1816
+ threatIntelScore: 1.0
1817
+ };
1640
1818
 
1641
1819
  // Un "individu" est un objet de configuration complet
1642
1820
  const createIndividual = () => ({
1643
1821
  thresholds: {
1644
- low: 15 + secureRandom() * 20, // 15-35
1645
- medium: 40 + secureRandom() * 25,
1646
- high: 70 + secureRandom() * 20,
1647
- },
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(),
1660
- subnetScore: secureRandom(),
1661
- ipReputationScore: secureRandom(),
1662
- botnetClusterScore: secureRandom(),
1663
- tcpAnomalyScore: secureRandom(),
1664
- quicAnomalyScore: secureRandom(),
1665
- clickVarianceScore: secureRandom(),
1666
- clientHintsInconsistencyScore: secureRandom()
1822
+ low: 15 + random() * 20, // 15-35
1823
+ medium: 40 + random() * 25,
1824
+ high: 70 + random() * 20,
1825
+ block: 90 + random() * 9,
1667
1826
  },
1827
+ weights: (() => {
1828
+ const w = {};
1829
+ for (const key in baseWeights) {
1830
+ w[key] = baseWeights[key] * (0.75 + random() * 0.5); // +/- 25%
1831
+ }
1832
+ return w;
1833
+ })(),
1668
1834
  patterns: {
1669
- velocityThreshold: 100 + secureRandom() * 400,
1670
- velocityWeight: 10 + secureRandom() * 40,
1671
- burstThreshold: 300 + secureRandom() * 700,
1672
- burstWeight: 20 + secureRandom() * 40,
1673
- scrapeThreshold: 500 + secureRandom() * 1000,
1674
- scrapeWeight: 15 + secureRandom() * 35,
1835
+ velocityThreshold: 100 + random() * 400,
1836
+ velocityWeight: 10 + random() * 40,
1837
+ burstThreshold: 300 + random() * 700,
1838
+ burstWeight: 20 + random() * 40,
1839
+ scrapeThreshold: 500 + random() * 1000,
1840
+ scrapeWeight: 15 + random() * 35,
1675
1841
  sequenceLength: 3 + crypto.randomInt(0, 3),
1676
- sequenceWeight: 20 + secureRandom() * 50,
1677
- regularityThreshold: 50 + secureRandom() * 200,
1678
- regularityWeight: 20 + secureRandom() * 40,
1679
- decayFactor: 0.85 + secureRandom() * 0.14,
1680
- inactivityReset: 15000 + secureRandom() * 45000,
1842
+ sequenceWeight: 20 + random() * 50,
1843
+ regularityThreshold: 50 + random() * 200,
1844
+ regularityWeight: 20 + random() * 40,
1845
+ decayFactor: 0.85 + random() * 0.14,
1846
+ inactivityReset: 15000 + random() * 45000,
1681
1847
  }
1682
1848
  });
1683
1849
 
@@ -1689,9 +1855,7 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1689
1855
  child.thresholds[key] = (c1.thresholds[key] + c2.thresholds[key]) / 2;
1690
1856
  }
1691
1857
  for (const key in child.weights) {
1692
- if (key !== 'honeypotScore') { // Ne pas croiser le poids du honeypot
1693
- child.weights[key] = (c1.weights[key] + c2.weights[key]) / 2;
1694
- }
1858
+ child.weights[key] = (c1.weights[key] + c2.weights[key]) / 2;
1695
1859
  }
1696
1860
  for (const key in child.patterns) {
1697
1861
  child.patterns[key] = (c1.patterns[key] + c2.patterns[key]) / 2;
@@ -1706,11 +1870,11 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1706
1870
  // On donne plus de poids à la mutation des 'patterns' et des 'weights',
1707
1871
  // car ils ont un impact plus direct sur la détection que les seuils.
1708
1872
  const sections = [
1709
- { name: 'patterns', weight: 0.5 }, // 50% de chance
1710
- { name: 'weights', weight: 0.35 }, // 35% de chance
1711
- { name: 'thresholds', weight: 0.15 } // 15% de chance
1873
+ { name: 'patterns', weight: 0.50 },
1874
+ { name: 'thresholds', weight: 0.25 },
1875
+ { name: 'weights', weight: 0.25 }
1712
1876
  ];
1713
- const rand = secureRandom();
1877
+ const rand = random();
1714
1878
  let cumulativeWeight = 0;
1715
1879
  let sectionToMutate = 'patterns'; // Fallback
1716
1880
  for (const section of sections) {
@@ -1724,21 +1888,18 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1724
1888
  const keys = Object.keys(newConfig[sectionToMutate]);
1725
1889
  const keyToMutate = keys[crypto.randomInt(0, keys.length)];
1726
1890
 
1727
- if (keyToMutate === 'honeypotScore') return newConfig; // Ne pas muter le poids du honeypot
1728
-
1729
- // Appliquer une mutation avec une amplitude variable
1730
- const mutationAmount = (secureRandom() - 0.5) * 0.4; // +/- 20%
1731
- newConfig[sectionToMutate][keyToMutate] *= (1 + mutationAmount);
1732
1891
 
1733
1892
  // S'assurer que les valeurs restent dans des limites raisonnables
1734
1893
  if (sectionToMutate === 'weights') {
1735
- newConfig[sectionToMutate][keyToMutate] = Math.max(0, Math.min(1.5, newConfig[sectionToMutate][keyToMutate]));
1736
- }
1737
- if (keyToMutate === 'decayFactor') {
1738
- newConfig.patterns.decayFactor = Math.max(0.8, Math.min(0.999, newConfig.patterns.decayFactor));
1739
- }
1740
- if (keyToMutate.includes('Threshold') || keyToMutate.includes('Reset')) {
1741
- newConfig.patterns[keyToMutate] = Math.max(50, newConfig.patterns[keyToMutate]);
1894
+ newConfig[sectionToMutate][keyToMutate] = Math.max(0.05, Math.min(1.5, newConfig[sectionToMutate][keyToMutate] + (random() - 0.5) * 0.1));
1895
+ } else if (sectionToMutate === 'thresholds') {
1896
+ newConfig[sectionToMutate][keyToMutate] = Math.round(newConfig[sectionToMutate][keyToMutate] + (random() - 0.5) * 5.0);
1897
+ } else {
1898
+ if (keyToMutate === 'decayFactor') {
1899
+ newConfig.patterns.decayFactor = Math.max(0.8, Math.min(0.999, newConfig.patterns.decayFactor + (random() - 0.5) * 0.05));
1900
+ } else if (keyToMutate.includes('Threshold') || keyToMutate.includes('Reset')) {
1901
+ newConfig.patterns[keyToMutate] = Math.max(50, newConfig.patterns[keyToMutate] + (random() - 0.5) * 50.0);
1902
+ }
1742
1903
  }
1743
1904
 
1744
1905
  // Contrainte de dérive maximale (±30% par rapport à la configuration actuelle)
@@ -154,6 +154,19 @@ export function createMongoDbStore(db, collectionName = 'fingerprint_store') {
154
154
  triggerFallback();
155
155
  }
156
156
  },
157
+ async clear() {
158
+ localStore.clear();
159
+ for (const timeout of localTimeouts.values()) {
160
+ clearTimeout(timeout);
161
+ }
162
+ localTimeouts.clear();
163
+ if (isDown) return;
164
+ try {
165
+ await collection.deleteMany({});
166
+ } catch (e) {
167
+ triggerFallback();
168
+ }
169
+ },
157
170
  async init() {
158
171
  try {
159
172
  // Automates index configuration