@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.
- package/CHANGELOG.md +22 -0
- package/package.json +1 -1
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +7014 -6802
- package/src/js/gpu_pow.solver.js +34 -3
- package/src/js/library.js +285 -124
- package/src/js/mongodb-store.js +13 -0
- package/src/js/redis-store.js +13 -0
- package/src/js/sql-store.js +80 -77
- package/src/js/tests/fingerprint.engine.test.js +2 -3
- package/src/js/tests/fingerprint.test.js +2 -2
- package/src/js/tests/gpu_pow.test.js +9 -7
- package/src/php/AutoTuner.php +41 -0
- package/src/php/Challenge/ChallengeUtils.php +40 -6
- package/src/php/Config/SecurityProfiles.php +5 -0
- package/src/php/FingerprintEngine.php +113 -25
- package/src/php/Optimization/OptimizationOperators.php +127 -35
- package/src/php/RequestContext.php +4 -0
- package/src/php/Store/IStore.php +6 -0
- package/src/php/Store/MongoDbStore.php +12 -0
- package/src/php/Store/RedisStore.php +12 -0
- package/src/php/Tests/ChallengeUtilsTest.php +6 -2
- package/src/php/Tests/FingerprintEngineTest.php +46 -0
- package/src/php/Tests/IpReputationTest.php +1 -0
- package/src/php/Utils/RequestUtils.php +42 -9
package/src/js/gpu_pow.solver.js
CHANGED
|
@@ -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 {
|
|
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,
|
|
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
|
|
19
|
+
* Génère un nombre flottant aléatoire entre 0 (inclus) et 1 (exclus).
|
|
20
20
|
* @returns {number}
|
|
21
21
|
*/
|
|
22
|
-
const
|
|
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 ||
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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 }, () =>
|
|
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
|
-
() =>
|
|
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 }, () =>
|
|
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] += (
|
|
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 = (
|
|
1113
|
-
const moveY = (
|
|
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 +
|
|
1130
|
-
y: 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 +
|
|
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 + (
|
|
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 +
|
|
1505
|
-
const maxClickVariance = 1 +
|
|
1506
|
-
const minMouseEntropy =
|
|
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 = (
|
|
1555
|
-
|
|
1556
|
-
|
|
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
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
let
|
|
1565
|
-
|
|
1566
|
-
|
|
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
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
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
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
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
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
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
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
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 +
|
|
1645
|
-
medium: 40 +
|
|
1646
|
-
high: 70 +
|
|
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 +
|
|
1670
|
-
velocityWeight: 10 +
|
|
1671
|
-
burstThreshold: 300 +
|
|
1672
|
-
burstWeight: 20 +
|
|
1673
|
-
scrapeThreshold: 500 +
|
|
1674
|
-
scrapeWeight: 15 +
|
|
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 +
|
|
1677
|
-
regularityThreshold: 50 +
|
|
1678
|
-
regularityWeight: 20 +
|
|
1679
|
-
decayFactor: 0.85 +
|
|
1680
|
-
inactivityReset: 15000 +
|
|
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
|
-
|
|
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
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1873
|
+
{ name: 'patterns', weight: 0.50 },
|
|
1874
|
+
{ name: 'thresholds', weight: 0.25 },
|
|
1875
|
+
{ name: 'weights', weight: 0.25 }
|
|
1712
1876
|
];
|
|
1713
|
-
const rand =
|
|
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
|
-
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
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)
|
package/src/js/mongodb-store.js
CHANGED
|
@@ -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
|