@anonympins/fingerprint 0.3.0 → 0.3.2

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/library.js CHANGED
@@ -1635,6 +1635,8 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1635
1635
  behaviorScore: secureRandom(),
1636
1636
  crossLayerInconsistencyScore: secureRandom(),
1637
1637
  timeInconsistencyScore: secureRandom(),
1638
+ tlsSpoofingScore: secureRandom(), // NOUVEAU: Ajout du poids pour le spoofing TLS
1639
+ botScore: secureRandom(), // NOUVEAU: Ajout du poids pour la détection de bot
1638
1640
  },
1639
1641
  patterns: {
1640
1642
  velocityThreshold: 100 + secureRandom() * 400, // 100-500ms
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anonympins/fingerprint",
3
- "version": "0.3.0",
3
+ "version": "0.3.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
5
  "main": "fingerprint.js",
6
6
  "type": "module",
@@ -8,20 +8,24 @@
8
8
  "node": ">=20.0.0"
9
9
  },
10
10
  "scripts": {
11
- "test": "vitest run --reporter=verbose"
11
+ "test": "vitest run --reporter=verbose",
12
+ "build": "node build-client.js"
12
13
  },
13
14
  "files": [
14
15
  "fingerprint.js",
15
16
  "fingerprint.client.js",
17
+ "fingerprint.client.obfuscated.js",
16
18
  "fingerprint.builder.js",
17
19
  "pow.solver.js",
18
20
  "pow.worker.js",
21
+ "pow.solver.inline.js",
19
22
  "problem-manager.js",
20
23
  "optimization.worker.js",
21
24
  "library.js",
22
25
  "redis-store.js",
23
26
  "mongodb-store.js",
24
- "sql-store.js",
27
+ "sql-store.js",
28
+ "public/fp.wasm",
25
29
  "README.md",
26
30
  "LICENSE"
27
31
  ],
@@ -63,7 +67,10 @@
63
67
  "cookie-parser": "^1.4.6",
64
68
  "express": "^4.18.2",
65
69
  "prom-client": "^15.1.2",
66
- "vitest": "^4.1.11"
70
+ "vitest": "^4.1.11",
71
+ "javascript-obfuscator": "^4.1.0",
72
+ "terser": "^5.30.3",
73
+ "jsdom": "^24.0.0"
67
74
  },
