@anonympins/fingerprint 0.7.2 → 0.7.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +36 -9
  2. package/README.md +1 -1
  3. package/package.json +109 -107
  4. package/public/fingerprint-anti-bot.zip +0 -0
  5. package/src/js/fingerprint.client.js +252 -45
  6. package/src/js/fingerprint.client.obfuscated.js +1 -1
  7. package/src/js/fingerprint.js +265 -185
  8. package/src/js/fingerprint.utils.js +32 -5
  9. package/src/js/fingerprint.worker.js +81 -0
  10. package/src/js/library.js +6 -2
  11. package/src/js/pow.solver.js +2 -3
  12. package/src/js/problem-manager.js +113 -113
  13. package/src/js/tests/fingerprint.client.init.test.js +30 -0
  14. package/src/js/tests/fingerprint.test.js +136 -4
  15. package/src/js/tests/threatIntel.test.js +76 -0
  16. package/src/php/AutoTuner.php +36 -18
  17. package/src/php/Challenge/ChallengeUtils.php +121 -240
  18. package/src/php/DirectFingerprint.php +16 -4
  19. package/src/php/FingerprintClient.php +17 -19
  20. package/src/php/FingerprintEngine.php +77 -39
  21. package/src/php/Ja3AnomalyDetector.php +20 -12
  22. package/src/php/Optimization/Optimization.php +5 -1
  23. package/src/php/Optimization/OptimizationOperators.php +5 -1
  24. package/src/php/Optimization/ProblemInitializers.php +16 -4
  25. package/src/php/ProblemManager.php +20 -14
  26. package/src/php/Tests/ChallengeUtilsTest.php +469 -453
  27. package/src/php/Tests/FingerprintEngineTest.php +6 -1
  28. package/src/php/Tests/RequestUtilsTest.php +1 -1
  29. package/src/php/Tests/ThreatIntelTest.php +64 -0
  30. package/src/php/Utils/Env.php +51 -49
  31. package/src/php/Utils/MaliciousPatterns.php +74 -74
  32. package/src/php/Utils/RequestUtils.php +231 -132
  33. package/src/php/WordPress/WpDbStore.php +4 -0
  34. package/src/php/WordPress/{fingerprint-wordpress.php → fingerprint-anti-bot.php} +131 -126
  35. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  36. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +1 -1
  37. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  38. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +1 -1
  39. package/src/php/WordPress/package.php +6 -3
  40. package/src/php/WordPress/readme.txt +36 -0
  41. package/public/fingerprint-wordpress.zip +0 -0
