@anonympins/fingerprint 0.2.2 → 0.2.4

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/package.json CHANGED
@@ -1,79 +1,88 @@
1
- {
2
- "name": "@anonympins/fingerprint",
3
- "version": "0.2.2",
4
- "description": "An HTTP(S) client mitigation and anti-bot protection library for Node.js/Express, based on digital fingerprinting and dynamic Proof-of-Work (PoW) challenges.",
5
- "main": "fingerprint.js",
6
- "type": "module",
7
- "engines": {
8
- "node": ">=20.0.0"
9
- },
10
- "scripts": {
11
- "test": "vitest run --reporter=verbose"
12
- },
13
- "files": [
14
- "fingerprint.js",
15
- "fingerprint.client.js",
16
- "fingerprint.builder.js",
17
- "pow.solver.js",
18
- "pow.worker.js",
19
- "problem-manager.js",
20
- "optimization.worker.js",
21
- "library.js",
22
- "redis-store.js",
23
- "mongodb-store.js",
24
- "sql-store.js",
25
- "README.md",
26
- "LICENSE"
27
- ],
28
- "repository": {
29
- "type": "git",
30
- "url": "git+https://github.com/anonympins/fingerprint.git"
31
- },
32
- "keywords": [
33
- "fingerprint",
34
- "bot",
35
- "anti-bot",
36
- "security",
37
- "express",
38
- "middleware",
39
- "proof-of-work",
40
- "pow",
41
- "rate-limiting",
42
- "mitigation",
43
- "captcha",
44
- "dns"
45
- ],
46
- "author": "anonympins",
47
- "license": "MIT",
48
- "bugs": {
49
- "url": "https://github.com/anonympins/fingerprint/issues"
50
- },
51
- "homepage": "https://github.com/anonympins/fingerprint#readme",
52
- "devDependencies": {
53
- "body-parser": "^1.20.2",
54
- "cookie-parser": "^1.4.6",
55
- "express": "^4.18.2",
56
- "prom-client": "^15.1.2",
57
- "vitest": "^4.1.11"
58
- },
59
- "peerDependencies": {
60
- "ioredis": "^5.3.2",
61
- "knex": ">=3.0.0",
62
- "mongodb": "^6.3.0",
63
- "sqlite3": "^5.1.7"
64
- },
65
- "peerDependenciesMeta": {
66
- "ioredis": {
67
- "optional": true
68
- },
69
- "mongodb": {
70
- "optional": true
71
- },
72
- "knex": {
73
- "optional": true
74
- },
75
- "sqlite3": {
76
- "optional": true
77
- }
78
- }
79
- }
1
+ {
2
+ "name": "@anonympins/fingerprint",
3
+ "version": "0.2.4",
4
+ "description": "Advanced anti-bot library for Node.js using multi-layer fingerprinting (JA3, client-side, headers), behavioral analysis, and adaptive Proof-of-Work (PoW) challenges to mitigate scraping, scalping, and automated threats.",
5
+ "main": "fingerprint.js",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=20.0.0"
9
+ },
10
+ "scripts": {
11
+ "test": "vitest run --reporter=verbose"
12
+ },
13
+ "files": [
14
+ "fingerprint.js",
15
+ "fingerprint.client.js",
16
+ "fingerprint.builder.js",
17
+ "pow.solver.js",
18
+ "pow.worker.js",
19
+ "problem-manager.js",
20
+ "optimization.worker.js",
21
+ "library.js",
22
+ "redis-store.js",
23
+ "mongodb-store.js",
24
+ "sql-store.js",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/anonympins/fingerprint.git"
31
+ },
32
+ "keywords": [
33
+ "anti-bot",
34
+ "bot-detection",
35
+ "security",
36
+ "middleware",
37
+ "express",
38
+ "nodejs",
39
+ "fingerprint",
40
+ "device-fingerprinting",
41
+ "ja3",
42
+ "tls-fingerprinting",
43
+ "proof-of-work",
44
+ "pow",
45
+ "useful-proof-of-work",
46
+ "upow",
47
+ "waf",
48
+ "scraping",
49
+ "scalping",
50
+ "mitigation",
51
+ "rate-limiting",
52
+ "honeypot",
53
+ "behavioral-analysis"
54
+ ],
55
+ "author": "anonympins",
56
+ "license": "MIT",
57
+ "bugs": {
58
+ "url": "https://github.com/anonympins/fingerprint/issues"
59
+ },
60
+ "homepage": "https://github.com/anonympins/fingerprint#readme",
61
+ "devDependencies": {
62
+ "body-parser": "^1.20.2",
63
+ "cookie-parser": "^1.4.6",
64
+ "express": "^4.18.2",
65
+ "prom-client": "^15.1.2",
66
+ "vitest": "^4.1.11"
67
+ },
68
+ "peerDependencies": {
69
+ "ioredis": "^5.3.2",
70
+ "knex": ">=3.0.0",
71
+ "mongodb": "^6.3.0",
72
+ "sqlite3": "^5.1.7"
73
+ },
74
+ "peerDependenciesMeta": {
75
+ "ioredis": {
76
+ "optional": true
77
+ },
78
+ "mongodb": {
79
+ "optional": true
80
+ },
81
+ "knex": {
82
+ "optional": true
83
+ },
84
+ "sqlite3": {
85
+ "optional": true
86
+ }
87
+ }
88
+ }
package/pow.solver.js CHANGED
@@ -293,6 +293,18 @@ async function solveUsefulWorkTask(task) {
293
293
  // On simule l'appel avec les arguments de base.
294
294
  // Note: `baseSolverArgs` peut contenir des options.
295
295
  return clientSolver(...baseSolverArgs);
296
+
297
+ case 'multi_objective_genetic_algorithm': {
298
+ // C'est ici que la connexion se fait !
299
+ // On cherche le solveur demandé (ex: 'cpc.solve') dans notre registre client.
300
+ const solverFunction = ClientOptimizers[task.solverName];
301
+ if (!solverFunction) {
302
+ throw new Error(`Solver '${task.solverName}' not found on client.`);
303
+ }
304
+ // On appelle le solveur en lui passant le payload et les options.
305
+ // La fonction `solveOptimalCPC` attend le payload comme premier argument.
306
+ return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
307
+ }
296
308
 
297
309
  default:
298
310
  throw new Error(`Unknown useful work type: ${task.type}`);
@@ -1,6 +1,64 @@
1
1
  import { readFileSync, writeFileSync } from 'node:fs';
2
2
  import { Optimization } from './library.js';
3
3
 
4
+ /**
5
+ * @namespace FunctionRegistry
6
+ * @description Registre pour exposer de manière contrôlée les fonctions de la bibliothèque.
7
+ * Permet de les appeler dynamiquement depuis la configuration des problèmes.
8
+ * Utilise la notation par points pour accéder aux fonctions imbriquées (ex: 'tsp.calculateEnergy').
9
+ */
10
+ const FunctionRegistry = {};
11
+
12
+ // --- Fonctions de "Scoring" (évaluation d'une solution) ---
13
+ // Ces fonctions sont des adaptateurs pour utiliser les utilitaires de la bibliothèque
14
+ // avec la structure attendue par le ProblemManager.
15
+ FunctionRegistry['cpc.solve'] = Optimization.Operators.solveOptimalCPC; // NOUVEAU: Enregistrement du solveur CPC
16
+
17
+ /**
18
+ /**
19
+ * Évalue la distance totale d'un chemin pour le problème du voyageur de commerce (TSP).
20
+ * @param {Array<{x: number, y: number}>} path - Un tableau de points représentant le chemin.
21
+ * @returns {number} La distance totale du chemin.
22
+ */
23
+ FunctionRegistry['tsp.calculateEnergy'] = (path) => {
24
+ // Crée un tableau d'indices [0, 1, 2, ...] pour la fonction evaluatePathDistance.
25
+ const indices = Array.from({ length: path.length }, (_, i) => i);
26
+ return Optimization.Utils.evaluatePathDistance(path, indices);
27
+ };
28
+
29
+ /**
30
+ * Évalue les métriques d'un portefeuille (rendement et volatilité).
31
+ * Pour l'instant, retourne le rendement négatif pour correspondre à l'objectif de minimisation
32
+ * de l'algorithme génétique de la bibliothèque.
33
+ * @param {Array<number>} weights - Les poids des actifs dans le portefeuille.
34
+ * @param {object} payload - Le payload du problème, contenant les actifs.
35
+ * @returns {number} Le rendement négatif du portefeuille.
36
+ */
37
+ FunctionRegistry['portfolio.calculateMetrics'] = (weights, payload) => {
38
+ const { assets, maxVolatility } = payload;
39
+ // On utilise l'opérateur de la bibliothèque pour créer la fonction de fitness
40
+ // et on l'appelle immédiatement.
41
+ const fitnessFunction = Optimization.Operators.createPortfolioAllocator({
42
+ assets,
43
+ maxVolatility,
44
+ });
45
+ // La fonction de fitness retourne le rendement négatif, ce qui est ce que nous voulons
46
+ // stocker comme "énergie" ou score.
47
+ return fitnessFunction(weights);
48
+ };
49
+
50
+ // --- Fonctions de "Résolution" (algorithmes complets) ---
51
+ // Utiles pour les workers qui exécutent une tâche de bout en bout.
52
+ FunctionRegistry['tsp.solve'] = Optimization.Operators.solveTSP;
53
+ FunctionRegistry['portfolio.solve'] = Optimization.Operators.solvePortfolio;
54
+ FunctionRegistry['fraud.solve'] = Optimization.Operators.solveFraudDetection; // NOUVEAU: Enregistrement du solveur de fraude
55
+ FunctionRegistry['facility.solve'] = Optimization.Operators.solveFacilityLocation;
56
+ FunctionRegistry['security.tune'] = Optimization.Operators.solveFullSecurityTuning;
57
+
58
+ // --- Fonctions "Utilitaires" ---
59
+ FunctionRegistry['utils.evaluatePathDistance'] = Optimization.Utils.evaluatePathDistance;
60
+
61
+
4
62
  /**
5
63
  * @namespace ProblemInitializers
6
64
  * @description Fonctions pour générer dynamiquement les données d'un problème.
@@ -68,11 +126,18 @@ class ProblemManager {
68
126
  const problems = JSON.parse(data);
69
127
  // Initialisation dynamique des problèmes
70
128
  for (const problem of problems) { // eslint-disable-line no-unused-vars
129
+ // Résolution des fonctions via le registre
130
+ if (problem.workUnit.scoreFunction) {
131
+ problem.workUnit.scoreFunction = FunctionRegistry[problem.workUnit.scoreFunction] || null;
132
+ }
133
+
71
134
  for (const key in problem.payload) {
72
135
  const value = problem.payload[key];
73
136
  // On cherche une instruction d'initialisation (ex: { "$init": "generate:randomPoints", ... })
74
137
  if (typeof value === 'object' && value !== null && value.$init) {
75
138
  const initializer = ProblemInitializers[value.$init];
139
+ // On cherche une instruction de fonction (ex: { "$func": "tsp.calculateEnergy" })
140
+ // Note: Actuellement non utilisé, mais prêt pour une future extension.
76
141
  if (initializer) {
77
142
  // On remplace l'objet d'instruction par les données générées.
78
143
  problem.payload[key] = initializer(value.params || {});
@@ -140,6 +205,18 @@ class ProblemManager {
140
205
  task.logProgress = problem.payload.logProgress || false;
141
206
  task.concurrency = problem.payload.concurrency;
142
207
  break;
208
+
209
+ case 'multi_objective_genetic_algorithm':
210
+ // La difficulté s'applique au nombre de générations
211
+ const baseGenerationsMulti = Math.max(30, problem.workUnit.baseGenerations || 0);
212
+ task.generations = scalingFactor
213
+ ? Math.floor(baseGenerationsMulti * Math.pow(scalingFactor, suspicionFactor))
214
+ : Math.floor(baseGenerationsMulti * (0.5 + suspicionFactor));
215
+ task.payload = problem.payload;
216
+ // L'état initial est le front de Pareto actuel, que le client peut utiliser pour l'élitisme
217
+ task.initialFront = problem.state.paretoFront;
218
+ task.solverName = problem.workUnit.solverName; // Le nom du solveur à utiliser (ex: 'cpc.solve')
219
+ break;
143
220
  }
144
221
 
145
222
  return { problemId: problem.id, task };
@@ -156,16 +233,28 @@ class ProblemManager {
156
233
 
157
234
  switch (problem.workUnit.type) {
158
235
  case 'simulated_annealing_iterations':
159
- if (solutionData.energy < (parseFloat(problem.state.bestEnergy) || Infinity)) {
236
+ const currentBest = parseFloat(problem.state.bestEnergy) || Infinity;
237
+ const isBetter = problem.workUnit.objective === 'maximize'
238
+ ? solutionData.energy > currentBest
239
+ : solutionData.energy < currentBest;
240
+
241
+ if (isBetter) {
160
242
  problem.state.bestSolution = solutionData.solution;
161
243
  problem.state.bestEnergy = solutionData.energy;
244
+ problem.state.lastUpdate = new Date().toISOString();
162
245
  console.log(`[ProblemManager] Nouvelle meilleure solution pour ${problemId}: ${solutionData.energy.toFixed(2)}`);
163
246
  }
164
247
  break;
165
248
  case 'genetic_algorithm_generations':
249
+ // Pour l'algo génétique, on pourrait comparer le meilleur fitness de la nouvelle population
166
250
  problem.state.population = solutionData.population;
167
251
  console.log(`[ProblemManager] Population mise à jour pour ${problemId}.`);
168
252
  break;
253
+
254
+ case 'multi_objective_genetic_algorithm':
255
+ // Pour le multi-objectifs, on fusionne le front de Pareto existant avec celui du client.
256
+ this._integrateParetoFront(problem, solutionData.paretoFront);
257
+ break;
169
258
  }
170
259
  this.saveProblems();
171
260
  }
@@ -182,26 +271,76 @@ class ProblemManager {
182
271
 
183
272
  console.log(`[ProblemManager] Génération d'une solution initiale pour le problème ${problem.id}...`);
184
273
 
185
- // On se base sur le type de problème pour générer une solution de base.
186
- // Pour l'instant, on gère le cas le plus commun (TSP/points)
187
- // qui utilise le recuit simulé.
188
- switch (problem.workUnit.type) {
189
- case 'simulated_annealing_iterations': {
190
- // Pour un TSP, une solution initiale est un ordre des points.
191
- // On prend l'ordre initial des points du payload.
192
- const initialSolution = problem.payload.points;
193
- if (initialSolution && Array.isArray(initialSolution)) {
194
- // On calcule l'énergie (coût) de cette solution initiale.
195
- const energy = Optimization.tsp.calculateEnergy(initialSolution);
196
- problem.state.bestSolution = initialSolution;
197
- problem.state.bestEnergy = energy;
198
- problem.state.lastUpdate = new Date().toISOString();
199
- console.log(`[ProblemManager] Solution initiale pour ${problem.id} générée avec une énergie de ${energy.toFixed(2)}.`);
200
- this.saveProblems(); // On sauvegarde la nouvelle solution
274
+ // On utilise la fonction de score définie dans la config
275
+ const scoreFunction = problem.workUnit.scoreFunction;
276
+ // On suppose que la source de la solution initiale est définie dans la config
277
+ const initialSolutionSource = problem.payload[problem.workUnit.initialSolutionSource];
278
+
279
+ if (scoreFunction && initialSolutionSource && Array.isArray(initialSolutionSource)) {
280
+ const initialSolution = initialSolutionSource;
281
+ // On calcule le score (énergie, fitness, etc.) de cette solution initiale.
282
+ // La fonction de scoring peut nécessiter des arguments supplémentaires du payload.
283
+ const score = scoreFunction(initialSolution, problem.payload);
284
+
285
+ problem.state.bestSolution = initialSolution;
286
+ // Le nom de la propriété du score dépend du type de problème
287
+ problem.state.bestEnergy = score; // Pourrait être généralisé si besoin
288
+ problem.state.lastUpdate = new Date().toISOString();
289
+
290
+ console.log(`[ProblemManager] Solution initiale pour ${problem.id} générée avec un score de ${score.toFixed(2)}.`);
291
+ this.saveProblems(); // On sauvegarde la nouvelle solution
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Intègre un nouveau front de Pareto dans l'état du problème.
297
+ * @param {object} problem - L'objet problème.
298
+ * @param {Array<object>} newFront - Le front de Pareto renvoyé par un client.
299
+ * @private
300
+ */
301
+ _integrateParetoFront(problem, newFront) {
302
+ if (!Array.isArray(newFront) || newFront.length === 0) return;
303
+
304
+ const currentFront = problem.state.paretoFront || [];
305
+ const combined = [...currentFront, ...newFront];
306
+
307
+ // --- Logique de tri non-dominé pour trouver le nouveau meilleur front ---
308
+ const paretoDominates = (a, b) => {
309
+ let aIsBetterInOne = false;
310
+ // On suppose que les objectifs sont à minimiser
311
+ for (let i = 0; i < a.objectives.length; i++) {
312
+ if (a.objectives[i] > b.objectives[i]) return false; // A est pire sur au moins un objectif
313
+ if (a.objectives[i] < b.objectives[i]) aIsBetterInOne = true; // A est strictement meilleur sur au moins un
314
+ }
315
+ return aIsBetterInOne;
316
+ };
317
+
318
+ const nextFront = [];
319
+ const dominatedIndices = new Set();
320
+
321
+ for (let i = 0; i < combined.length; i++) {
322
+ if (dominatedIndices.has(i)) continue;
323
+ let isDominated = false;
324
+ for (let j = 0; j < combined.length; j++) {
325
+ if (i === j || dominatedIndices.has(j)) continue;
326
+ if (paretoDominates(combined[j], combined[i])) {
327
+ isDominated = true;
328
+ break;
201
329
  }
202
- break;
330
+ if (paretoDominates(combined[i], combined[j])) {
331
+ dominatedIndices.add(j);
332
+ }
333
+ }
334
+ if (!isDominated) {
335
+ nextFront.push(combined[i]);
203
336
  }
204
- // D'autres types de problèmes (ex: algo génétique) pourraient être ajoutés ici.
337
+ }
338
+
339
+ if (nextFront.length > currentFront.length || !problem.state.paretoFront) {
340
+ console.log(`[ProblemManager] Nouveau front de Pareto pour ${problem.id} avec ${nextFront.length} solutions (précédemment ${currentFront.length}).`);
341
+ problem.state.paretoFront = nextFront;
342
+ problem.state.lastUpdate = new Date().toISOString();
343
+ this.saveProblems();
205
344
  }
206
345
  }
207
346
 
@@ -218,16 +357,29 @@ class ProblemManager {
218
357
  ? this.problems.filter(p => p.id === problemId)
219
358
  : this.problems;
220
359
 
221
- problemsToProcess.forEach(p => this._ensureInitialSolution(p));
360
+ // On ne génère une solution initiale que pour les problèmes mono-objectif
361
+ problemsToProcess
362
+ .filter(p => p.workUnit.type !== 'multi_objective_genetic_algorithm')
363
+ .forEach(p => this._ensureInitialSolution(p));
222
364
 
223
365
  const formatSolution = (p) => {
224
366
  // Après _ensureInitialSolution, on peut supposer que p.state existe.
225
367
  if (!p || !p.state) return null;
368
+
369
+ // Cas spécial pour les problèmes multi-objectifs
370
+ if (p.workUnit.type === 'multi_objective_genetic_algorithm') {
371
+ return {
372
+ id: p.id,
373
+ solution: p.state.paretoFront, // La "solution" est l'ensemble du front
374
+ score: p.state.paretoFront?.length || 0, // Le "score" est le nombre de points sur le front
375
+ lastUpdate: p.state.lastUpdate,
376
+ };
377
+ }
378
+
226
379
  return {
227
380
  id: p.id,
228
381
  solution: p.state.bestSolution,
229
- // Gère les deux types de scores : 'energy' (recuit simulé) et 'fitness' (algo génétique)
230
- score: p.state.bestEnergy !== undefined ? p.state.bestEnergy : p.state.bestFitness,
382
+ score: p.state.bestEnergy,
231
383
  lastUpdate: p.state.lastUpdate,
232
384
  };
233
385
  };
@@ -241,6 +393,30 @@ class ProblemManager {
241
393
  return this.problems.map(formatSolution).filter(s => s && s.solution);
242
394
  }
243
395
 
396
+ /**
397
+ * Met à jour le payload d'un problème spécifique par son ID.
398
+ * @param {string} problemId - L'ID du problème à mettre à jour.
399
+ * @param {object} newPayload - Le nouvel objet payload qui remplacera l'ancien.
400
+ * @returns {boolean} - True si la mise à jour a réussi, false sinon.
401
+ */
402
+ updateProblemPayload(problemId, newPayload) {
403
+ const problem = this.problems.find(p => p.id === problemId);
404
+ if (!problem) {
405
+ console.error(`[ProblemManager] Impossible de mettre à jour : problème avec l'ID '${problemId}' non trouvé.`);
406
+ return false;
407
+ }
408
+
409
+ console.log(`[ProblemManager] Mise à jour du payload pour le problème '${problemId}'.`);
410
+ problem.payload = newPayload;
411
+
412
+ // Invalider l'état actuel car le problème a changé
413
+ problem.state.bestSolution = null;
414
+ problem.state.bestEnergy = "Infinity";
415
+
416
+ this.saveProblems();
417
+ return true;
418
+ }
419
+
244
420
  }
245
421
 
246
422
  export { ProblemManager }; // Export the class for testing