@anonympins/fingerprint 0.5.4 → 0.5.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.
@@ -1,6 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import {BlockList, isIPv4, isIPv6} from "node:net";
3
3
  import * as dns from "node:dns/promises";
4
+ import {Worker} from "node:worker_threads";
4
5
  import {getProblemManager, problemManager} from "./problem-manager.js";
5
6
  import {Optimization} from "./library.js";
6
7
  import {cyrb53, FingerprintBuilder} from "./fingerprint.builder.js";
@@ -123,7 +124,7 @@ const yandexEntries = loadBotWhitelist('yandex.json', [
123
124
  ]);
124
125
 
125
126
  function generateSessionMapping() {
126
- const randomStr = (len = 6) => crypto.randomBytes(len).toString('hex').replace(/[0-9]/g, 'g').substring(0, len);
127
+ const randomStr = (len = 6) => Array.from({ length: len }, () => String.fromCharCode(crypto.randomInt(97, 123))).join('');
127
128
  const randomHeader = () => `X-Sess-${crypto.randomBytes(4).toString('hex')}`;
128
129
 
129
130
  return {
@@ -184,18 +185,29 @@ async function compilePolymorphicJs(mapping) {
184
185
  jsCode = jsCode.replace(regex2, `addRaw("${randKey}"`);
185
186
  }
186
187
 
187
- const obfuscationResult = JavaScriptObfuscator.obfuscate(jsCode, {
188
- compact: true,
189
- controlFlowFlattening: true,
190
- deadCodeInjection: true,
191
- stringArray: true,
192
- stringArrayRotate: true,
193
- stringArrayShuffle: true,
194
- seed: Math.abs(mapping.wasmConstants.seed),
195
- selfDefending: true,
188
+ return new Promise((resolve, reject) => {
189
+ const worker = new Worker(new URL('./obfuscation.worker.js', import.meta.url));
190
+ worker.on('message', (message) => {
191
+ if (message.error) {
192
+ console.error('[Fingerprint] Obfuscation worker error:', message.error, message.stack);
193
+ reject(new Error('Obfuscation failed in worker.'));
194
+ } else {
195
+ resolve(message.obfuscatedCode);
196
+ }
197
+ worker.terminate();
198
+ });
199
+ worker.on('error', (error) => {
200
+ console.error('[Fingerprint] Obfuscation worker crashed:', error);
201
+ reject(error);
202
+ });
203
+ worker.on('exit', (code) => {
204
+ if (code !== 0) {
205
+ console.error(`[Fingerprint] Obfuscation worker stopped with exit code ${code}`);
206
+ reject(new Error(`Obfuscation worker stopped with exit code ${code}`));
207
+ }
208
+ });
209
+ worker.postMessage({ jsCode, seed: mapping.wasmConstants.seed });
196
210
  });
197
-
198
- return obfuscationResult.getObfuscatedCode();
199
211
  }
200
212
 
201
213
  async function ensureLatestMapping() {
@@ -750,6 +762,54 @@ const cipherSuiteMap = {
750
762
  'TLS_RSA_WITH_3DES_EDE_CBC_SHA': 10,
751
763
  };
752
764
 
765
+ /**
766
+ * Valide cryptographiquement l'attestation/assertion WebAuthn émise par l'enclave sécurisée.
767
+ * @private
768
+ * @param {object} anchor Les données d'attestation WebAuthn reçues du client
769
+ * @param {object} deviceData Les données persistantes de l'appareil dans notre store
770
+ * @returns {boolean} True si la signature matérielle est valide
771
+ */
772
+ function verifyWebAuthnHardwareAnchor(anchor, deviceData) {
773
+ if (!anchor || !anchor.type) return false;
774
+
775
+ try {
776
+ const clientDataHash = crypto.createHash('sha256')
777
+ .update(Buffer.from(anchor.clientDataJSON, 'base64'))
778
+ .digest();
779
+
780
+ if (anchor.type === 'registration') {
781
+ if (!anchor.publicKey || !anchor.credentialId) return false;
782
+
783
+ // Enregistrement initial : on stocke la clé publique matérielle SPKI
784
+ deviceData.webauthnPublicKey = anchor.publicKey;
785
+ deviceData.webauthnCredentialId = anchor.credentialId;
786
+ return true;
787
+ } else if (anchor.type === 'assertion') {
788
+ const storedPublicKeyPem = deviceData.webauthnPublicKey;
789
+ if (!storedPublicKeyPem || deviceData.webauthnCredentialId !== anchor.credentialId) {
790
+ return false;
791
+ }
792
+
793
+ // Reconstitution du message signé (authenticatorData + clientDataHash)
794
+ const verifyBuffer = Buffer.concat([
795
+ Buffer.from(anchor.authenticatorData, 'base64'),
796
+ clientDataHash
797
+ ]);
798
+
799
+ const publicKey = crypto.createPublicKey(Buffer.from(storedPublicKeyPem, 'base64'));
800
+ return crypto.verify(
801
+ 'sha256',
802
+ verifyBuffer,
803
+ publicKey,
804
+ Buffer.from(anchor.signature, 'base64')
805
+ );
806
+ }
807
+ } catch (e) {
808
+ console.error('[WebAuthn-Server] Verification failed:', e.message);
809
+ }
810
+ return false;
811
+ }
812
+
753
813
  /**
754
814
  * Extracts TLS fingerprints (JA3 and JA4) from request context.
755
815
  * Prioritizes headers from reverse proxies (x-ja4-hash) and falls back to JA3 calculation
@@ -1964,10 +2024,29 @@ function getBehaviorScore(context) {
1964
2024
  * @param {object} metrics - Les métriques comportementales parsées depuis le client.
1965
2025
  * @returns {{timeInconsistencyScore: number}}
1966
2026
  */
1967
- function getTimeInconsistencyScore(context, metrics) {
2027
+ function getTimeInconsistencyScore(context, metrics, deviceData = null) {
1968
2028
  const REPLAY_THRESHOLD_MS = 5000; // 5 secondes
1969
2029
  let score = 0;
1970
2030
 
2031
+ if (deviceData && deviceData.sessionHmacKey && metrics.signature) {
2032
+ try {
2033
+ const copy = JSON.parse(JSON.stringify(metrics));
2034
+ const clientSig = copy.signature;
2035
+ delete copy.signature;
2036
+ const dataToSign = JSON.stringify(copy);
2037
+ const expectedSig = crypto.createHmac('sha256', Buffer.from(deviceData.sessionHmacKey, 'hex'))
2038
+ .update(dataToSign)
2039
+ .digest('hex');
2040
+ if (!crypto.timingSafeEqual(Buffer.from(clientSig, 'hex'), Buffer.from(expectedSig, 'hex'))) {
2041
+ return { timeInconsistencyScore: 100 }; // Replay/tampering detected
2042
+ }
2043
+ } catch (e) {
2044
+ return { timeInconsistencyScore: 100 };
2045
+ }
2046
+ } else if (deviceData && deviceData.sessionHmacKey && !metrics.signature) {
2047
+ return { timeInconsistencyScore: 100 }; // Missing mandatory signature
2048
+ }
2049
+
1971
2050
  if (metrics.clientTimestamp && context.requestTimestamp) {
1972
2051
  const timeDelta = context.requestTimestamp - metrics.clientTimestamp;
1973
2052
 
@@ -2972,6 +3051,23 @@ async function getBehavioralIndicators(context, deviceData) {
2972
3051
  deviceData.lastFpHash = currentFpHash;
2973
3052
  deviceData.ips.add(clientIp); // Record the IP used by this device
2974
3053
 
3054
+ // --- VALIDATION DE L'ANCRAGE MATÉRIEL WEBAUTHN ---
3055
+ const behaviorHeader = context.headers?.['x-behavior-metrics'];
3056
+ let webauthnVerified = false;
3057
+ if (behaviorHeader) {
3058
+ try {
3059
+ const metrics = JSON.parse(behaviorHeader);
3060
+ if (metrics && metrics.webauthnAnchor) {
3061
+ if (verifyWebAuthnHardwareAnchor(metrics.webauthnAnchor, deviceData)) {
3062
+ webauthnVerified = true;
3063
+ deviceData.webauthnVerified = true;
3064
+ }
3065
+ }
3066
+ } catch (e) {
3067
+ // Ignorer les erreurs de parsing
3068
+ }
3069
+ }
3070
+
2975
3071
  // NOUVELLE LOGIQUE : Le score d'historique est basé sur le nombre d'IPs utilisées par l'appareil.
2976
3072
  // Très efficace contre la rotation de proxy.
2977
3073
  const maxIpsForDevice = isSharedIp
@@ -3015,7 +3111,6 @@ export const getSuspicionVector = async (context, securityConfig) => {
3015
3111
  context._newCookies = context._newCookies || [];
3016
3112
  context._newCookies.push(newCookie);
3017
3113
  }
3018
- await store.set(`ip-device:${clientIp}`, deviceId, 600); // Link the IP to the device for 10 minutes
3019
3114
 
3020
3115
  // Periodically clean up device data
3021
3116
  if (Date.now() - deviceData.lastUpdate > 10 * 60 * 1000) { // 10 minutes
@@ -3024,48 +3119,56 @@ export const getSuspicionVector = async (context, securityConfig) => {
3024
3119
  }
3025
3120
  deviceData.lastUpdate = Date.now();
3026
3121
 
3027
- const behavioral = await getBehavioralIndicators(context, deviceData);
3028
- const { headerAnomalyScore } = getHeaderAnomalies(context);
3029
- // Calculate the inconsistency score here, separately.
3030
- let inconsistencyScore = Math.min(100, Math.max(0, (1 - consistencyScore) * 200)); // Amplified score
3031
-
3032
- // NOUVEAU: Si l'incohérence est très forte (cookie probablement volé), on applique une pénalité maximale.
3033
- if (consistencyScore < 0.7) { // Seuil de rupture
3034
- inconsistencyScore = 100;
3035
- }
3036
-
3037
- const { behaviorScore } = getBehaviorScore(context); // Appel de la fonction
3038
-
3039
- // On appelle getHoneypotScore ici pour que son résultat soit inclus dans le vecteur.
3040
- const { honeypotScore } = getHoneypotScore(context, honeypotConfig);
3122
+ const stableFp = extractStablePart(currentDeviceHash);
3123
+ const stableFpHash = cyrb53(stableFp).toString();
3041
3124
 
3042
- const { tlsSpoofingScore } = await getTlsSpoofingScore(context);
3125
+ // Execute non-interdependent asynchronous operations in parallel
3126
+ const [
3127
+ behavioral,
3128
+ { tlsSpoofingScore },
3129
+ { subnetScore },
3130
+ ipReputationScore,
3131
+ { botnetClusterScore },
3132
+ _ // store.set result
3133
+ ] = await Promise.all([
3134
+ getBehavioralIndicators(context, deviceData),
3135
+ getTlsSpoofingScore(context),
3136
+ getSubnetScore(context, deviceId),
3137
+ getIpReputationScore(clientIp),
3138
+ getBotnetClusterScore(context, stableFpHash),
3139
+ store.set(`ip-device:${clientIp}`, deviceId, 600) // Link the IP to the device for 10 minutes
3140
+ ]);
3141
+
3142
+ // Synchronous calculations
3143
+ const { headerAnomalyScore } = getHeaderAnomalies(context);
3144
+ let inconsistencyScore = Math.min(100, Math.max(0, (1 - consistencyScore) * 200)); // Amplified score
3145
+
3146
+ // NOUVEAU: Si l'incohérence est très forte (cookie probablement volé), on applique une pénalité maximale.
3147
+ if (consistencyScore < 0.7) { // Seuil de rupture
3148
+ inconsistencyScore = 100;
3149
+ }
3043
3150
 
3044
- const { botScore } = getBotScore(context);
3151
+ const { behaviorScore } = getBehaviorScore(context); // Appel de la fonction
3045
3152
 
3046
- // NOUVEAU: On calcule le score d'incohérence temporelle.
3047
- const { timeInconsistencyScore } = getTimeInconsistencyScore(context, JSON.parse(context.headers['x-behavior-metrics'] || '{}'));
3153
+ // On appelle getHoneypotScore ici pour que son résultat soit inclus dans le vecteur.
3154
+ const { honeypotScore } = getHoneypotScore(context, honeypotConfig);
3048
3155
 
3049
- // NOUVEAU: On calcule le score d'incohérence entre les couches.
3050
- const { crossLayerInconsistencyScore } = getCrossLayerInconsistency(context);
3156
+ const { botScore } = getBotScore(context);
3051
3157
 
3052
- // NOUVEAU: On calcule le score de variance des clics.
3053
- const { clickVarianceScore } = getClickVarianceScore(context);
3158
+ // NOUVEAU: On calcule le score d'incohérence temporelle.
3159
+ const { timeInconsistencyScore } = getTimeInconsistencyScore(context, JSON.parse(context.headers['x-behavior-metrics'] || '{}'), deviceData);
3054
3160
 
3055
- // NOUVEAU: On calcule le score d'incohérence des Client-Hints.
3056
- const { clientHintsInconsistencyScore } = getClientHintsInconsistencyScore(context);
3161
+ // NOUVEAU: On calcule le score d'incohérence entre les couches.
3162
+ const { crossLayerInconsistencyScore } = getCrossLayerInconsistency(context);
3057
3163
 
3058
- // NOUVEAU: On calcule le score de réputation du sous-réseau.
3059
- // FIX: Pass the deviceId to getSubnetScore
3060
- const { subnetScore } = await getSubnetScore(context, deviceId);
3164
+ // NOUVEAU: On calcule le score de variance des clics.
3165
+ const { clickVarianceScore } = getClickVarianceScore(context);
3061
3166
 
3062
- const { requestPatternScore } = getRequestPatternScore(context, deviceData, securityConfig.patterns);
3167
+ // NOUVEAU: On calcule le score d'incohérence des Client-Hints.
3168
+ const { clientHintsInconsistencyScore } = getClientHintsInconsistencyScore(context);
3063
3169
 
3064
- const ipReputationScore = await getIpReputationScore(clientIp);
3170
+ const { requestPatternScore } = getRequestPatternScore(context, deviceData, securityConfig.patterns);
3065
3171
 
3066
- const stableFp = extractStablePart(currentDeviceHash);
3067
- const stableFpHash = cyrb53(stableFp).toString();
3068
- const { botnetClusterScore } = await getBotnetClusterScore(context, stableFpHash);
3069
3172
  const { tcpAnomalyScore } = getTcpAnomalyScore(context);
3070
3173
  const { quicAnomalyScore } = getQuicAnomalyScore(context);
3071
3174
  const { renderingAnomalyScore } = getRenderingAnomalyScore(context);
@@ -3204,7 +3307,40 @@ export function generateCpuTargetChallenge(
3204
3307
  };
3205
3308
  }
3206
3309
 
3207
- const htmlTemplateCache = new Map();
3310
+ class SimpleLRUCache {
3311
+ constructor(maxSize = 100) {
3312
+ this.maxSize = maxSize;
3313
+ this.cache = new Map();
3314
+ }
3315
+
3316
+ get(key) {
3317
+ if (!this.cache.has(key)) return undefined;
3318
+ const value = this.cache.get(key);
3319
+ this.cache.delete(key);
3320
+ this.cache.set(key, value);
3321
+ return value;
3322
+ }
3323
+
3324
+ set(key, value) {
3325
+ if (this.cache.has(key)) {
3326
+ this.cache.delete(key);
3327
+ } else if (this.cache.size >= this.maxSize) {
3328
+ const lruKey = this.cache.keys().next().value;
3329
+ this.cache.delete(lruKey);
3330
+ }
3331
+ this.cache.set(key, value);
3332
+ }
3333
+
3334
+ has(key) {
3335
+ return this.cache.has(key);
3336
+ }
3337
+
3338
+ clear() {
3339
+ this.cache.clear();
3340
+ }
3341
+ }
3342
+
3343
+ const htmlTemplateCache = new SimpleLRUCache(100);
3208
3344
 
3209
3345
  /**
3210
3346
  * Generates the HTML page for the CPU target challenge.
@@ -3812,13 +3948,19 @@ export class FingerprintEngine {
3812
3948
 
3813
3949
  this._log('Processing request', { clientIp, path, isStatic });
3814
3950
 
3951
+ // Bypass instantané si l'appareil a prouvé cryptographiquement son identité matérielle (Secure Enclave / TPM)
3952
+ const { deviceId, deviceData, newCookie } = await resolveRequestIdentity(requestContext, this.securityConfig);
3953
+ if (deviceData && deviceData.webauthnVerified) {
3954
+ this._log('Hardware-anchored device verified (WebAuthn) - full bypass granted', { deviceId });
3955
+ return { action: 'next', score: 0, vector: { webauthn_verified: 100 } };
3956
+ }
3957
+
3815
3958
  if (isStatic) {
3816
3959
  this._log('Static resource - skipping checks');
3817
3960
  return { action: 'next', score: 0, vector: {} };
3818
3961
  }
3819
3962
 
3820
3963
  // Resolve identity and check for persisted "condemned" status early.
3821
- const { deviceId, deviceData, newCookie } = await resolveRequestIdentity(requestContext, this.securityConfig);
3822
3964
  const currentDeviceHash = getCompositeDeviceHash(requestContext);
3823
3965
  const isNewDevice = !!newCookie;
3824
3966
  const allowRoaming = this.securityConfig?.allowCrossNetworkRoaming ?? false;
@@ -4237,17 +4379,22 @@ export class FingerprintEngine {
4237
4379
 
4238
4380
  const isBlocked = finalScore >= blockThreshold;
4239
4381
 
4240
- const isSuspiciousHigh = finalScore >= thresholds.high && !isBlocked;
4382
+ const isSuspiciousHigh = finalScore >= thresholds.high && !isBlocked && finalScore > 0;
4241
4383
  const isSuspiciousMedium = finalScore >= thresholds.medium;
4242
4384
  const isSuspicious = finalScore >= thresholds.low;
4243
4385
  const isVerySuspicious = finalScore >= thresholds.medium; // Seuil pour le challenge d'optimisation
4244
4386
 
4245
4387
  // Calculate an analog "suspicion factor" (0 to 1+) for progressive difficulty
4246
- const suspicionFactor = isSuspicious
4247
- ? Math.min(
4248
- 1.5, // On autorise un dépassement pour rendre les challenges très difficiles si le score est très élevé
4249
- (finalScore - thresholds.low) / (thresholds.high - thresholds.low),
4250
- )
4388
+ const suspicionFactor = isSuspicious // eslint-disable-line no-nested-ternary
4389
+ ? (() => {
4390
+ const denominator = thresholds.high - thresholds.low;
4391
+ if (denominator === 0) {
4392
+ // If the range is zero, and finalScore is at or above low threshold,
4393
+ // return 0 to avoid NaN.
4394
+ return 0;
4395
+ }
4396
+ return Math.min(1.5, (finalScore - thresholds.low) / denominator);
4397
+ })()
4251
4398
  : 0;
4252
4399
 
4253
4400
  this._log('Suspicion levels evaluated', {
@@ -5337,7 +5484,7 @@ function getTlsSessionId(context) {
5337
5484
  }
5338
5485
 
5339
5486
  // --- Proof-of-Work Middleware (The Tollbooth) ---
5340
- const staticFileCache = new Map();
5487
+ const staticFileCache = new SimpleLRUCache(50);
5341
5488
 
5342
5489
  export const powMiddleware = (securityConfig) => {
5343
5490
  const engine = new FingerprintEngine(securityConfig);
@@ -5369,6 +5516,36 @@ export const powMiddleware = (securityConfig) => {
5369
5516
  }
5370
5517
 
5371
5518
  return async (req, res, next) => {
5519
+ if (req.query && req.query.fp_handshake) {
5520
+ const clientKeyHex = req.headers['x-client-ephemeral-key'];
5521
+ if (clientKeyHex) {
5522
+ try {
5523
+ const serverECDH = crypto.createECDH('prime256v1');
5524
+ serverECDH.generateKeys();
5525
+ const serverPubKeyHex = serverECDH.getKeys('hex');
5526
+ const sharedSecret = serverECDH.computeSecret(clientKeyHex, 'hex');
5527
+ const hmacKey = crypto.createHash('sha256').update(sharedSecret).digest('hex');
5528
+
5529
+ const requestContext = {
5530
+ clientIp: req.ip || req.socket?.remoteAddress || "unknown",
5531
+ headers: req.headers,
5532
+ cookies: req.cookies
5533
+ };
5534
+ const { deviceId, deviceData } = await resolveRequestIdentity(requestContext, securityConfig);
5535
+ if (deviceData) {
5536
+ deviceData.sessionHmacKey = hmacKey;
5537
+ await store.set(`device:${deviceId}`, deviceData);
5538
+ }
5539
+
5540
+ res.setHeader('x-server-ephemeral-key', serverPubKeyHex);
5541
+ return res.status(200).json({ status: 'success' });
5542
+ } catch (e) {
5543
+ return res.status(400).json({ error: 'Handshake failed' });
5544
+ }
5545
+ }
5546
+ return res.status(400).json({ error: 'Missing client key' });
5547
+ }
5548
+
5372
5549
  if (!req.headers_translated) {
5373
5550
  req.headers_translated = true;
5374
5551
  const matchedMapping = getActiveMappingForRequest(req.headers);
@@ -5543,6 +5720,7 @@ export const __internal = {
5543
5720
  getTlsFingerprint, // NOUVEAU: Expose pour les tests
5544
5721
  sanitizeTrafficData, // NOUVEAU: Expose pour l'auto-tuner/tests
5545
5722
  getTlsSpoofingScore, // NOUVEAU: Expose pour les tests
5723
+ verifyWebAuthnHardwareAnchor,
5546
5724
  generateStatelessTicket,
5547
5725
  parseStatelessTicket,
5548
5726
  parseJa3,
@@ -5559,6 +5737,8 @@ export const __internal = {
5559
5737
  setLastBestSolution: (val) => { lastBestSolution = val; }, // Expose to test auto-tuning metrics
5560
5738
  verifyZkpProof,
5561
5739
  modPow,
5740
+ generateSessionMapping,
5741
+ compilePolymorphicJs,
5562
5742
  parseTcpSyn, // Expose for testing
5563
5743
  classifyTcpOs, // Expose for testing
5564
5744
  getTcpAnomalyScore, // Expose for testing,
@@ -5839,6 +6019,43 @@ function runThresholdOptimization(securityConfig, trafficData, minDataPoints, ma
5839
6019
  // Plus le ratio est équilibré et le volume important, plus nous faisons confiance au Front de Pareto.
5840
6020
  const trafficConfidence = Math.min(1.5, Math.max(0.3, highConfidenceRatio * 4));
5841
6021
 
6022
+ // --- VALIDATION POST-CALCUL (Anti-empoisonnement & Validation Croisée) ---
6023
+ const tempConfig = {
6024
+ thresholds: { ...securityConfig.thresholds },
6025
+ weights: { ...securityConfig.weights },
6026
+ patterns: { ...securityConfig.patterns }
6027
+ };
6028
+
6029
+ applyInertialUpdate(tempConfig.thresholds, newConfig.thresholds, 'thresholds', trafficConfidence);
6030
+ applyInertialUpdate(tempConfig.weights, newConfig.weights, 'weights', trafficConfidence);
6031
+ applyInertialUpdate(tempConfig.patterns, newConfig.patterns, 'patterns', trafficConfidence);
6032
+
6033
+ const fitnessFunction = Optimization.Operators.createFullSecurityConfigEvaluator({ trafficData: sanitizedData });
6034
+ const currentObjectives = fitnessFunction(securityConfig);
6035
+ const proposedObjectives = fitnessFunction(tempConfig);
6036
+
6037
+ const currentFPR = currentObjectives[0];
6038
+ const currentFNR = currentObjectives[1];
6039
+ const proposedFPR = proposedObjectives[0];
6040
+ const proposedFNR = proposedObjectives[1];
6041
+
6042
+ const validationTolerance = securityConfig?.autotuning?.validationTolerance ?? tuningOptions?.validationTolerance ?? 0.15;
6043
+
6044
+ if (proposedFPR > currentFPR + validationTolerance || proposedFNR > currentFNR + validationTolerance) {
6045
+ console.error(`[AutoTuning] [SECURITY ALERT] Proposed configuration rejected due to instability/poisoning risk! Proposed FPR: ${proposedFPR.toFixed(4)} (Current: ${currentFPR.toFixed(4)}), Proposed FNR: ${proposedFNR.toFixed(4)} (Current: ${currentFNR.toFixed(4)})`);
6046
+ if (securityConfig.logger && typeof securityConfig.logger === 'function') {
6047
+ securityConfig.logger({
6048
+ type: 'autotuning_instability_alert',
6049
+ proposedFPR,
6050
+ currentFPR,
6051
+ proposedFNR,
6052
+ currentFNR,
6053
+ timestamp: Date.now()
6054
+ });
6055
+ }
6056
+ return; // Rollback automatique : On arrête l'application
6057
+ }
6058
+
5842
6059
  applyInertialUpdate(securityConfig.thresholds, newConfig.thresholds, 'thresholds', trafficConfidence);
5843
6060
  applyInertialUpdate(securityConfig.weights, newConfig.weights, 'weights', trafficConfidence);
5844
6061
  applyInertialUpdate(securityConfig.patterns, newConfig.patterns, 'patterns', trafficConfidence);
@@ -0,0 +1,23 @@
1
+ import { parentPort } from 'node:worker_threads';
2
+ import JavaScriptObfuscator from 'javascript-obfuscator';
3
+
4
+ if (parentPort) {
5
+ parentPort.on('message', (workerData) => {
6
+ const { jsCode, seed } = workerData;
7
+ try {
8
+ const obfuscationResult = JavaScriptObfuscator.obfuscate(jsCode, {
9
+ compact: true,
10
+ controlFlowFlattening: true,
11
+ deadCodeInjection: true,
12
+ stringArray: true,
13
+ stringArrayRotate: true,
14
+ stringArrayShuffle: true,
15
+ seed: Math.abs(seed),
16
+ selfDefending: true,
17
+ });
18
+ parentPort.postMessage({ obfuscatedCode: obfuscationResult.getObfuscatedCode() });
19
+ } catch (error) {
20
+ parentPort.postMessage({ error: error.message, stack: error.stack });
21
+ }
22
+ });
23
+ }
@@ -6,6 +6,15 @@
6
6
 
7
7
  'use strict';
8
8
 
9
+ function secureRandom() {
10
+ if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
11
+ const array = new Uint32Array(1);
12
+ (window.crypto || window.msCrypto).getRandomValues(array);
13
+ return array[0] / 0x100000000;
14
+ }
15
+ return Math.random();
16
+ }
17
+
9
18
  function cyrb53(str, seed = 0) {
10
19
  const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
11
20
  let h1 = 0xdeadbeef ^ seed,
@@ -193,7 +202,7 @@ const ClientOptimizers = {
193
202
  for (let i = 0; i < iterations; i++) {
194
203
  const newSolution = neighbor(currentSolution);
195
204
  const newEnergy = evaluator(newSolution);
196
- if (newEnergy < currentEnergy || Math.random() < Math.exp((currentEnergy - newEnergy) / temperature)) {
205
+ if (newEnergy < currentEnergy || secureRandom() < Math.exp((currentEnergy - newEnergy) / temperature)) {
197
206
  currentSolution = newSolution;
198
207
  currentEnergy = newEnergy;
199
208
  }
@@ -212,10 +221,10 @@ const ClientOptimizers = {
212
221
  population.sort((a, b) => a.fitness - b.fitness);
213
222
  const newPopulation = [population[0]]; // Elitism
214
223
  while (newPopulation.length < popSize) {
215
- const p1 = population[Math.floor(Math.random() * (popSize / 2))];
216
- const p2 = population[Math.floor(Math.random() * (popSize / 2))];
224
+ const p1 = population[Math.floor(secureRandom() * (popSize / 2))];
225
+ const p2 = population[Math.floor(secureRandom() * (popSize / 2))];
217
226
  let offspring = crossover(p1.chromosome, p2.chromosome);
218
- if (Math.random() < 0.1) offspring = mutate(offspring);
227
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
219
228
  newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
220
229
  }
221
230
  population = newPopulation;
@@ -236,10 +245,10 @@ const ClientOptimizers = {
236
245
  for (let gen = 0; gen < generations; gen++) {
237
246
  const offspring = [];
238
247
  for (let i = 0; i < populationSize; i++) {
239
- const parent1 = population[Math.floor(Math.random() * population.length)];
240
- const parent2 = population[Math.floor(Math.random() * population.length)];
248
+ const parent1 = population[Math.floor(secureRandom() * population.length)];
249
+ const parent2 = population[Math.floor(secureRandom() * population.length)];
241
250
  let childIndividual = crossover(parent1.individual, parent2.individual);
242
- if (Math.random() < mutationRate) {
251
+ if (secureRandom() < mutationRate) {
243
252
  childIndividual = mutate(childIndividual);
244
253
  }
245
254
  const child = { individual: childIndividual };
@@ -307,9 +316,9 @@ const ClientOptimizers = {
307
316
  return [-platformRevenue, -advertiserValue, marketImbalance];
308
317
  };
309
318
 
310
- const createIndividual = () => 0.5 + Math.random() * 4.5;
319
+ const createIndividual = () => 0.5 + secureRandom() * 4.5;
311
320
  const crossover = (cpc1, cpc2) => (cpc1 + cpc2) / 2;
312
- const mutate = (cpc) => Math.max(0.1, cpc + (Math.random() - 0.5) * 0.5);
321
+ const mutate = (cpc) => Math.max(0.1, cpc + (secureRandom() - 0.5) * 0.5);
313
322
 
314
323
  const gaOptions = {
315
324
  generations: 50,
@@ -413,10 +422,10 @@ const ClientOptimizers = {
413
422
  };
414
423
 
415
424
  const createIndividual = () => [
416
- 100 + Math.random() * 4900,
417
- 1 + Math.random() * 9999,
418
- Math.random() * 0.5,
419
- Math.floor(Math.random() * 10)
425
+ 100 + secureRandom() * 4900,
426
+ 1 + secureRandom() * 9999,
427
+ secureRandom() * 0.5,
428
+ Math.floor(secureRandom() * 10)
420
429
  ];
421
430
 
422
431
  const crossover = (s1, s2) => [
@@ -430,7 +439,7 @@ const ClientOptimizers = {
430
439
  const newSolution = [...solution];
431
440
  const i = Math.floor(Math.random() * 4);
432
441
  const mutationFactors = [500, 1000, 0.1, 2];
433
- newSolution[i] += (Math.random() - 0.5) * mutationFactors[i];
442
+ newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
434
443
  return newSolution;
435
444
  };
436
445
 
@@ -479,16 +488,16 @@ async function solveUsefulWorkTask(task) {
479
488
  };
480
489
  const neighbor = (facilities) => {
481
490
  const newFacilities = facilities.map((f) => ({ ...f }));
482
- const i = Math.floor(Math.random() * numFacilities);
483
- const moveX = (Math.random() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
484
- const moveY = (Math.random() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
491
+ const i = Math.floor(secureRandom() * numFacilities);
492
+ const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
493
+ const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
485
494
  newFacilities[i].x = Math.max(bounds.minX, min(bounds.maxX, newFacilities[i].x + moveX));
486
495
  newFacilities[i].y = Math.max(bounds.minY, min(bounds.maxY, newFacilities[i].y + moveY));
487
496
  return newFacilities;
488
497
  };
489
498
  const initialSolution = task.initialSolution || Array.from({ length: numFacilities }, () => ({
490
- x: bounds.minX + Math.random() * (bounds.maxX - bounds.minX),
491
- y: bounds.minY + Math.random() * (bounds.maxY - bounds.minY),
499
+ x: bounds.minX + secureRandom() * (bounds.maxX - bounds.minX),
500
+ y: bounds.minY + secureRandom() * (bounds.maxY - bounds.minY),
492
501
  }));
493
502
  return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
494
503
  }
@@ -503,11 +512,11 @@ async function solveUsefulWorkTask(task) {
503
512
  };
504
513
  const neighbor = (path) => {
505
514
  const newPath = [...path];
506
- const [i, j] = [Math.floor(Math.random() * path.length), Math.floor(Math.random() * path.length)];
515
+ const [i, j] = [Math.floor(secureRandom() * path.length), Math.floor(secureRandom() * path.length)];
507
516
  [newPath[i], newPath[j]] = [newPath[j], newPath[i]];
508
517
  return newPath;
509
518
  };
510
- const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - Math.random());
519
+ const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - secureRandom());
511
520
 
512
521
  return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
513
522
  }
@@ -523,9 +532,9 @@ async function solveUsefulWorkTask(task) {
523
532
  if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
524
533
  return -ret;
525
534
  };
526
- const createIndividual = () => Array.from({ length: assets.length }, Math.random);
535
+ const createIndividual = () => Array.from({ length: assets.length }, secureRandom);
527
536
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
528
- const mutate = p => { const n = [...p], i = Math.floor(Math.random() * n.length); n[i] += (Math.random() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
537
+ const mutate = p => { const n = [...p], i = Math.floor(secureRandom() * n.length); n[i] += (secureRandom() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
529
538
 
530
539
  const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
531
540
  initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
@@ -557,8 +566,8 @@ async function solveOptimizationTask(initialPopulation, generations) {
557
566
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
558
567
  const mutate = (p) => {
559
568
  const newP = [...p];
560
- const i = Math.floor(Math.random() * newP.length);
561
- newP[i] += (Math.random() - 0.5) * 0.2;
569
+ const i = Math.floor(secureRandom() * newP.length);
570
+ newP[i] += (secureRandom() - 0.5) * 0.2;
562
571
  return newP;
563
572
  };
564
573
 
@@ -569,10 +578,10 @@ async function solveOptimizationTask(initialPopulation, generations) {
569
578
  const newPopulation = [...parents];
570
579
 
571
580
  while (newPopulation.length < population.length) {
572
- const parent1 = parents[Math.floor(Math.random() * parents.length)];
573
- const parent2 = parents[Math.floor(Math.random() * parents.length)];
581
+ const parent1 = parents[Math.floor(secureRandom() * parents.length)];
582
+ const parent2 = parents[Math.floor(secureRandom() * parents.length)];
574
583
  let offspring = crossover(parent1.chromosome, parent2.chromosome);
575
- if (Math.random() < 0.1) offspring = mutate(offspring);
584
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
576
585
  newPopulation.push({ chromosome: offspring, fitness: -1 });
577
586
  }
578
587
  population = newPopulation;