@@ -159,9 +159,14 @@ const ClientLibrary = {
159
159
  * @param {object} [detail={}] - The data to include in the event's detail property.
160
160
  */
161
161
  _dispatchEvent(eventName, detail = {}) {
162
- if (typeof window === 'undefined' || typeof CustomEvent === 'undefined') return;
163
- const event = new CustomEvent(`fingerprint:${eventName}`, { detail });
164
- window.dispatchEvent(event);
162
+ if (typeof window === 'undefined') return;
163
+ const CustomEventCtor = window.CustomEvent || (typeof CustomEvent !== 'undefined' ? CustomEvent : null);
164
+ if (!CustomEventCtor) return;
165
+ try {
166
+ const event = new CustomEventCtor(`fingerprint:${eventName}`, { detail });
167
+ window.dispatchEvent(event);
168
+ } catch (e) {
169
+ }
165
170
  },
166
171
 
167
172
  /**
@@ -190,10 +195,20 @@ const ClientLibrary = {
190
195
  const canvas2d = typeof CanvasRenderingContext2D !== 'undefined' ? CanvasRenderingContext2D : win.CanvasRenderingContext2D;
191
196
  const webgl = typeof WebGLRenderingContext !== 'undefined' ? WebGLRenderingContext : win.WebGLRenderingContext;
192
197
 
198
+ const isFetchTampered = () => {
199
+ if (ClientLibrary._isFetchPatched) {
200
+ if (ClientLibrary._originalFetch) {
201
+ return !Function.prototype.toString.call(ClientLibrary._originalFetch).includes('[native code]');
202
+ }
203
+ return false;
204
+ }
205
+ return checkNative(win, 'fetch');
206
+ };
207
+
193
208
  return checkNative(htmlCanvas?.prototype, 'toDataURL') ||
194
209
  checkNative(canvas2d?.prototype, 'getImageData') ||
195
210
  checkNative(webgl?.prototype, 'getParameter') ||
196
- checkNative(win, 'fetch');
211
+ isFetchTampered();
197
212
  },
198
213
 
199
214
  /**
@@ -949,38 +964,118 @@ const ClientLibrary = {
949
964
  },
950
965
 
951
966
  /**
952
- * Retrieves collected client behavioral metrics.
953
- * Call before submitting sensitive requests.
954
- * @returns {ClientBehaviorMetrics}
967
+ * Évalue le comportement côté client en local sans transmettre de données brutes.
968
+ * Retourne 1 si le comportement est humain/légitime, 0 en cas de suspicion/bot.
969
+ * @returns {number} 1 ou 0
955
970
  */
956
- getClientBehaviorMetrics() {
957
- // Add history length as a behavioral signal.
958
- metrics.historyLength = window.history.length;
971
+ evaluateBehavior() {
972
+ let score = 0;
973
+ if (metrics.honeypotInteraction) return 100;
974
+ if (ClientLibrary.detectTamperedPrototypes()) score += 80;
975
+
976
+ // Vérification du rendu graphique
977
+ if (metrics.rendering) {
978
+ if (metrics.rendering.offscreenAnom) return 0;
979
+ const fps = parseFloat(metrics.rendering.fps || 0);
980
+ const jitter = parseFloat(metrics.rendering.jitter || 0);
981
+ if (fps > 250 || (fps > 0 && fps < 15)) score += 50;
982
+ if (jitter > 6.0) score += Math.min(80, (jitter - 6.0) * 10);
983
+ }
984
+
985
+ // Mouvements de souris robotiques
986
+ if (mouseMovementsHistory.length >= 3) {
987
+ let totalDist = 0;
988
+ let pauses = 0;
989
+ const segments = [];
990
+ for (let i = 1; i < mouseMovementsHistory.length; i++) {
991
+ const p1 = mouseMovementsHistory[i - 1];
992
+ const p2 = mouseMovementsHistory[i];
993
+ const dx = p2.x - p1.x;
994
+ const dy = p2.y - p1.y;
995
+ const dt = p2.t - p1.t;
996
+ const dist = Math.sqrt(dx * dx + dy * dy);
997
+ if (dt > 0) segments.push(dist / dt);
998
+ totalDist += dist;
999
+ if (dt > 100 && dist < 5) pauses++;
1000
+ }
1001
+ if (segments.length >= 2) {
1002
+ const totalTime = mouseMovementsHistory[mouseMovementsHistory.length - 1].t - mouseMovementsHistory[0].t;
1003
+ const avgSpeed = totalTime > 0 ? totalDist / totalTime : 0;
1004
+ const pStart = mouseMovementsHistory[0];
1005
+ const pEnd = mouseMovementsHistory[mouseMovementsHistory.length - 1];
1006
+ const straightDist = Math.sqrt(Math.pow(pEnd.x - pStart.x, 2) + Math.pow(pEnd.y - pStart.y, 2));
1007
+ const straightness = totalDist > 0 ? straightDist / totalDist : 1;
1008
+ if (avgSpeed > 3.0) score += 25;
1009
+ if (straightness > 0.95) score += 30;
1010
+ if (pauses === 0 && segments.length > 20) score += 15;
1011
+ }
1012
+ }
1013
+
1014
+ // Analyse des événements tactiles
1015
+ if (touchMovementsHistory.length >= 3) {
1016
+ let totalDist = 0;
1017
+ let totalPressure = 0;
1018
+ let totalRadius = 0;
1019
+ for (const pt of touchMovementsHistory) {
1020
+ totalPressure += pt.p || 0;
1021
+ totalRadius += pt.r || 0;
1022
+ }
1023
+ const avgPressure = totalPressure / touchMovementsHistory.length;
1024
+ const avgRadius = totalRadius / touchMovementsHistory.length;
1025
+ let sqDiffPressure = 0;
1026
+ let sqDiffRadius = 0;
1027
+ for (const pt of touchMovementsHistory) {
1028
+ sqDiffPressure += Math.pow((pt.p || 0) - avgPressure, 2);
1029
+ sqDiffRadius += Math.pow((pt.r || 0) - avgRadius, 2);
1030
+ }
1031
+ if (avgPressure > 0 && (sqDiffPressure / touchMovementsHistory.length) === 0) {
1032
+ score += 30;
1033
+ }
1034
+ if (avgRadius > 0 && (sqDiffRadius / touchMovementsHistory.length) === 0) {
1035
+ score += 30;
1036
+ }
1037
+ }
959
1038
 
960
- // Attach collection timestamp for replay detection
961
- metrics.clicksHistory = clicksHistory;
962
- metrics.clientTimestamp = Date.now();
1039
+ // Détection de rack / ferme de téléphones mobiles immobiles
1040
+ if (typeof window !== 'undefined' && (window.navigator?.userAgent || '').includes('Mobile')) {
1041
+ if (metrics.motionVariance === 0) return 0;
1042
+ }
963
1043
 
964
- metrics.touchMovementsHistory = touchMovementsHistory;
965
- if (ClientLibrary.detectTamperedPrototypes()) {
966
- metrics.prototypeTampered = true;
1044
+ // Variance des clics ultra-précise (bot de clic)
1045
+ if (clicksHistory.length >= 3) {
1046
+ const targets = {};
1047
+ for (const c of clicksHistory) {
1048
+ if (!c.targetId) continue;
1049
+ if (!targets[c.targetId]) targets[c.targetId] = [];
1050
+ targets[c.targetId].push(c);
1051
+ }
1052
+ for (const id in targets) {
1053
+ const clks = targets[id];
1054
+ if (clks.length >= 3) {
1055
+ const mx = clks.reduce((s, c) => s + c.x, 0) / clks.length;
1056
+ const my = clks.reduce((s, c) => s + c.y, 0) / clks.length;
1057
+ const v = clks.reduce((s, c) => s + Math.pow(c.x - mx, 2) + Math.pow(c.y - my, 2), 0) / clks.length;
1058
+ if (v < 1.0) return 0;
1059
+ }
1060
+ }
967
1061
  }
968
- metrics.motionVariance = metrics.motionVariance ?? -1;
969
- // Include mouse trajectory history for server-side evaluation
970
- metrics.mouseMovementsHistory = mouseMovementsHistory;
971
-
972
- // NOUVEAU: Keystroke dynamics metrics (dwell and flight times)
973
- metrics.keystrokeDwellTimes = keystrokeDwellTimes;
974
- metrics.keystrokeFlightTimes = keystrokeFlightTimes;
975
-
976
- // Calculate mean keystroke latency
977
- if (keystrokeLatencies.length > 0) {
978
- const sum = keystrokeLatencies.reduce((a, b) => a + b, 0);
979
- metrics.keystrokeLatency = sum / keystrokeLatencies.length;
980
- } else {
981
- metrics.keystrokeLatency = 0;
1062
+
1063
+ // Dynamique des touches de clavier robotique
1064
+ if (keystrokeDwellTimes.length >= 5) {
1065
+ const mean = keystrokeDwellTimes.reduce((a, b) => a + b, 0) / keystrokeDwellTimes.length;
1066
+ const variance = keystrokeDwellTimes.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / keystrokeDwellTimes.length;
1067
+ if (Math.sqrt(variance) < 2.0 || mean < 15.0) return 0;
982
1068
  }
983
- return metrics;
1069
+
1070
+ return 1;
1071
+ },
1072
+
1073
+ /**
1074
+ * Récupère le résultat comportemental sous forme binaire (1 = valide/humain, 0 = suspect).
1075
+ * @returns {number} 1 ou 0
1076
+ */
1077
+ getClientBehaviorMetrics() {
1078
+ return this.evaluateBehavior();
984
1079
  },
985
1080
 
986
1081
  /**
@@ -991,14 +1086,13 @@ const ClientLibrary = {
991
1086
  */
992
1087
  async protectedFetch(resource, options = {}) {
993
1088
  const fp = this.getDeviceFingerprint();
994
- const rawBehavior = this.getClientBehaviorMetrics();
995
1089
  const zkpProof = await this.generateZkpProof(fp); // Generate ZKP proof
996
- const behavior = await signMetrics(rawBehavior);
1090
+ const behavior = this.getClientBehaviorMetrics();
997
1091
 
998
1092
  const headers = new Headers(options.headers || {});
999
1093
  headers.set('X-ZKP-Proof', zkpProof); // Add ZKP proof to headers
1000
1094
  headers.set('X-Device-Fingerprint', fp);
1001
- headers.set('X-Behavior-Metrics', JSON.stringify(behavior));
1095
+ headers.set('X-Behavior-Metrics', String(behavior));
1002
1096
 
1003
1097
  options.headers = headers;
1004
1098
  return fetch(resource, options);
@@ -1008,6 +1102,8 @@ const ClientLibrary = {
1008
1102
  _isFetchPatched: false,
1009
1103
  _interceptorChain: [],
1010
1104
  // Store original fetch bound to window to prevent illegal invocation errors
1105
+ _targetDomains: [],
1106
+ _swRegistration: null,
1011
1107
  _originalFetch: (typeof window !== 'undefined') ? window.fetch.bind(window) : null,
1012
1108
 
1013
1109
  /**
@@ -1045,10 +1141,78 @@ const ClientLibrary = {
1045
1141
  */
1046
1142
  onHoneypotTrigger() {
1047
1143
  metrics.honeypotInteraction = true;
1144
+ if (this.workerPath) {
1145
+ this.syncServiceWorkerCredentials();
1146
+ }
1048
1147
  // Dispatch event allowing host applications to react
1049
1148
  this._dispatchEvent('honeypotTriggered');
1050
1149
  },
1051
1150
 
1151
+ /**
1152
+ * Registers and manages the Service Worker for transparent HTTPS request decoration.
1153
+ * @param {string} workerPath - Path to the worker script.
1154
+ * @param {string[]} [targetDomains=[]] - Domains to intercept.
1155
+ * @param {string} [scope] - Service Worker registration scope.
1156
+ * @returns {Promise<ServiceWorkerRegistration|null>}
1157
+ */
1158
+ async initServiceWorker(workerPath, targetDomains = [], scope = undefined) {
1159
+ if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {
1160
+ console.warn('[Fingerprint] Service Worker is not supported in this browser.');
1161
+ return null;
1162
+ }
1163
+
1164
+ try {
1165
+ const registerOptions = scope ? { scope } : {};
1166
+ const registration = await navigator.serviceWorker.register(workerPath, registerOptions);
1167
+ this._swRegistration = registration;
1168
+
1169
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
1170
+ this.syncServiceWorkerCredentials(targetDomains);
1171
+ });
1172
+
1173
+ await navigator.serviceWorker.ready;
1174
+ this.syncServiceWorkerCredentials(targetDomains);
1175
+
1176
+ if (typeof window !== 'undefined') {
1177
+ window.addEventListener('focus', () => this.syncServiceWorkerCredentials(targetDomains), { passive: true });
1178
+ document.addEventListener('visibilitychange', () => {
1179
+ if (document.visibilityState === 'visible') {
1180
+ this.syncServiceWorkerCredentials(targetDomains);
1181
+ }
1182
+ }, { passive: true });
1183
+ }
1184
+
1185
+ console.log('[Fingerprint] Network Service Worker active and synchronized.');
1186
+ return registration;
1187
+ } catch (error) {
1188
+ console.warn('[Fingerprint] Failed to register Service Worker:', error);
1189
+ return null;
1190
+ }
1191
+ },
1192
+
1193
+ /**
1194
+ * Synchronizes device fingerprint and behavior metrics with the active Service Worker.
1195
+ * @param {string[]} [targetDomains=[]]
1196
+ */
1197
+ syncServiceWorkerCredentials(targetDomains = []) {
1198
+ if (typeof navigator === 'undefined' || !navigator.serviceWorker) return;
1199
+ const domains = (Array.isArray(targetDomains) && targetDomains.length > 0)
1200
+ ? targetDomains
1201
+ : (this._targetDomains || []);
1202
+ const payload = {
1203
+ type: 'UPDATE_CREDENTIALS',
1204
+ fp: this.getDeviceFingerprint(),
1205
+ behavior: this.getClientBehaviorMetrics(),
1206
+ targetDomains: domains
1207
+ };
1208
+
1209
+ if (navigator.serviceWorker.controller) {
1210
+ navigator.serviceWorker.controller.postMessage(payload);
1211
+ } else if (this._swRegistration && this._swRegistration.active) {
1212
+ this._swRegistration.active.postMessage(payload);
1213
+ }
1214
+ },
1215
+
1052
1216
  /**
1053
1217
  * Initializes the fingerprinting fetch interceptor.
1054
1218
  * Appends to interceptor chain without clobbering existing handlers.
@@ -1071,11 +1235,10 @@ const ClientLibrary = {
1071
1235
 
1072
1236
  if (shouldProtect) {
1073
1237
  const fp = this.getDeviceFingerprint();
1074
- const rawBehavior = this.getClientBehaviorMetrics();
1075
- const behavior = await signMetrics(rawBehavior);
1238
+ const behavior = this.getClientBehaviorMetrics();
1076
1239
  const headers = new Headers(options.headers || {});
1077
1240
  headers.set('X-Device-Fingerprint', fp);
1078
- headers.set('X-Behavior-Metrics', JSON.stringify(behavior));
1241
+ headers.set('X-Behavior-Metrics', String(behavior));
1079
1242
  options.headers = headers;
1080
1243
  }
1081
1244
 
@@ -1193,10 +1356,20 @@ const ClientLibrary = {
1193
1356
  * @param {Response} response - Initial response (status 404/challenge).
1194
1357
  * @param {RequestInfo} resource - Original request resource.
1195
1358
  * @param {RequestInit} options - Original request options.
1359
+ * @param {number} [maxRetries=3] - Maximum allowed challenge retry attempts.
1196
1360
  * @returns {Promise<Response>} - Retried response.
1197
1361
  * @private
1198
1362
  */
1199
- async solveChallengeAndRetry(response, resource, options) {
1363
+ async solveChallengeAndRetry(response, resource, options = {}, maxRetries = 3) {
1364
+ const effectiveMaxRetries = (options && typeof options._powMaxRetries === 'number')
1365
+ ? options._powMaxRetries
1366
+ : (typeof maxRetries === 'number' ? maxRetries : 3);
1367
+ const currentRetries = (options && options._powRetryCount) || (resource && resource._powRetryCount) || 0;
1368
+ if (currentRetries >= effectiveMaxRetries) {
1369
+ console.warn(`[Fingerprint] Challenge retry limit reached (${effectiveMaxRetries}). Aborting challenge retry loop.`);
1370
+ return response;
1371
+ }
1372
+
1200
1373
  if (!response || response.status !== 404 || !response.headers?.get?.('content-type')?.includes('application/json') || response.bodyUsed) {
1201
1374
  return response;
1202
1375
  }
@@ -1223,8 +1396,16 @@ const ClientLibrary = {
1223
1396
  // Attach solver fingerprint to retry request
1224
1397
  url.searchParams.set('pow_fp', solverFp);
1225
1398
 
1226
- // Use fetch to retry with appropriate execution context
1227
- return window.fetch(url.toString(), options);
1399
+ // Use fetch to retry with incremented retry count to prevent infinite challenge loops
1400
+ const retryOptions = {
1401
+ ...options,
1402
+ _powRetryCount: currentRetries + 1,
1403
+ _powMaxRetries: effectiveMaxRetries
1404
+ };
1405
+ const fetchFn = (typeof window !== 'undefined' && typeof window.fetch === 'function')
1406
+ ? window.fetch
1407
+ : fetch;
1408
+ return fetchFn(url.toString(), retryOptions);
1228
1409
  } catch (e) {
1229
1410
  console.error('[Fingerprint] Failed to solve or retry challenge:', e);
1230
1411
  return response; // Retourne la réponse 429 originale en cas d'échec
@@ -1249,6 +1430,7 @@ const ClientLibrary = {
1249
1430
  honeypots = [],
1250
1431
  trapUrls = [], // Trap URLs
1251
1432
  wasmPath,
1433
+ worker = false,
1252
1434
  workerPath, // NOUVEAU
1253
1435
  fetch: fetchConfig = {}
1254
1436
  } = config;
@@ -1262,8 +1444,30 @@ const ClientLibrary = {
1262
1444
  if (mouse) {
1263
1445
  this.startMouseEntropyTracker();
1264
1446
  }
1265
- if (workerPath) {
1266
- this.workerPath = workerPath;
1447
+
1448
+ // Instantiation du Service Worker via initializeClient
1449
+ let resolvedWorkerPath = null;
1450
+ let workerScope = undefined;
1451
+ let workerTargetDomains = fetchConfig.targetDomains || [];
1452
+
1453
+ if (typeof worker === 'string') {
1454
+ resolvedWorkerPath = worker;
1455
+ } else if (typeof worker === 'object' && worker !== null) {
1456
+ resolvedWorkerPath = worker.path || '/fingerprint.worker.js';
1457
+ workerScope = worker.scope;
1458
+ if (Array.isArray(worker.targetDomains)) {
1459
+ workerTargetDomains = worker.targetDomains;
1460
+ }
1461
+ } else if (worker === true) {
1462
+ resolvedWorkerPath = '/fingerprint.worker.js';
1463
+ } else if (workerPath) {
1464
+ resolvedWorkerPath = workerPath;
1465
+ }
1466
+
1467
+ if (resolvedWorkerPath) {
1468
+ this.workerPath = resolvedWorkerPath;
1469
+ this._targetDomains = workerTargetDomains;
1470
+ this.initServiceWorker(resolvedWorkerPath, workerTargetDomains, workerScope);
1267
1471
  }
1268
1472
  if (keystrokes) {
1269
1473
  this.startKeystrokeDynamicsTracker();
@@ -1294,16 +1498,17 @@ const ClientLibrary = {
1294
1498
  if (trapUrls.length > 0) {
1295
1499
  this.injectTrapLinks(trapUrls);
1296
1500
  }
1297
- // Enable fetch interception if configured
1298
- if (config.fetch) {
1501
+ // Enable fetch monkey-patching only if explicitly configured and no Service Worker is used
1502
+ if (config.fetch && !resolvedWorkerPath) {
1299
1503
  this.initializeFetch(fetchConfig.targetDomains);
1300
1504
 
1301
1505
  // Add challenge resolution interceptor
1302
1506
  if (fetchConfig.handleChallenges !== false) {
1507
+ const maxRetries = typeof fetchConfig.maxRetries === 'number' ? fetchConfig.maxRetries : 3;
1303
1508
  this.addFetchInterceptor(async (resource, options, next) => {
1304
1509
  const originalResponse = await next(resource, options);
1305
1510
  // Clone response to prevent draining the body stream for original caller
1306
- return this.solveChallengeAndRetry(originalResponse.clone(), resource, options);
1511
+ return this.solveChallengeAndRetry(originalResponse.clone(), resource, options, maxRetries);
1307
1512
  });
1308
1513
  }
1309
1514
  }
@@ -1521,6 +1726,8 @@ export const generateZkpProof = ClientLibrary.generateZkpProof.bind(ClientLibrar
1521
1726
  export const initializeSpace = ClientLibrary.initializeSpace.bind(ClientLibrary);
1522
1727
  export const readSpaceBlock = ClientLibrary.readSpaceBlock.bind(ClientLibrary);
1523
1728
  export const solveSpaceChallenge = ClientLibrary.solveSpaceChallenge.bind(ClientLibrary);
1729
+ export const initServiceWorker = ClientLibrary.initServiceWorker.bind(ClientLibrary);
1730
+ export const syncServiceWorkerCredentials = ClientLibrary.syncServiceWorkerCredentials.bind(ClientLibrary);
1524
1731
 
1525
1732
  // Export the internal object for testing purposes
1526
1733
  export default ClientLibrary;