@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.
- package/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/src/js/build-client.js +67 -67
- package/src/js/fingerprint.client.js +1433 -1271
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +272 -55
- package/src/js/obfuscation.worker.js +23 -0
- package/src/js/pow.solver.inline.js +37 -28
- package/src/js/pow.solver.js +37 -28
- package/src/js/tests/cross_parity.test.js +175 -0
- package/src/js/tests/fingerprint.test.js +25 -0
- package/src/js/tests/obfuscation.test.js +48 -0
- package/src/php/AutoTuner.php +42 -0
- package/src/php/FingerprintEngine.php +1 -1
package/src/js/fingerprint.js
CHANGED
|
@@ -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) =>
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
3028
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
3151
|
+
const { behaviorScore } = getBehaviorScore(context); // Appel de la fonction
|
|
3045
3152
|
|
|
3046
|
-
|
|
3047
|
-
|
|
3153
|
+
// On appelle getHoneypotScore ici pour que son résultat soit inclus dans le vecteur.
|
|
3154
|
+
const { honeypotScore } = getHoneypotScore(context, honeypotConfig);
|
|
3048
3155
|
|
|
3049
|
-
|
|
3050
|
-
const { crossLayerInconsistencyScore } = getCrossLayerInconsistency(context);
|
|
3156
|
+
const { botScore } = getBotScore(context);
|
|
3051
3157
|
|
|
3052
|
-
|
|
3053
|
-
|
|
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
|
-
|
|
3056
|
-
|
|
3161
|
+
// NOUVEAU: On calcule le score d'incohérence entre les couches.
|
|
3162
|
+
const { crossLayerInconsistencyScore } = getCrossLayerInconsistency(context);
|
|
3057
3163
|
|
|
3058
|
-
|
|
3059
|
-
|
|
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
|
-
|
|
3167
|
+
// NOUVEAU: On calcule le score d'incohérence des Client-Hints.
|
|
3168
|
+
const { clientHintsInconsistencyScore } = getClientHintsInconsistencyScore(context);
|
|
3063
3169
|
|
|
3064
|
-
|
|
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
|
-
|
|
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
|
-
?
|
|
4248
|
-
|
|
4249
|
-
(
|
|
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
|
|
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 ||
|
|
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(
|
|
216
|
-
const p2 = population[Math.floor(
|
|
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 (
|
|
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(
|
|
240
|
-
const parent2 = population[Math.floor(
|
|
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 (
|
|
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 +
|
|
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 + (
|
|
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 +
|
|
417
|
-
1 +
|
|
418
|
-
|
|
419
|
-
Math.floor(
|
|
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] += (
|
|
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(
|
|
483
|
-
const moveX = (
|
|
484
|
-
const moveY = (
|
|
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 +
|
|
491
|
-
y: 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(
|
|
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 -
|
|
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 },
|
|
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(
|
|
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(
|
|
561
|
-
newP[i] += (
|
|
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(
|
|
573
|
-
const parent2 = parents[Math.floor(
|
|
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 (
|
|
584
|
+
if (secureRandom() < 0.1) offspring = mutate(offspring);
|
|
576
585
|
newPopulation.push({ chromosome: offspring, fitness: -1 });
|
|
577
586
|
}
|
|
578
587
|
population = newPopulation;
|