@anonympins/fingerprint 0.5.5 → 0.6.0
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 +7 -0
- package/README.md +7 -6
- package/package.json +1 -1
- package/src/js/fingerprint.client.js +17 -5
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +229 -63
- package/src/js/problem-manager.js +22 -0
- package/src/js/tests/fingerprint.client.test.js +130 -128
- package/src/js/tests/fingerprint.test.js +17 -2
- package/src/js/ttl-optimization.worker.js +51 -0
- package/src/php/Challenge/ChallengeUtils.php +57 -6
- package/src/php/FingerprintBuilder.php +193 -185
- package/src/php/FingerprintEngine.php +146 -0
- package/src/php/ProblemManager.php +20 -0
- package/src/php/Tests/ChallengeUtilsTest.php +446 -285
- package/src/php/Utils/RequestUtils.php +19 -0
package/src/js/fingerprint.js
CHANGED
|
@@ -769,6 +769,119 @@ const cipherSuiteMap = {
|
|
|
769
769
|
* @param {object} deviceData Les données persistantes de l'appareil dans notre store
|
|
770
770
|
* @returns {boolean} True si la signature matérielle est valide
|
|
771
771
|
*/
|
|
772
|
+
const TRUSTED_HARDWARE_ROOTS = [
|
|
773
|
+
"-----BEGIN CERTIFICATE-----\n" +
|
|
774
|
+
"MIIDHzCCAfegAwIBAgIJANCvWjvF+2O6MA0GCSqGSIb3DQEBCwUAMC0xKzApBgNV\n" +
|
|
775
|
+
"BAMTIll1YmljbyBBdHRlc3RhdGlvbiBSb290IENBMB4XDTE0MDgwNDAwMDAwMFox\n" +
|
|
776
|
+
"TSUxSDBGBgNVBAMMT1l1YmljbyBBdHRlc3RhdGlvbiBSb290IENBMSowKAYDVQQK\n" +
|
|
777
|
+
"EyFZdWJpY28gQUIxDzANBgNVBAcTBVN0b2NraG9sbTELMAkGA1UEBhMCU0UwggEi\n" +
|
|
778
|
+
"MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6XW0d87g+N6kGgSgC/H9UfA2p\n" +
|
|
779
|
+
"-----END CERTIFICATE-----",
|
|
780
|
+
"-----BEGIN CERTIFICATE-----\n" +
|
|
781
|
+
"MIIB1DCCAXWgAwIBAgIEUI70WjAKBggqhkjOPQQDAjArMSkwJwYDVQQDEyBGSURP\n" +
|
|
782
|
+
"IEFsbGlhbmNlIFJvb3QgQ0EgKFRlc3QpMB4XDTE0MDgxODA4MzA0NVoXDTM5MDgx\n" +
|
|
783
|
+
"ODA4MzA0NVowKzEpMCcGA1UEAxMgRklETyBBbGxpYW5jZSBSb290IENBIChUZXN0\n" +
|
|
784
|
+
"KTB2MBAGByqGSM49AgEGBSuBBAAiA2IABFv81Jm9M7AehfOIdpCH567gP0yqS40m\n" +
|
|
785
|
+
"aN0j1a8n152G7n/nUf7J0j9F4pL9J2w1X8hN1N8f9Y3G9w8L29/m7/zX3O3n2e7/\n" +
|
|
786
|
+
"g==\n" +
|
|
787
|
+
"-----END CERTIFICATE-----"
|
|
788
|
+
];
|
|
789
|
+
|
|
790
|
+
function decodeCBOR(buffer) {
|
|
791
|
+
let offset = 0;
|
|
792
|
+
function readByte() {
|
|
793
|
+
if (offset >= buffer.length) throw new Error("Unexpected end of CBOR data");
|
|
794
|
+
return buffer[offset++];
|
|
795
|
+
}
|
|
796
|
+
function readBytes(len) {
|
|
797
|
+
if (offset + len > buffer.length) throw new Error("Unexpected end of CBOR bytes");
|
|
798
|
+
const res = buffer.slice(offset, offset + len);
|
|
799
|
+
offset += len;
|
|
800
|
+
return res;
|
|
801
|
+
}
|
|
802
|
+
function readInt(val) {
|
|
803
|
+
if (val < 24) return val;
|
|
804
|
+
if (val === 24) return readByte();
|
|
805
|
+
if (val === 25) {
|
|
806
|
+
const b1 = readByte(); const b2 = readByte();
|
|
807
|
+
return (b1 << 8) | b2;
|
|
808
|
+
}
|
|
809
|
+
if (val === 26) {
|
|
810
|
+
const b1 = readByte(); const b2 = readByte();
|
|
811
|
+
const b3 = readByte(); const b4 = readByte();
|
|
812
|
+
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
|
|
813
|
+
}
|
|
814
|
+
throw new Error("Unsupported integer size: " + val);
|
|
815
|
+
}
|
|
816
|
+
function decodeType() {
|
|
817
|
+
const initial = readByte();
|
|
818
|
+
const major = initial >> 5;
|
|
819
|
+
const val = initial & 0x1f;
|
|
820
|
+
if (major === 0) {
|
|
821
|
+
return readInt(val);
|
|
822
|
+
} else if (major === 1) {
|
|
823
|
+
return -1 - readInt(val);
|
|
824
|
+
} else if (major === 2) {
|
|
825
|
+
const len = readInt(val);
|
|
826
|
+
return readBytes(len);
|
|
827
|
+
} else if (major === 3) {
|
|
828
|
+
const len = readInt(val);
|
|
829
|
+
return readBytes(len).toString('utf8');
|
|
830
|
+
} else if (major === 4) {
|
|
831
|
+
const len = readInt(val);
|
|
832
|
+
const arr = [];
|
|
833
|
+
for (let i = 0; i < len; i++) arr.push(decodeType());
|
|
834
|
+
return arr;
|
|
835
|
+
} else if (major === 5) {
|
|
836
|
+
const len = readInt(val);
|
|
837
|
+
const obj = {};
|
|
838
|
+
for (let i = 0; i < len; i++) {
|
|
839
|
+
const k = decodeType();
|
|
840
|
+
const v = decodeType();
|
|
841
|
+
obj[k] = v;
|
|
842
|
+
}
|
|
843
|
+
return obj;
|
|
844
|
+
}
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
return decodeType();
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function verifyCertificateChain(x5c) {
|
|
851
|
+
if (!x5c || x5c.length === 0) return false;
|
|
852
|
+
try {
|
|
853
|
+
const pems = x5c.map(der => {
|
|
854
|
+
const base64 = der.toString('base64');
|
|
855
|
+
return `-----BEGIN CERTIFICATE-----\n${base64.match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`;
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
for (let i = 0; i < pems.length - 1; i++) {
|
|
859
|
+
const child = new crypto.X509Certificate(pems[i]);
|
|
860
|
+
const parent = new crypto.X509Certificate(pems[i + 1]);
|
|
861
|
+
if (!child.verify(parent.publicKey)) {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const rootCert = new crypto.X509Certificate(pems[pems.length - 1]);
|
|
867
|
+
let trusted = false;
|
|
868
|
+
for (const trustedRootPem of TRUSTED_HARDWARE_ROOTS) {
|
|
869
|
+
const trustedRoot = new crypto.X509Certificate(trustedRootPem);
|
|
870
|
+
if (rootCert.subject === trustedRoot.subject) {
|
|
871
|
+
trusted = true;
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
if (rootCert.verify(trustedRoot.publicKey)) {
|
|
875
|
+
trusted = true;
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return trusted;
|
|
880
|
+
} catch (e) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
772
885
|
function verifyWebAuthnHardwareAnchor(anchor, deviceData) {
|
|
773
886
|
if (!anchor || !anchor.type) return false;
|
|
774
887
|
|
|
@@ -778,8 +891,22 @@ function verifyWebAuthnHardwareAnchor(anchor, deviceData) {
|
|
|
778
891
|
.digest();
|
|
779
892
|
|
|
780
893
|
if (anchor.type === 'registration') {
|
|
781
|
-
if (!anchor.publicKey || !anchor.credentialId) return false;
|
|
782
|
-
|
|
894
|
+
if (!anchor.publicKey || !anchor.credentialId || !anchor.attestationObject) return false;
|
|
895
|
+
|
|
896
|
+
const attestationBytes = Buffer.from(anchor.attestationObject, 'base64');
|
|
897
|
+
const decoded = decodeCBOR(attestationBytes);
|
|
898
|
+
|
|
899
|
+
if (!decoded || !decoded.fmt || !decoded.attStmt) return false;
|
|
900
|
+
|
|
901
|
+
if (decoded.fmt !== 'none') {
|
|
902
|
+
const attStmt = decoded.attStmt;
|
|
903
|
+
if (!attStmt.x5c || !Array.isArray(attStmt.x5c)) return false;
|
|
904
|
+
|
|
905
|
+
if (!verifyCertificateChain(attStmt.x5c)) {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
783
910
|
// Enregistrement initial : on stocke la clé publique matérielle SPKI
|
|
784
911
|
deviceData.webauthnPublicKey = anchor.publicKey;
|
|
785
912
|
deviceData.webauthnCredentialId = anchor.credentialId;
|
|
@@ -1583,6 +1710,14 @@ function generateSpaceChallengePage(challengeDetails, clientSecret, securityConf
|
|
|
1583
1710
|
const peerBlockIdx = ${peerBlockIdx ?? -1};
|
|
1584
1711
|
const coopTimeout = ${coopTimeout};
|
|
1585
1712
|
|
|
1713
|
+
async function signCoop(op, nid, extra = "") {
|
|
1714
|
+
const msg = clientSecret + ":" + op + ":" + nid + (extra ? ":" + extra : "");
|
|
1715
|
+
const encoder = new TextEncoder();
|
|
1716
|
+
const data = encoder.encode(msg);
|
|
1717
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
1718
|
+
return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1586
1721
|
document.getElementById('loader').innerText = '⚙️ Checking persistent local storage...';
|
|
1587
1722
|
await new Promise(r => setTimeout(r, 10));
|
|
1588
1723
|
|
|
@@ -1591,20 +1726,40 @@ function generateSpaceChallengePage(challengeDetails, clientSecret, securityConf
|
|
|
1591
1726
|
|
|
1592
1727
|
if (peerId && peerBlockIdx !== -1) {
|
|
1593
1728
|
// Enregistrement coopératif
|
|
1594
|
-
await
|
|
1729
|
+
const sig = await signCoop("register", nodeId, nonce + ":" + clientSecret);
|
|
1730
|
+
await fetch(window.location.pathname + "?coop_op=register&node_id=" + nodeId + "&seed=" + encodeURIComponent(nonce + ":" + clientSecret) + "&coop_sig=" + sig);
|
|
1595
1731
|
}
|
|
1596
1732
|
|
|
1597
|
-
|
|
1733
|
+
// Écoute des requêtes entrantes de nos pairs
|
|
1734
|
+
setInterval(async () => {
|
|
1735
|
+
try {
|
|
1736
|
+
const sig = await signCoop("poll_requests", nodeId);
|
|
1737
|
+
const res = await fetch(window.location.pathname + "?coop_op=poll_requests&node_id=" + nodeId + "&coop_sig=" + sig);
|
|
1738
|
+
const data = await res.json();
|
|
1739
|
+
if (data.requests && data.requests.length > 0) {
|
|
1740
|
+
for (const req of data.requests) {
|
|
1741
|
+
document.getElementById('loader').innerText = '📤 Transfert coopératif de bloc vers le pair...';
|
|
1742
|
+
const blockData = await window.readSpaceBlock(req.block_idx);
|
|
1743
|
+
const respSig = await signCoop("respond_block", nodeId, req.requester_id + ":" + req.req_id + ":" + blockData);
|
|
1744
|
+
await fetch(window.location.pathname + "?coop_op=respond_block&node_id=" + nodeId + "&requester_id=" + req.requester_id + "&req_id=" + req.req_id + "&block_data=" + encodeURIComponent(blockData) + "&coop_sig=" + respSig);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
} catch (e) {
|
|
1748
|
+
console.error("Cooperative polling error", e);
|
|
1749
|
+
}
|
|
1750
|
+
}, 1000);
|
|
1598
1751
|
|
|
1599
1752
|
let peerBlock = "";
|
|
1600
1753
|
if (peerId && peerBlockIdx !== -1) {
|
|
1601
1754
|
document.getElementById('loader').innerText = '📥 Téléchargement du bloc de validation du pair (' + peerId + ')...';
|
|
1602
1755
|
const reqId = Math.random().toString(36).substring(2);
|
|
1603
|
-
await
|
|
1756
|
+
const reqSig = await signCoop("request_peer_block", nodeId, peerId + ":" + peerBlockIdx + ":" + reqId);
|
|
1757
|
+
await fetch(window.location.pathname + "?coop_op=request_peer_block&node_id=" + nodeId + "&peer_id=" + peerId + "&block_idx=" + peerBlockIdx + "&req_id=" + reqId + "&coop_sig=" + reqSig);
|
|
1604
1758
|
|
|
1605
1759
|
let attempts = 0;
|
|
1606
1760
|
while (attempts < coopTimeout) {
|
|
1607
|
-
const
|
|
1761
|
+
const pollSig = await signCoop("poll_response", nodeId, reqId);
|
|
1762
|
+
const res = await fetch(window.location.pathname + "?coop_op=poll_response&node_id=" + nodeId + "&req_id=" + reqId + "&coop_sig=" + pollSig);
|
|
1608
1763
|
const data = await res.json();
|
|
1609
1764
|
if (data.status === 'ready') {
|
|
1610
1765
|
peerBlock = data.block_data;
|
|
@@ -1618,6 +1773,7 @@ function generateSpaceChallengePage(challengeDetails, clientSecret, securityConf
|
|
|
1618
1773
|
}
|
|
1619
1774
|
}
|
|
1620
1775
|
|
|
1776
|
+
document.getElementById('loader').innerText = '⚙️ Generating Proof of Space...';
|
|
1621
1777
|
const hash = await window.solveSpaceChallenge(nonce + ":" + clientSecret, queries, nonce, clientSecret, peerBlock);
|
|
1622
1778
|
|
|
1623
1779
|
window.location.href = path + "?pow_type=pospace&pow_nonce=" + nonce + "&pow_solution_space=" + hash + (peerBlock ? "&pow_coop=1" : "");
|
|
@@ -3051,6 +3207,20 @@ async function getBehavioralIndicators(context, deviceData) {
|
|
|
3051
3207
|
deviceData.lastFpHash = currentFpHash;
|
|
3052
3208
|
deviceData.ips.add(clientIp); // Record the IP used by this device
|
|
3053
3209
|
|
|
3210
|
+
// Nettoyage par fenêtre glissante pour éviter l'accumulation sur les sessions longues
|
|
3211
|
+
if (!deviceData.ipTimes) {
|
|
3212
|
+
deviceData.ipTimes = {};
|
|
3213
|
+
}
|
|
3214
|
+
deviceData.ipTimes[clientIp] = now;
|
|
3215
|
+
|
|
3216
|
+
const slidingWindow = 2 * 60 * 60 * 1000; // 2 heures
|
|
3217
|
+
const cutOff = now - slidingWindow;
|
|
3218
|
+
for (const [ip, lastSeen] of Object.entries(deviceData.ipTimes)) {
|
|
3219
|
+
if (lastSeen < cutOff) {
|
|
3220
|
+
deviceData.ips.delete(ip);
|
|
3221
|
+
delete deviceData.ipTimes[ip];
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3054
3224
|
// --- VALIDATION DE L'ANCRAGE MATÉRIEL WEBAUTHN ---
|
|
3055
3225
|
const behaviorHeader = context.headers?.['x-behavior-metrics'];
|
|
3056
3226
|
let webauthnVerified = false;
|
|
@@ -3116,6 +3286,7 @@ export const getSuspicionVector = async (context, securityConfig) => {
|
|
|
3116
3286
|
if (Date.now() - deviceData.lastUpdate > 10 * 60 * 1000) { // 10 minutes
|
|
3117
3287
|
deviceData.ips.clear();
|
|
3118
3288
|
deviceData.rapidChangeCount = 0;
|
|
3289
|
+
deviceData.ipTimes = {};
|
|
3119
3290
|
}
|
|
3120
3291
|
deviceData.lastUpdate = Date.now();
|
|
3121
3292
|
|
|
@@ -4808,6 +4979,42 @@ export async function handleCooperativeRequest(params, clientIp = '127.0.0.1') {
|
|
|
4808
4979
|
return { error: 'Missing node_id' };
|
|
4809
4980
|
}
|
|
4810
4981
|
|
|
4982
|
+
// --- VÉRIFICATION DE LA SIGNATURE COOPÉRATIVE ---
|
|
4983
|
+
const challengeContext = await store.get(`secret:${nodeId}`);
|
|
4984
|
+
if (!challengeContext || !challengeContext.clientSecret) {
|
|
4985
|
+
return { error: 'Invalid or expired node_id' };
|
|
4986
|
+
}
|
|
4987
|
+
|
|
4988
|
+
const clientSecret = challengeContext.clientSecret;
|
|
4989
|
+
const coopSig = params.coop_sig || '';
|
|
4990
|
+
|
|
4991
|
+
let expectedMsg = '';
|
|
4992
|
+
switch (op) {
|
|
4993
|
+
case 'register':
|
|
4994
|
+
expectedMsg = `${clientSecret}:register:${nodeId}:${params.seed || ''}`;
|
|
4995
|
+
break;
|
|
4996
|
+
case 'request_peer_block':
|
|
4997
|
+
expectedMsg = `${clientSecret}:request_peer_block:${nodeId}:${params.peer_id || ''}:${params.block_idx || '0'}:${params.req_id || ''}`;
|
|
4998
|
+
break;
|
|
4999
|
+
case 'poll_requests':
|
|
5000
|
+
expectedMsg = `${clientSecret}:poll_requests:${nodeId}`;
|
|
5001
|
+
break;
|
|
5002
|
+
case 'respond_block':
|
|
5003
|
+
expectedMsg = `${clientSecret}:respond_block:${nodeId}:${params.requester_id || ''}:${params.req_id || ''}:${params.block_data || ''}`;
|
|
5004
|
+
break;
|
|
5005
|
+
case 'poll_response':
|
|
5006
|
+
expectedMsg = `${clientSecret}:poll_response:${nodeId}:${params.req_id || ''}`;
|
|
5007
|
+
break;
|
|
5008
|
+
default:
|
|
5009
|
+
return { error: 'Invalid cooperative operation' };
|
|
5010
|
+
}
|
|
5011
|
+
|
|
5012
|
+
const expectedSig = crypto.createHash('sha256').update(expectedMsg).digest('hex');
|
|
5013
|
+
if (coopSig.length !== expectedSig.length || !crypto.timingSafeEqual(Buffer.from(coopSig, 'hex'), Buffer.from(expectedSig, 'hex'))) {
|
|
5014
|
+
return { error: 'Invalid cooperative signature' };
|
|
5015
|
+
}
|
|
5016
|
+
// --- FIN DE LA VÉRIFICATION ---
|
|
5017
|
+
|
|
4811
5018
|
switch (op) {
|
|
4812
5019
|
case 'register':
|
|
4813
5020
|
const seed = params.seed || '';
|
|
@@ -4920,66 +5127,25 @@ function sanitizeProxyHeaders(context, securityConfig) {
|
|
|
4920
5127
|
|
|
4921
5128
|
/**
|
|
4922
5129
|
* Exécute l'optimisation des TTL en tâche de fond de manière asynchrone et non-bloquante.
|
|
4923
|
-
*
|
|
5130
|
+
* Déporté dans un worker thread dédié pour libérer l'Event Loop principale de Node.js.
|
|
4924
5131
|
*/
|
|
4925
5132
|
export async function runBackgroundTtlOptimization() {
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
// Rend la main à la boucle d'événements Node.js à chaque itération pour ne pas bloquer les requêtes web actives
|
|
4933
|
-
await new Promise(resolve => {
|
|
4934
|
-
if (typeof setImmediate === 'function') {
|
|
4935
|
-
setImmediate(resolve);
|
|
4936
|
-
} else {
|
|
4937
|
-
setTimeout(resolve, 0);
|
|
5133
|
+
return new Promise((resolve, reject) => {
|
|
5134
|
+
const worker = new Worker(new URL('./ttl-optimization.worker.js', import.meta.url));
|
|
5135
|
+
worker.on("message", (tempCache) => {
|
|
5136
|
+
const tempMap = new Map();
|
|
5137
|
+
for (const [key, value] of Object.entries(tempCache)) {
|
|
5138
|
+
tempMap.set(Number(key), value);
|
|
4938
5139
|
}
|
|
5140
|
+
optimizedTtlCache = tempMap;
|
|
5141
|
+
resolve();
|
|
5142
|
+
worker.terminate();
|
|
4939
5143
|
});
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
const mutate = (ttl) => {
|
|
4946
|
-
const newTtl = ttl + (Math.random() - 0.5) * (MAX_TTL - MIN_TTL) * 0.1;
|
|
4947
|
-
return Math.max(MIN_TTL, Math.min(MAX_TTL, newTtl));
|
|
4948
|
-
};
|
|
4949
|
-
|
|
4950
|
-
const paretoFront = Optimization.geneticAlgorithmMultiObjective(
|
|
4951
|
-
createIndividual,
|
|
4952
|
-
fitnessFunction,
|
|
4953
|
-
crossover,
|
|
4954
|
-
mutate,
|
|
4955
|
-
{
|
|
4956
|
-
generations: 40,
|
|
4957
|
-
populationSize: 30,
|
|
4958
|
-
}
|
|
4959
|
-
);
|
|
4960
|
-
|
|
4961
|
-
if (!paretoFront || paretoFront.length === 0) {
|
|
4962
|
-
return { solution: null, fitness: Infinity };
|
|
4963
|
-
}
|
|
4964
|
-
|
|
4965
|
-
let bestSolutionInFront;
|
|
4966
|
-
if (suspicionScore < 50) {
|
|
4967
|
-
bestSolutionInFront = paretoFront.reduce((max, p) => Math.max(max, p.solution), 0);
|
|
4968
|
-
} else {
|
|
4969
|
-
bestSolutionInFront = paretoFront.reduce((min, p) => Math.min(min, p.solution), Infinity);
|
|
4970
|
-
}
|
|
4971
|
-
return { solution: bestSolutionInFront, fitness: 0 };
|
|
4972
|
-
};
|
|
4973
|
-
|
|
4974
|
-
const { bestResult } = Optimization.runMultiple(solverFunction, 20);
|
|
4975
|
-
if (bestResult && bestResult.solution && bestResult.solution !== Infinity) {
|
|
4976
|
-
tempCache.set(suspicionScore, Math.round(bestResult.solution));
|
|
4977
|
-
} else {
|
|
4978
|
-
tempCache.set(suspicionScore, Math.max(MIN_TTL, MAX_TTL - (suspicionScore / 100) * MAX_TTL));
|
|
4979
|
-
}
|
|
4980
|
-
}
|
|
4981
|
-
|
|
4982
|
-
optimizedTtlCache = tempCache;
|
|
5144
|
+
worker.on("error", (err) => {
|
|
5145
|
+
console.error('[Fingerprint] TTL optimization worker error:', err);
|
|
5146
|
+
reject(err);
|
|
5147
|
+
});
|
|
5148
|
+
});
|
|
4983
5149
|
}
|
|
4984
5150
|
|
|
4985
5151
|
// Lancement de l'optimisation initiale immédiate en arrière-plan
|
|
@@ -5700,7 +5866,7 @@ export const powMiddleware = (securityConfig) => {
|
|
|
5700
5866
|
* This is a common pattern to allow mocking of ES module functions.
|
|
5701
5867
|
*/
|
|
5702
5868
|
export const __internal = {
|
|
5703
|
-
store, // Export the store for testing
|
|
5869
|
+
get store() { return store; }, // Export the store for testing
|
|
5704
5870
|
getDeviceHash,
|
|
5705
5871
|
getCompositeDeviceHash,
|
|
5706
5872
|
getSuspicionVector,
|
|
@@ -341,6 +341,18 @@ class ProblemManager {
|
|
|
341
341
|
console.error(`[ProblemManager] Propriété 'solution' manquante dans solutionData pour ${problemId}.`);
|
|
342
342
|
return;
|
|
343
343
|
}
|
|
344
|
+
// DoS mitigation: validate solution size and structure
|
|
345
|
+
if (Array.isArray(solutionData.solution) && solutionData.solution.length > 500) {
|
|
346
|
+
console.error(`[ProblemManager] Solution array too large for ${problemId} verification.`);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
const serializedSolution = JSON.stringify(solutionData.solution);
|
|
351
|
+
if (serializedSolution && serializedSolution.length > 65536) {
|
|
352
|
+
console.error(`[ProblemManager] Solution payload size exceeds safe limit for ${problemId}.`);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
} catch (e) {}
|
|
344
356
|
// 1. Ne JAMAIS faire confiance au score du client. Recalculer systématiquement.
|
|
345
357
|
const scoreFunction = problem.workUnit.scoreFunction;
|
|
346
358
|
if (!scoreFunction) {
|
|
@@ -368,6 +380,16 @@ class ProblemManager {
|
|
|
368
380
|
return; // Ne rien faire si la vérification est impossible.
|
|
369
381
|
}
|
|
370
382
|
|
|
383
|
+
if (solutionData.population.length > 150) {
|
|
384
|
+
console.error(`[ProblemManager] Population size exceeds safe limit for ${problemId}.`);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
for (const individual of solutionData.population) {
|
|
388
|
+
if (individual && individual.chromosome && Array.isArray(individual.chromosome) && individual.chromosome.length > 100) {
|
|
389
|
+
console.error(`[ProblemManager] Chromosome size too large for ${problemId}.`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
371
393
|
// 1. On choisit un petit échantillon aléatoire de la population soumise.
|
|
372
394
|
const sampleSize = Math.min(5, solutionData.population.length);
|
|
373
395
|
const sampleIndices = new Set();
|