@anonympins/fingerprint 0.6.5 → 0.6.6

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.
@@ -9,6 +9,7 @@ import {DynamicWasmGenerator} from "./dynamic-wasm.js";
9
9
  import {writeFileSync, readFileSync, existsSync} from "node:fs";
10
10
  import {fileURLToPath} from "node:url";
11
11
  import {dirname, join, resolve} from "node:path";
12
+ import {GpuPowSolver} from "./gpu_pow.solver.js";
12
13
  import {
13
14
  verifyZkpProof,
14
15
  sanitizeRedirectPath,
@@ -354,46 +355,75 @@ export function generateStatelessTicket(payload) {
354
355
  return `${base64UrlEncode(iv)}.${base64UrlEncode(encrypted)}.${base64UrlEncode(signature)}`;
355
356
  }
356
357
  /**
357
- * Détecte les anomalies de flux QUIC/HTTP3 par rapport au User-Agent.
358
+ * Détecte les anomalies de protocole (HTTP/2 et QUIC/HTTP3) par rapport au User-Agent.
358
359
  * @private
359
360
  * @param {object} context - Le contexte de la requête.
360
- * @returns {{quicAnomalyScore: number}}
361
+ * @returns {{protocolAnomalyScore: number}}
361
362
  */
362
- function getQuicAnomalyScore(context) {
363
- const quicFp = context.headers?.['x-quic-fp'] || context.quicFingerprint || null;
364
- if (!quicFp || typeof quicFp !== 'string') {
365
- return { quicAnomalyScore: 0.0 };
366
- }
367
-
368
- const parts = quicFp.split(';');
369
- if (parts.length < 2) return { quicAnomalyScore: 0.0 };
370
-
371
- const params = {};
372
- parts[1].split(',').forEach(p => {
373
- const kv = p.split('=');
374
- if (kv.length === 2) params[kv[0]] = kv[1];
375
- });
376
- const priorityOrder = parts[2] || '';
363
+ function getProtocolAnomalyScore(context) {
364
+ let http2Anomaly = 0.0;
365
+ let quicAnomaly = 0.0;
377
366
 
378
367
  const ua = context.headers?.['user-agent'] || '';
379
368
  const uaParts = parseUserAgent(ua);
380
369
  const browser = uaParts.browser;
381
370
 
382
- if (!browser) return { quicAnomalyScore: 0.0 };
371
+ if (browser) {
372
+ // HTTP/2 Anomaly Logic
373
+ const h2Fp = context.headers?.['x-http2-fingerprint'] || context.http2Fingerprint || null;
374
+ if (h2Fp && typeof h2Fp === 'string') {
375
+ const parts = h2Fp.split('|');
376
+ if (parts.length >= 4) {
377
+ const connWindow = parseInt(parts[1], 10);
378
+ const headerOrder = parts[3];
379
+ const isChromium = browser.startsWith('Chrome') || browser.startsWith('Edge');
380
+ const isFirefox = browser.startsWith('Firefox');
381
+ const isSafari = browser.startsWith('Safari');
382
+
383
+ if (isChromium) {
384
+ if (headerOrder && headerOrder !== 'm,a,s,p') http2Anomaly += 60.0;
385
+ if (connWindow === 65535 || connWindow === 65536) http2Anomaly += 40.0;
386
+ } else if (isFirefox) {
387
+ if (headerOrder && headerOrder !== 'm,s,p,a') http2Anomaly += 60.0;
388
+ } else if (isSafari) {
389
+ if (headerOrder && headerOrder !== 'm,s,p,a') http2Anomaly += 60.0;
390
+ }
391
+ }
392
+ }
383
393
 
384
- let anomaly = 0.0;
385
- if (browser.startsWith('Chrome') || browser.startsWith('Edge')) {
386
- const maxData = parseInt(params['1'] || '0', 10);
387
- const maxStreams = parseInt(params['4'] || '0', 10);
388
- if (maxData > 0 && maxData < 1048576) anomaly += 40.0;
389
- if (maxStreams > 0 && maxStreams !== 100) anomaly += 30.0;
390
- if (priorityOrder && !priorityOrder.includes('u=')) anomaly += 30.0;
391
- } else if (browser.startsWith('Firefox')) {
392
- const maxData = parseInt(params['1'] || '0', 10);
393
- if (maxData > 0 && maxData > 5000000) anomaly += 40.0;
394
+ // QUIC Anomaly Logic
395
+ const quicFp = context.headers?.['x-quic-fp'] || context.quicFingerprint || null;
396
+ if (quicFp && typeof quicFp === 'string') {
397
+ const parts = quicFp.split(';');
398
+ if (parts.length >= 2) {
399
+ const params = {};
400
+ parts[1].split(',').forEach(p => {
401
+ const kv = p.split('=');
402
+ if (kv.length === 2) params[kv[0]] = kv[1];
403
+ });
404
+ const priorityOrder = parts[2] || '';
405
+
406
+ if (browser.startsWith('Chrome') || browser.startsWith('Edge')) {
407
+ const maxData = parseInt(params['1'] || '0', 10);
408
+ const maxStreams = parseInt(params['4'] || '0', 10);
409
+ if (maxData > 0 && maxData < 1048576) quicAnomaly += 40.0;
410
+ if (maxStreams > 0 && maxStreams !== 100) quicAnomaly += 30.0;
411
+ if (priorityOrder && !priorityOrder.includes('u=')) quicAnomaly += 30.0;
412
+ } else if (browser.startsWith('Firefox')) {
413
+ const maxData = parseInt(params['1'] || '0', 10);
414
+ if (maxData > 0 && maxData > 5000000) quicAnomaly += 40.0;
415
+ }
416
+ }
417
+ }
394
418
  }
395
419
 
396
- return { quicAnomalyScore: Math.max(0.0, Math.min(100.0, anomaly)) };
420
+ return {
421
+ protocolAnomalyScore: Math.max(
422
+ 0.0,
423
+ Math.min(100.0, http2Anomaly),
424
+ Math.min(100.0, quicAnomaly)
425
+ )
426
+ };
397
427
  }
398
428
  /**
399
429
  * Détecte les anomalies de rendu (V-Sync, FPS, gigue) à partir des métriques d'affichage.
@@ -523,6 +553,39 @@ async function checkChallengeRateLimit(clientIp) {
523
553
  return true;
524
554
  }
525
555
 
556
+ /**
557
+ * Génère la page HTML de challenge GPU basée sur la trajectoire d'une carte logistique chaotique.
558
+ * @private
559
+ * @param {object} challengeDetails - Les détails du challenge.
560
+ * @returns {string}
561
+ */
562
+ function generateGpuChallengePage(challengeDetails) {
563
+ const { nonce, iterations, path } = challengeDetails;
564
+ const safePath = sanitizeRedirectPath(path);
565
+ const solverCode = getPowSolverCode();
566
+ return `
567
+ <html><head><title>GPU Hardware Verification</title></head>
568
+ <body style="font-family:sans-serif; text-align:center; padding-top:50px;">
569
+ <h1>Hardware Performance Check</h1>
570
+ <p>Please wait while we verify your graphics card's math precision to ensure you are a human...</p>
571
+ <div id="loader" style="margin:20px;">⚙️ Running parallel chaotic iterations...</div>
572
+ <script>${solverCode}</script>
573
+ <script type="module">
574
+ import { GpuPowSolver } from './gpu_pow.solver.js';
575
+ async function solve() {
576
+ const nonce = ${safeJsonStringify(nonce)};
577
+ const iterations = ${iterations};
578
+ try {
579
+ const result = await GpuPowSolver.solve(nonce, iterations);
580
+ window.location.href = ${safeJsonStringify(safePath)} + "?pow_type=gpu&pow_nonce=" + nonce + "&pow_solution=" + result.solution;
581
+ } catch(e) {
582
+ document.getElementById('loader').innerText = "GPU hardware acceleration is required to complete this verification.";
583
+ }
584
+ }
585
+ solve();
586
+ </script>
587
+ </body></html>`;
588
+ }
526
589
 
527
590
  const securityProfiles = {
528
591
  /**
@@ -546,6 +609,7 @@ const securityProfiles = {
546
609
  botnetClusterScore: 0.6, // NOUVEAU: Poids pour le clustering botnet
547
610
  tcpAnomalyScore: 0.8, // NEW: Anomalie de pile TCP/IP
548
611
  quicAnomalyScore: 0.8, // NOUVEAU: Poids pour l'anomalie QUIC
612
+ protocolAnomalyScore: 0.8, // NOUVEAU: Poids pour l'anomalie HTTP/2
549
613
  renderingAnomalyScore: 0.8, // NOUVEAU: Poids pour l'anomalie de rendu
550
614
  threatIntelScore: 1.0, // NOUVEAU: Poids pour le réseau de Threat Intelligence Fédéré
551
615
  },
@@ -563,9 +627,10 @@ const securityProfiles = {
563
627
  inactivityReset: 5000,
564
628
  },
565
629
  allowCrossNetworkRoaming: true, // Profil balancé : tolérant par défaut
566
- wasm: true,
567
- filterWhitelist: 85.0, // Stratégie d'inspection modérée pour les IP/chemins en liste blanche
630
+ wasm: true,
631
+ filterWhitelist: 85.0, // Stratégie d'inspection modérée pour les IP/chemins en liste blanche
568
632
  useAsymmetricTickets: true,
633
+ enableGpuPow: true, // Activer le challenge GPU
569
634
  },
570
635
  /**
571
636
  * @summary **Strict Profile**
@@ -587,6 +652,7 @@ const securityProfiles = {
587
652
  ipReputationScore: 0.6, // NOUVEAU: Poids pour la réputation IP
588
653
  botnetClusterScore: 0.8, // NOUVEAU: Poids pour le clustering botnet
589
654
  renderingAnomalyScore: 1.0, // NOUVEAU: Poids pour l'anomalie de rendu
655
+ protocolAnomalyScore: 1.0, // NOUVEAU
590
656
  threatIntelScore: 1.0, // NOUVEAU: Poids pour le réseau de Threat Intelligence Fédéré
591
657
  },
592
658
  thresholds: { low: 10, medium: 35, high: 65, block: 90 },
@@ -607,6 +673,7 @@ const securityProfiles = {
607
673
  filterWhitelist: true, // Tout comportement d'attaque certain bypass immédiatement la liste blanche
608
674
  wasm: true,
609
675
  useAsymmetricTickets: true,
676
+ enableGpuPow: true,
610
677
  },
611
678
  /**
612
679
  * @summary **API Profile**
@@ -628,6 +695,7 @@ const securityProfiles = {
628
695
  ipReputationScore: 0.5, // NOUVEAU: Poids pour la réputation IP
629
696
  botnetClusterScore: 0.7, // NOUVEAU: Poids pour le clustering botnet
630
697
  tcpAnomalyScore: 0.8, // NEW: Anomalie de pile TCP/IP
698
+ protocolAnomalyScore: 0.8,
631
699
  quicAnomalyScore: 0.8 // NOUVEAU: Poids pour l'anomalie QUIC
632
700
  },
633
701
  thresholds: { low: 25, medium: 50, high: 80, block: 95 },
@@ -675,6 +743,7 @@ const securityProfiles = {
675
743
  ipReputationScore: 0.3, // NOUVEAU: Poids pour la réputation IP
676
744
  botnetClusterScore: 0.5, // NOUVEAU: Poids pour le clustering botnet
677
745
  tcpAnomalyScore: 0.5, // NEW: Anomalie de pile TCP/IP
746
+ protocolAnomalyScore: 0.5,
678
747
  quicAnomalyScore: 0.5, // NOUVEAU: Poids pour l'anomalie QUIC
679
748
  renderingAnomalyScore: 0.5, // NOUVEAU: Poids pour l'anomalie de rendu
680
749
  },
@@ -722,6 +791,7 @@ const securityProfiles = {
722
791
  ipReputationScore: 0.6, // NOUVEAU: Poids pour la réputation IP
723
792
  botnetClusterScore: 0.9, // NOUVEAU: Poids pour le clustering botnet
724
793
  tcpAnomalyScore: 0.9, // NEW: Anomalie de pile TCP/IP
794
+ protocolAnomalyScore: 0.9,
725
795
  quicAnomalyScore: 0.9, // NOUVEAU: Poids pour l'anomalie QUIC
726
796
  renderingAnomalyScore: 0.9, // NOUVEAU: Poids pour l'anomalie de rendu,threatIntelScore: 1.0, // NOUVEAU: Poids pour le réseau de Threat Intelligence Fédéré
727
797
  },
@@ -2298,6 +2368,7 @@ function getTimeInconsistencyScore(context, metrics, deviceData = null) {
2298
2368
  function getCrossLayerInconsistency(context) {
2299
2369
  try {
2300
2370
  const clientFpString = context.headers['x-device-fingerprint'];
2371
+ const h2Fingerprint = context.headers['x-http2-fingerprint'] || context.http2Fingerprint || null;
2301
2372
  if (!clientFpString) return { crossLayerInconsistencyScore: 0 };
2302
2373
 
2303
2374
  const clientFpMap = new Map(clientFpString.split("|").map(part => part.split(":")));
@@ -2362,6 +2433,40 @@ function getCrossLayerInconsistency(context) {
2362
2433
  }
2363
2434
  }
2364
2435
 
2436
+ // 4. Incohérence des paramètres HTTP/2 (SETTINGS & WINDOW_UPDATE)
2437
+ // Les bots de spoofing TLS oublient souvent de modifier la signature de la couche HTTP/2
2438
+ if (h2Fingerprint && serverOsParts.browser) {
2439
+ const claimedBrowser = serverOsParts.browser.split('/')[0];
2440
+
2441
+ // Profils de paramètres HTTP/2 Settings attendus (Format standard type Akamai)
2442
+ // Chrome :SETTINGS_HEADER_TABLE_SIZE=65536, SETTINGS_MAX_CONCURRENT_STREAMS=1000, etc.
2443
+ const isChromium = ['Chrome', 'Edge'].includes(claimedBrowser);
2444
+ const isFirefox = claimedBrowser === 'Firefox';
2445
+
2446
+ if (isChromium) {
2447
+ // Chrome envoie typiquement : "1:65536;3:1000;4:6291456;6:65536" ou similaire
2448
+ const hasChromiumSettings = h2Fingerprint.includes('1:65536') && h2Fingerprint.includes('4:6291456');
2449
+ const isGoDefaultH2 = h2Fingerprint.includes('3:100') && h2Fingerprint.includes('4:1048576'); // Signature Go net/http par défaut
2450
+
2451
+ if (isGoDefaultH2) {
2452
+ score += 90; // Très forte suspicion d'un bot Go (tls-client) usurpant Chrome
2453
+ } else if (!hasChromiumSettings) {
2454
+ score += 40; // Anomalie de configuration HTTP/2
2455
+ }
2456
+ } else if (isFirefox) {
2457
+ // Firefox utilise des valeurs de fenêtres initiales et de paramètres différentes
2458
+ const isPythonH2 = h2Fingerprint.includes('1:4096') && h2Fingerprint.includes('4:65536'); // Signature hyper-générique type Python hyper/h2
2459
+ if (isPythonH2) {
2460
+ score += 90; // Bot Python usurpant Firefox
2461
+ }
2462
+ }
2463
+ }
2464
+
2465
+ // 5. Corrélation stricte OS Réseau (TCP/IP) vs OS Applicatif (User-Agent)
2466
+ if (context.tcpAnomalyScore && context.tcpAnomalyScore > 70) {
2467
+ score += 40; // Augmente drastiquement la pénalité si l'OS réseau ne correspond pas à l'OS applicatif
2468
+ }
2469
+
2365
2470
  return { crossLayerInconsistencyScore: Math.min(100, score) };
2366
2471
  } catch (e) {
2367
2472
  return { crossLayerInconsistencyScore: 10 }; // Erreur de parsing = suspect.
@@ -2791,6 +2896,58 @@ function getIpSubnet(ip, ipv4Prefix = 24, ipv6Prefix = 48) { // eslint-disable-l
2791
2896
  return null;
2792
2897
  }
2793
2898
 
2899
+ /**
2900
+ * Applies temporal decay (half-life of 30 minutes) to subnet metrics.
2901
+ * @private
2902
+ * @param {object} subnetData The subnet data.
2903
+ * @param {number} now The current timestamp.
2904
+ * @returns {object} The decayed subnet data.
2905
+ */
2906
+ function decaySubnetData(subnetData, now) {
2907
+ const inactivityMs = now - (subnetData.lastActivity || now);
2908
+ const halfLives = Math.floor(inactivityMs / (30 * 60 * 1000));
2909
+
2910
+ if (halfLives > 0) {
2911
+ const decay = Math.pow(2, halfLives);
2912
+ subnetData.highScoreCount = Math.max(0, Math.floor((subnetData.highScoreCount || 0) / decay));
2913
+
2914
+ if (subnetData.highScoreDevices) {
2915
+ for (const fpId in subnetData.highScoreDevices) {
2916
+ const decayedVal = Math.floor(subnetData.highScoreDevices[fpId] / decay);
2917
+ if (decayedVal <= 0) {
2918
+ delete subnetData.highScoreDevices[fpId];
2919
+ } else {
2920
+ subnetData.highScoreDevices[fpId] = decayedVal;
2921
+ }
2922
+ }
2923
+ }
2924
+
2925
+ if (subnetData.deviceIds) {
2926
+ const newLen = Math.max(0, Math.floor(subnetData.deviceIds.length / decay));
2927
+ subnetData.deviceIds = subnetData.deviceIds.slice(0, newLen);
2928
+ }
2929
+
2930
+ if (subnetData.ips) {
2931
+ const currentLen = subnetData.ips instanceof Set ? subnetData.ips.size : (subnetData.ips.length || 0);
2932
+ const newLen = Math.max(0, Math.floor(currentLen / decay));
2933
+ if (subnetData.ips instanceof Set) {
2934
+ const arr = Array.from(subnetData.ips).slice(0, newLen);
2935
+ subnetData.ips = new Set(arr);
2936
+ } else {
2937
+ subnetData.ips = subnetData.ips.slice(0, newLen);
2938
+ }
2939
+ }
2940
+
2941
+ if (subnetData.uas) {
2942
+ const newLen = Math.max(0, Math.floor(subnetData.uas.length / decay));
2943
+ subnetData.uas = subnetData.uas.slice(0, newLen);
2944
+ }
2945
+
2946
+ subnetData.lastActivity = now - (inactivityMs % (30 * 60 * 1000));
2947
+ }
2948
+ return subnetData;
2949
+ }
2950
+
2794
2951
  /**
2795
2952
  * Updates aggregated metrics for an IP subnet.
2796
2953
  * @param {object} context The request context.
@@ -2821,12 +2978,15 @@ async function updateSubnetMetrics(context, deviceId, finalScore) {
2821
2978
  }
2822
2979
  if (!subnetData.uas) subnetData.uas = [];
2823
2980
 
2981
+ const now = Date.now();
2982
+ decaySubnetData(subnetData, now);
2983
+
2824
2984
  // Utilisation d'un identifiant d'appareil stable (fingerprint matériel) plutôt que l'ID de cookie volatil
2825
2985
  const currentDeviceHash = getCompositeDeviceHash(context);
2826
2986
  const stableFpId = cyrb53(extractStablePart(currentDeviceHash)).toString();
2827
2987
 
2828
2988
  const currentDeviceContributions = subnetData.highScoreDevices[stableFpId] || 0;
2829
- if (currentDeviceContributions < 5) {
2989
+ if (currentDeviceContributions < 1) {
2830
2990
  subnetData.highScoreDevices[stableFpId] = currentDeviceContributions + 1;
2831
2991
  subnetData.highScoreCount++;
2832
2992
  }
@@ -2844,7 +3004,7 @@ async function updateSubnetMetrics(context, deviceId, finalScore) {
2844
3004
  subnetData.uas.push(userAgent);
2845
3005
  }
2846
3006
 
2847
- subnetData.lastActivity = Date.now();
3007
+ subnetData.lastActivity = now;
2848
3008
 
2849
3009
  if (subnetData.deviceIds.length > 100) {
2850
3010
  const oldDeviceId = subnetData.deviceIds.shift();
@@ -2872,11 +3032,15 @@ async function getSubnetScore(context) {
2872
3032
  const subnetData = await store.get(`subnet:${subnet}`);
2873
3033
  if (!subnetData) return { subnetScore: 0 };
2874
3034
 
2875
- // Application d'une décroissance temporelle (demi-vie de 30 minutes)
2876
3035
  const now = Date.now();
2877
3036
  const inactivityMs = now - (subnetData.lastActivity || now);
2878
3037
  const halfLives = Math.floor(inactivityMs / (30 * 60 * 1000));
2879
3038
 
3039
+ if (halfLives > 0) {
3040
+ decaySubnetData(subnetData, now);
3041
+ await store.set(key, subnetData, 86400);
3042
+ }
3043
+
2880
3044
  let highScoreCount = subnetData.highScoreCount || 0;
2881
3045
  let deviceCount = subnetData.deviceIds ? subnetData.deviceIds.length : 0;
2882
3046
  let ipCount = 1;
@@ -2885,14 +3049,6 @@ async function getSubnetScore(context) {
2885
3049
  }
2886
3050
  let uaCount = subnetData.uas ? subnetData.uas.length : 1;
2887
3051
 
2888
- if (halfLives > 0) {
2889
- const decay = Math.pow(2, halfLives);
2890
- highScoreCount = Math.max(0, Math.floor(highScoreCount / decay));
2891
- deviceCount = Math.max(0, Math.floor(deviceCount / decay));
2892
- ipCount = Math.max(1, Math.floor(ipCount / decay));
2893
- uaCount = Math.max(1, Math.floor(uaCount / decay));
2894
- }
2895
-
2896
3052
  if (deviceCount === 0) {
2897
3053
  return { subnetScore: 0.0 };
2898
3054
  }
@@ -3483,8 +3639,8 @@ export const getSuspicionVector = async (context, securityConfig) => {
3483
3639
  const { requestPatternScore } = getRequestPatternScore(context, deviceData, securityConfig.patterns);
3484
3640
 
3485
3641
  const { tcpAnomalyScore } = getTcpAnomalyScore(context);
3486
- const { quicAnomalyScore } = getQuicAnomalyScore(context);
3487
- const { renderingAnomalyScore } = getRenderingAnomalyScore(context);
3642
+ const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
3643
+ const { renderingAnomalyScore } = getRenderingAnomalyScore(context);
3488
3644
 
3489
3645
  // Save the updated device state to the store
3490
3646
  // Note: deviceData.ips is a Set, which may not serialize correctly in all stores (e.g., JSON). A Redis store should handle this via custom serialization or by converting to an array.
@@ -3496,7 +3652,7 @@ export const getSuspicionVector = async (context, securityConfig) => {
3496
3652
  deviceData.ips = new Set(deviceData.ips);
3497
3653
  }
3498
3654
  // Le vecteur de suspicion est maintenant complet.
3499
- return { ...behavioral, headerAnomalyScore, inconsistencyScore, behaviorScore, honeypotScore, botScore, requestPatternScore, crossLayerInconsistencyScore, timeInconsistencyScore, tlsSpoofingScore, clickVarianceScore, clientHintsInconsistencyScore, subnetScore, ipReputationScore, botnetClusterScore, tcpAnomalyScore, quicAnomalyScore, renderingAnomalyScore, threatIntelScore };
3655
+ return { ...behavioral, headerAnomalyScore, inconsistencyScore, behaviorScore, honeypotScore, botScore, requestPatternScore, crossLayerInconsistencyScore, timeInconsistencyScore, tlsSpoofingScore, clickVarianceScore, clientHintsInconsistencyScore, subnetScore, ipReputationScore, botnetClusterScore, tcpAnomalyScore, protocolAnomalyScore, renderingAnomalyScore, threatIntelScore };
3500
3656
  };
3501
3657
 
3502
3658
  // A residential user can change networks (home, 4G, public wifi).
@@ -3997,7 +4153,7 @@ export class FingerprintEngine {
3997
4153
  'trustedProxies',
3998
4154
  'wasm',
3999
4155
  'similarityThreshold', 'reset',
4000
- 'ed25519_private_key', 'ed25519_public_key',
4156
+ 'ed25519_private_key', 'ed25519_public_key', 'upowModel',
4001
4157
  'federatedPeers', 'federationSecret', 'filterWhitelist'
4002
4158
  ]);
4003
4159
 
@@ -4058,6 +4214,7 @@ export class FingerprintEngine {
4058
4214
  (suspicionVector.ipReputationScore || 0) * (weights.ipReputationScore || 0) +
4059
4215
  (suspicionVector.tcpAnomalyScore || 0) * (weights.tcpAnomalyScore || 0) +
4060
4216
  (suspicionVector.quicAnomalyScore || 0) * (weights.quicAnomalyScore || 0) + // NOUVEAU: QUIC Anomaly
4217
+ (suspicionVector.http2AnomalyScore || 0) * (weights.http2AnomalyScore || 0) + // NOUVEAU: HTTP/2 Anomaly
4061
4218
  (suspicionVector.threatIntelScore || 0) * (weights.threatIntelScore || 0) + // NOUVEAU: QUIC Anomaly
4062
4219
  (suspicionVector.renderingAnomalyScore || 0) * (weights.renderingAnomalyScore || 0); // NOUVEAU: Rendering Anomaly
4063
4220
 
@@ -4491,7 +4648,7 @@ export class FingerprintEngine {
4491
4648
  const blockThreshold = thresholds.block ?? 95;
4492
4649
 
4493
4650
  // Mettre à jour les métriques du sous-réseau après le calcul du score final
4494
- if (finalScore > (thresholds.low ?? 20) && finalScore < blockThreshold) {
4651
+ if (finalScore >= (thresholds.medium ?? 45) && finalScore < blockThreshold) {
4495
4652
  await __internal.updateSubnetMetrics(requestContext, deviceId, finalScore);
4496
4653
  }
4497
4654
 
@@ -4619,6 +4776,18 @@ export class FingerprintEngine {
4619
4776
  deviceHash: currentDeviceHash
4620
4777
  });
4621
4778
  }
4779
+ } else if (pow_type === "gpu" && pow_solution) {
4780
+ const isGpuValid = GpuPowSolver.verify(pow_nonce, challengeContext.iterations || 200000, pow_solution, clientIp, getPowSecret());
4781
+ isValid = isGpuValid;
4782
+ if (isValid) {
4783
+ const ttl = finalTtl || 3600000;
4784
+ ticket = generateStatelessTicket({
4785
+ expiry: Date.now() + ttl,
4786
+ originalIp: clientIp,
4787
+ deviceId,
4788
+ deviceHash: currentDeviceHash
4789
+ });
4790
+ }
4622
4791
  }
4623
4792
  }
4624
4793
  } else {
@@ -5082,6 +5251,28 @@ export class FingerprintEngine {
5082
5251
  }
5083
5252
  return decision;
5084
5253
  }
5254
+
5255
+ // NOUVEAU : Challenge matériel GPU (Logistic Map floating point parity check)
5256
+ const shouldUseGpu = this.securityConfig.enableGpuPow && finalScore >= thresholds.high;
5257
+ if (shouldUseGpu) {
5258
+ const iterations = this.securityConfig.gpuPowIterations || 200000;
5259
+ await store.set(`secret:${nonce}`, {
5260
+ clientSecret,
5261
+ iterations,
5262
+ fingerprint: originalFingerprint,
5263
+ originalPath: path
5264
+ }, this.securityConfig.challengeTtl || 300);
5265
+
5266
+ if (isApi) {
5267
+ decision.body = {
5268
+ challenge: { type: "gpu", nonce, clientSecret, iterations }
5269
+ };
5270
+ } else {
5271
+ decision.body = generateGpuChallengePage({ nonce, iterations, path });
5272
+ }
5273
+ return decision;
5274
+ }
5275
+
5085
5276
  // Generate some trap URLs to embed in the challenge page.
5086
5277
  // These links are visually hidden but present in the DOM to trap bots.
5087
5278
  const trapUrls = Array.from({ length: 3 }, () => generateTrapUrl(nonce)); // Génère les URL
@@ -6303,9 +6494,9 @@ export const __internal = {
6303
6494
  parseTcpSyn, // Expose for testing
6304
6495
  classifyTcpOs, // Expose for testing
6305
6496
  getTcpAnomalyScore, // Expose for testing,
6306
- getQuicAnomalyScore, // NOUVEAU: Expose pour les tests
6307
6497
  getRenderingAnomalyScore, // NOUVEAU: Expose pour les tests
6308
6498
  dnsCircuitBreaker,
6499
+ getProtocolAnomalyScore,
6309
6500
  recordDnsSuccess,
6310
6501
  recordDnsFailure,
6311
6502
  canAttemptDns,
@@ -156,7 +156,7 @@ export class GpuPowSolver {
156
156
 
157
157
  const inputData = new Float32Array(64);
158
158
  for (let i = 0; i < 64; i++) {
159
- inputData[i] = seed + (i * 0.015);
159
+ inputData[i] = (seed + (i * 0.015)) % 1;
160
160
  }
161
161
 
162
162
  const gpuBuffer = device.createBuffer({
@@ -210,7 +210,8 @@ export class GpuPowSolver {
210
210
  uniform int uIterations;
211
211
  void main() {
212
212
  float index = gl_FragCoord.x + (gl_FragCoord.y * 8.0);
213
- float x = uSeed + (index * 0.015);
213
+ float x = mod(uSeed + (index * 0.015), 1.0);
214
+ float x = mod(uSeed + (index * 0.015), 1.0);
214
215
  float r = 3.9999;
215
216
  for(int i = 0; i < uIterations; i++) {
216
217
  x = r * x * (1.0 - x);
@@ -53,6 +53,44 @@ function generateBlock(seed, blockIndex, blockSize = 1024) {
53
53
  return block;
54
54
  }
55
55
 
56
+ async function loadTfjs() {
57
+ if (typeof tf !== 'undefined') return tf;
58
+ if (typeof window !== 'undefined') {
59
+ if (window.tf) return window.tf;
60
+ return new Promise((resolve, reject) => {
61
+ const script = document.createElement('script');
62
+ script.src = 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js';
63
+ script.onload = () => resolve(window.tf);
64
+ script.onerror = () => reject(new Error('Failed to load TensorFlow.js'));
65
+ document.head.appendChild(script);
66
+ });
67
+ }
68
+ try {
69
+ return await import('@tensorflow/tfjs');
70
+ } catch (e) {
71
+ throw new Error("TensorFlow.js is not available.");
72
+ }
73
+ }
74
+
75
+ async function loadOnnxRuntime() {
76
+ if (typeof ort !== 'undefined') return ort;
77
+ if (typeof window !== 'undefined') {
78
+ if (window.ort) return window.ort;
79
+ return new Promise((resolve, reject) => {
80
+ const script = document.createElement('script');
81
+ script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
82
+ script.onload = () => resolve(window.ort);
83
+ script.onerror = () => reject(new Error('Failed to load ONNX Runtime Web'));
84
+ document.head.appendChild(script);
85
+ });
86
+ }
87
+ try {
88
+ return await import('onnxruntime-node');
89
+ } catch (e) {
90
+ throw new Error("ONNX Runtime is not available.");
91
+ }
92
+ }
93
+
56
94
  async function initializeSpace(seed, sizeMb) {
57
95
  const db = await openDb();
58
96
  const transaction = db.transaction("blocks", "readwrite");
@@ -491,8 +529,8 @@ async function solveUsefulWorkTask(task) {
491
529
  const i = Math.floor(secureRandom() * numFacilities);
492
530
  const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
493
531
  const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
494
- newFacilities[i].x = Math.max(bounds.minX, min(bounds.maxX, newFacilities[i].x + moveX));
495
- newFacilities[i].y = Math.max(bounds.minY, min(bounds.maxY, newFacilities[i].y + moveY));
532
+ newFacilities[i].x = Math.max(bounds.minX, Math.min(bounds.maxX, newFacilities[i].x + moveX));
533
+ newFacilities[i].y = Math.max(bounds.minY, Math.min(bounds.maxY, newFacilities[i].y + moveY));
496
534
  return newFacilities;
497
535
  };
498
536
  const initialSolution = task.initialSolution || Array.from({ length: numFacilities }, () => ({
@@ -557,6 +595,94 @@ async function solveUsefulWorkTask(task) {
557
595
  return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
558
596
  }
559
597
 
598
+ case 'pytorch_onnx_learning': {
599
+ const { weights, payload, modelPath } = task;
600
+ const inputs = payload.inputs || [];
601
+ const labels = payload.labels || [];
602
+
603
+ try {
604
+ const ort = await loadOnnxRuntime();
605
+ const session = await ort.InferenceSession.create(modelPath);
606
+ const inputName = session.inputNames[0];
607
+ const outputName = session.outputNames[0];
608
+
609
+ const dims = [inputs.length, inputs[0].length];
610
+ const inputTensor = new ort.Tensor('float32', Float32Array.from(inputs.flat()), dims);
611
+ const results = await session.run({ [inputName]: inputTensor });
612
+ const outputTensor = results[outputName];
613
+ const predictions = Array.from(outputTensor.data);
614
+
615
+ const gradients = new Array(weights.length).fill(0);
616
+ for (let i = 0; i < inputs.length; i++) {
617
+ const x = inputs[i];
618
+ const y = labels[i];
619
+ const pred = predictions[i];
620
+ const error = pred - y;
621
+ for (let j = 0; j < weights.length; j++) {
622
+ gradients[j] += error * x[j] / inputs.length;
623
+ }
624
+ }
625
+ return { gradients, predictions };
626
+ } catch (err) {
627
+ console.warn('[Useful Work Solver] Real ONNX loading failed, using fallback simulation:', err);
628
+ const gradients = new Array(weights.length).fill(0);
629
+ for (let i = 0; i < inputs.length; i++) {
630
+ const x = inputs[i];
631
+ const y = labels[i];
632
+ let pred = 0;
633
+ for (let j = 0; j < weights.length; j++) {
634
+ pred += x[j] * weights[j];
635
+ }
636
+ const error = pred - y;
637
+ for (let j = 0; j < weights.length; j++) {
638
+ gradients[j] += error * x[j] / inputs.length;
639
+ }
640
+ }
641
+ return { gradients };
642
+ }
643
+ }
644
+
645
+ case 'tfjs_learning': {
646
+ const { weights, payload, modelPath } = task;
647
+ const inputs = payload.inputs || [];
648
+ const labels = payload.labels || [];
649
+ try {
650
+ const tf = await loadTfjs();
651
+ const model = await tf.loadLayersModel(modelPath);
652
+
653
+ const xs = tf.tensor2d(inputs);
654
+ const ys = tf.tensor2d(labels);
655
+
656
+ const trainableVars = model.trainableWeights.map(w => w.read());
657
+ const lossFn = () => {
658
+ const preds = model.predict(xs);
659
+ return tf.losses.meanSquaredError(ys, preds);
660
+ };
661
+
662
+ const gFn = tf.grad(lossFn);
663
+ const grads = gFn(trainableVars);
664
+
665
+ const flatGradients = [];
666
+ const arr = await grads.array();
667
+ flatGradients.push(...arr.flat());
668
+
669
+ tf.dispose([xs, ys, ...grads]);
670
+ return { gradients: flatGradients };
671
+ } catch (err) {
672
+ console.warn('[Useful Work Solver] Real TFJS loading failed, using fallback simulation:', err);
673
+ const gradients = new Array(weights.length).fill(0);
674
+ for (let i = 0; i < inputs.length; i++) {
675
+ const x = inputs[i];
676
+ const y = labels[i][0] !== undefined ? labels[i][0] : labels[i];
677
+ let pred = 0;
678
+ for (let j = 0; j < weights.length; j++) pred += x[j] * weights[j];
679
+ const error = pred - y;
680
+ for (let j = 0; j < weights.length; j++) gradients[j] += error * x[j] / inputs.length;
681
+ }
682
+ return { gradients };
683
+ }
684
+ }
685
+
560
686
  default:
561
687
  throw new Error(`Unknown useful work type: ${task.type}`);
562
688
  }