68
75
  "peerDependencies": {
69
76
  "ioredis": "^5.3.2",
@@ -0,0 +1,255 @@
1
+ /**
2
+ * @file @/pow.solver.inline.js
3
+ * @description Contient les fonctions côté client pour résoudre les différents types de challenges Proof-of-Work.
4
+ * Fichier compatible avec l'injection directe dans un script HTML (sans `export`).
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
11
+ * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
12
+ * @param {bigint} target - La cible à atteindre.
13
+ * @param {Function} progressCallback - Callback pour les mises à jour de progression.
14
+ * @returns {Promise<number>} La solution (un nombre entier).
15
+ */
16
+ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
17
+ // --- FIX: Add validation for the target to prevent BigInt conversion errors ---
18
+ if (typeof target !== 'bigint' && (typeof target !== 'string' || !/^[0-9a-fA-F]+$/.test(target))) {
19
+ throw new TypeError(`Invalid target type: expected a BigInt or a hex string, but got ${typeof target} with value ${target}`);
20
+ }
21
+
22
+ const cpuTarget = typeof target === 'bigint' ? target : BigInt('0x' + target);
23
+ // --- END FIX ---
24
+ const encoder = new TextEncoder();
25
+ let cpuSolution = 0;
26
+
27
+ while (true) {
28
+ const solutionBytes = encoder.encode(String(cpuSolution));
29
+
30
+ // Concaténation binaire directe : c'est plus rapide et plus sûr.
31
+ const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
32
+ finalBlock.set(baseBlock);
33
+ finalBlock.set(solutionBytes, baseBlock.length);
34
+
35
+ if (cpuSolution === 0) {
36
+ // Pour le débogage, on peut afficher le message reconstruit.
37
+ const reconstructedMsg = new TextDecoder().decode(finalBlock);
38
+ console.log(`[FP Client Solve] Hashing message: "${reconstructedMsg}"`);
39
+ }
40
+
41
+ const buf = await crypto.subtle.digest("SHA-256", finalBlock);
42
+ const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
43
+ // --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
44
+ if (cpuSolution === 0) { // Log only the first attempt
45
+ console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
46
+ }
47
+ // --- FIN DES LOGS ---
48
+ if (BigInt('0x' + hashHex) < cpuTarget) break;
49
+ cpuSolution++;
50
+ if (cpuSolution % 100000 === 0) {
51
+ await new Promise(r => setTimeout(r, 0));
52
+ if (progressCallback) progressCallback(cpuSolution);
53
+ }
54
+ }
55
+ return cpuSolution;
56
+ }
57
+
58
+ /**
59
+ * Résout un challenge CPU basé sur une cible (version Web Worker).
60
+ * @param {string} message - Le message à hasher (ex: `ip:nonce:solution:secret`).
61
+ * @param {bigint} target - La cible à atteindre.
62
+ * @returns {Promise<number>} La solution (un nombre entier).
63
+ */
64
+ async function solveCpuTarget(message, target) {
65
+ // Vérifie si les Web Workers sont supportés par le navigateur.
66
+ if (typeof(Worker) === "undefined") {
67
+ console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
68
+ // Ici, on pourrait remettre l'ancienne implémentation comme solution de secours.
69
+ // Pour la clarté, nous supposons que les workers sont disponibles.
70
+ throw new Error("Web Worker support is required for CPU challenges.");
71
+ }
72
+
73
+ return new Promise((resolve, reject) => {
74
+ // Crée un worker à partir du script dédié. Le chemin doit être accessible publiquement.
75
+ // Assurez-vous que `pow.worker.js` est servi par votre serveur statique.
76
+ const worker = new Worker('./pow.worker.js');
77
+
78
+ worker.onmessage = (event) => {
79
+ resolve(event.data.solution);
80
+ worker.terminate(); // Nettoie le worker une fois le travail terminé.
81
+ };
82
+
83
+ worker.onerror = (error) => {
84
+ reject(error);
85
+ worker.terminate();
86
+ };
87
+
88
+ // Envoie les données du challenge au worker pour qu'il commence le calcul.
89
+ worker.postMessage({ message, target });
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Résout un challenge basé sur la mémoire.
95
+ * @param {string} seed - La graine pour l'initialisation de la mémoire.
96
+ * @param {number} difficulty - La difficulté (en Mo).
97
+ * @returns {Promise<number>} La solution (nombre entier).
98
+ */
99
+ async function solveMemory(seed, difficulty) {
100
+ // On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
101
+ const YIELD_THRESHOLD = 100000;
102
+ const size = difficulty * 1024 * 1024;
103
+ const buffer = new Uint32Array(size / 4);
104
+ let h = new TextEncoder().encode(seed).reduce((acc, v) => acc + v, 0);
105
+ for (let i = 0; i < buffer.length; i++) {
106
+ buffer[i] = (h = Math.imul(h ^ i, 1597334677));
107
+ if (i % YIELD_THRESHOLD === 0) {
108
+ await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
109
+ }
110
+ }
111
+ let solution = 0;
112
+ const iterations = size / 16;
113
+ let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
114
+ for (let i = 0; i < iterations; i++) {
115
+ addr = buffer[addr] % buffer.length;
116
+ solution ^= addr;
117
+ if (i % YIELD_THRESHOLD === 0) {
118
+ await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
119
+ }
120
+ }
121
+ return solution;
122
+ }
123
+
124
+ /**
125
+ * Résout un challenge de type "Problème du Voyageur de Commerce" (TSP).
126
+ * NOTE: Ceci est une implémentation simple (heuristique du plus proche voisin) et n'est pas garantie
127
+ * de trouver la solution optimale, mais elle est suffisante pour un challenge.
128
+ * @param {Array<{x: number, y: number}>} cities - Les coordonnées des villes.
129
+ * @param {number} targetMaxDistance - La distance maximale acceptable.
130
+ * @returns {Promise<{path: number[], distance: number}>} Le chemin et la distance.
131
+ */
132
+ async function solveTsp(cities, targetMaxDistance) {
133
+ // Utility function to calculate the distance between two cities
134
+ function distance(city1, city2) {
135
+ return Math.sqrt(Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2));
136
+ }
137
+
138
+ // Utility function to evaluate the total distance of a path
139
+ function evaluatePathDistance(cities, path) {
140
+ let totalDistance = 0;
141
+ for (let i = 0; i < path.length - 1; i++) {
142
+ totalDistance += distance(cities[path[i]], cities[path[i + 1]]);
143
+ }
144
+ totalDistance += distance(cities[path[path.length - 1]], cities[path[0]]); // Return to start
145
+ return totalDistance;
146
+ }
147
+
148
+ // Solveur simple du TSP (heuristique du plus proche voisin)
149
+ function solveTspNearestNeighbor(cities) {
150
+ const numCities = cities.length;
151
+ if (numCities === 0) return [];
152
+
153
+ let currentPath = [];
154
+ let visited = new Array(numCities).fill(false);
155
+
156
+ let currentCityIndex = 0; // Always start with the first city for reproducibility
157
+ currentPath.push(currentCityIndex);
158
+ visited[currentCityIndex] = true;
159
+
160
+ for (let i = 1; i < numCities; i++) {
161
+ let nearestCityIndex = -1;
162
+ let minDistance = Infinity;
163
+
164
+ for (let j = 0; j < numCities; j++) {
165
+ if (!visited[j]) {
166
+ const dist = distance(cities[currentCityIndex], cities[j]);
167
+ if (dist < minDistance) {
168
+ minDistance = dist;
169
+ nearestCityIndex = j;
170
+ }
171
+ }
172
+ }
173
+ currentCityIndex = nearestCityIndex;
174
+ currentPath.push(currentCityIndex);
175
+ visited[currentCityIndex] = true;
176
+ }
177
+ return currentPath;
178
+ }
179
+
180
+ // To avoid freezing the browser, yield the thread from time to time
181
+ await new Promise(resolve => setTimeout(resolve, 10));
182
+ const solutionPath = solveTspNearestNeighbor(cities);
183
+ const solutionDistance = evaluatePathDistance(cities, solutionPath);
184
+
185
+ return { path: solutionPath, distance: solutionDistance };
186
+ }
187
+
188
+ /**
189
+ * Fonction principale qui reçoit un objet challenge et le résout.
190
+ * @param {object} challenge - L'objet challenge reçu du serveur.
191
+ * @returns {Promise<object>} Un objet contenant la ou les solutions.
192
+ */
193
+ async function solveChallenge(challenge, fingerprint = '') { // fingerprint parameter was already here, but unused in some calls
194
+ const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance } = challenge;
195
+ const solutions = {};
196
+
197
+ switch (type) {
198
+ case 'cpu_target':
199
+ // Note: This case is not fully exercised by tests as it relies on Web Workers.
200
+ if (!cpuTarget) {
201
+ throw new Error("Challenge data is missing 'cpuTarget' property.");
202
+ }
203
+ const target = cpuTarget; // Keep variable name for consistency below
204
+ // Pour ce challenge simple, le baseBlock est juste le nonce.
205
+ const baseBlockBytes = new TextEncoder().encode(nonce + ":");
206
+ solutions.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
207
+ break;
208
+ case 'cpu_mem':
209
+ // Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
210
+ const memSeed = `:${nonce}:${clientSecret}`;
211
+ const [cpuSol, memSol] = await Promise.all([
212
+ (async () => {
213
+ if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property."); // Pass fingerprint to solver
214
+ const baseBlock = new Uint8Array(challenge.baseBlock);
215
+ return solveCpuTargetInline(baseBlock, cpuTarget, null);
216
+ })(),
217
+ solveMemory(memSeed, memDifficulty)
218
+ ]);
219
+ solutions.cpu = cpuSol;
220
+ solutions.mem = memSol;
221
+ break;
222
+ case 'cpu_mem_inline':
223
+ // Version inline pour compatibilité HTML avec IP incluse
224
+ const memSeedInline = `:${nonce}:${clientSecret}`;
225
+ const [cpuSolInline, memSolInline] = await Promise.all([
226
+ (async () => {
227
+ if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property.");
228
+ const baseBlock = new Uint8Array(challenge.baseBlock);
229
+ return solveCpuTargetInline(baseBlock, cpuTarget, null);
230
+ })(),
231
+ solveMemory(memSeedInline, memDifficulty)
232
+ ]);
233
+ solutions.cpu = cpuSolInline;
234
+ solutions.mem = memSolInline;
235
+ break;
236
+ case 'tsp':
237
+ const tspResult = await solveTsp(cities, targetMaxDistance);
238
+ solutions.tsp = tspResult.path;
239
+ solutions.distance = tspResult.distance;
240
+ break;
241
+ default:
242
+ throw new Error(`Unknown challenge type: ${type}`);
243
+ }
244
+
245
+ return solutions;
246
+ }
247
+
248
+ // --- Compatibilité pour l'injection directe dans le HTML ---
249
+ // Si le script est chargé dans un navigateur (window existe), on attache les fonctions nécessaires à window.
250
+ if (typeof window !== 'undefined') {
251
+ window.solveCpuChallengeInline = solveCpuTargetInline;
252
+ window.solveMemoryChallenge = solveMemory;
253
+ window.solveTspChallenge = solveTsp;
254
+ window.solveChallenge = solveChallenge;
255
+ }
package/public/fp.wasm ADDED
Binary file