@anonympins/fingerprint 0.6.6 → 0.6.7

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.
@@ -3028,8 +3028,9 @@ async function updateSubnetMetrics(context, deviceId, finalScore) {
3028
3028
  async function getSubnetScore(context) {
3029
3029
  const subnet = getIpSubnet(context.clientIp);
3030
3030
  if (!subnet) return { subnetScore: 0 };
3031
+ const key = `subnet:${subnet}`;
3031
3032
 
3032
- const subnetData = await store.get(`subnet:${subnet}`);
3033
+ const subnetData = await store.get(key);
3033
3034
  if (!subnetData) return { subnetScore: 0 };
3034
3035
 
3035
3036
  const now = Date.now();
@@ -3057,6 +3058,10 @@ async function getSubnetScore(context) {
3057
3058
  const suspicionDensity = highScoreCount / deviceCount;
3058
3059
  const ipDeviceRatio = ipCount / deviceCount;
3059
3060
 
3061
+ // Ratio User-Agent / Device : détecte la rotation/spoofing de navigateurs sur une même empreinte matérielle
3062
+ const uaDeviceRatio = Math.max(1, uaCount) / deviceCount;
3063
+ const uaMultiplier = 0.6 + (0.4 * Math.min(2.5, uaDeviceRatio));
3064
+
3060
3065
  // Base score continu basé sur le volume de menaces
3061
3066
  const baseScore = 100 * (1 - Math.exp(-0.15 * highScoreCount));
3062
3067
 
@@ -3064,7 +3069,13 @@ async function getSubnetScore(context) {
3064
3069
  const densityMultiplier = 0.4 + (1.6 * suspicionDensity); // Favorise les densités de suspicion élevées
3065
3070
  const distributionMultiplier = 0.5 + (1.0 * ipDeviceRatio); // NAT (faible ratio IP/Device) vs Proxy distribué (fort ratio)
3066
3071
 
3067
- const finalScore = Math.min(100, Math.round(baseScore * densityMultiplier * distributionMultiplier * 10) / 10);
3072
+ // Amortissement pour éviter les faux positifs sur les réseaux NAT résidentiels (petits nombres d'appareils suspects)
3073
+ let dampening = 1.0;
3074
+ if (highScoreCount < 3) {
3075
+ dampening = highScoreCount / 3.0; // 0.33 pour 1 appareil, 0.66 pour 2 appareils
3076
+ }
3077
+
3078
+ const finalScore = Math.min(100, Math.round(baseScore * densityMultiplier * distributionMultiplier * uaMultiplier * dampening * 10) / 10);
3068
3079
  return { subnetScore: finalScore };
3069
3080
  }
3070
3081
 
@@ -3891,7 +3902,7 @@ function generateCombinedPoWChallengePage(cpuChallengeDetails, memoryDifficulty,
3891
3902
  await new Promise(r => setTimeout(r, 10)); // Yield to update UI
3892
3903
  let memSolution = 0;
3893
3904
  try {
3894
- const memSeed = nonce + ":" + clientSecret;
3905
+ const memSeed = ":" + nonce + ":" + clientSecret;
3895
3906
  memSolution = await window.solveMemoryChallenge(memSeed, memDifficulty);
3896
3907
  } catch(e) {
3897
3908
  document.getElementById('loader').innerText = "Error: Insufficient memory. Please refresh.";
@@ -5033,9 +5044,9 @@ export class FingerprintEngine {
5033
5044
  }
5034
5045
  }
5035
5046
  const isSuspiciousHigh = finalScore >= thresholds.high && !isBlocked && finalScore > 0;
5036
- const isSuspiciousMedium = finalScore >= thresholds.medium;
5037
- const isSuspicious = finalScore >= thresholds.low;
5038
- const isVerySuspicious = finalScore >= thresholds.medium; // Seuil pour le challenge d'optimisation
5047
+ const isSuspiciousMedium = finalScore >= thresholds.medium && finalScore > 0;
5048
+ const isSuspicious = finalScore >= thresholds.low && finalScore > 0;
5049
+ const isVerySuspicious = finalScore >= thresholds.medium && finalScore > 0; // Seuil pour le challenge d'optimisation
5039
5050
 
5040
5051
  // Calculate an analog "suspicion factor" (0 to 1+) for progressive difficulty
5041
5052
  const suspicionFactor = isSuspicious // eslint-disable-line no-nested-ternary
@@ -5122,10 +5133,10 @@ export class FingerprintEngine {
5122
5133
  // Un challenge est nécessaire si :
5123
5134
  // 1. La requête est suspecte ET il n'y a pas de ticket valide.
5124
5135
  // OU
5125
- // 2. La requête est *très* suspecte (dépasse le seuil 'high'), ce qui annule la validité du ticket actuel.
5136
+ // 2. La requête est très suspecte (dépasse le seuil 'high'), ce qui annule la validité du ticket actuel.
5126
5137
  const zkpProof = requestContext.headers['x-zkp-proof'] || query.pow_zkp || '';
5127
5138
  const hasValidTicket = await isTicketValid(clientIp, powCookie, deviceId, currentDeviceHash, allowRoaming, zkpProof);
5128
- const mustReChallenge = isSuspiciousHigh && hasValidTicket;
5139
+ const mustReChallenge = isSuspiciousHigh && !isBlocked && hasValidTicket;
5129
5140
 
5130
5141
  if (isSuspicious && (!hasValidTicket || mustReChallenge)) {
5131
5142
  if (mustReChallenge) {
@@ -5210,8 +5221,15 @@ export class FingerprintEngine {
5210
5221
  if (isApi) {
5211
5222
  return { action: 'challenge', score: finalScore, vector: suspicionVector, status: 404, body: challengePayload };
5212
5223
  } else {
5224
+ const taskType = challengePayload.challenge.usefulWorkTask.task.type;
5225
+ let dummyResult = { solution: [], energy: 0 };
5226
+ if (taskType === 'multi_objective_genetic_algorithm') {
5227
+ dummyResult = { paretoFront: [] };
5228
+ } else if (taskType === 'genetic_algorithm_generations') {
5229
+ dummyResult = { population: [] };
5230
+ }
5213
5231
  const html = `<html><body><script>
5214
- window.location.href = "${path}?pow_type=useful_work_task&pow_nonce=${nonce}&pow_problem_id=${challengePayload.challenge.usefulWorkTask.problemId}&pow_solution_work_result=" + encodeURIComponent(JSON.stringify({ solution: [], energy: 0 }));
5232
+ window.location.href = "${path}?pow_type=useful_work_task&pow_nonce=${nonce}&pow_problem_id=${challengePayload.challenge.usefulWorkTask.problemId}&pow_solution_work_result=" + encodeURIComponent(JSON.stringify(${JSON.stringify(dummyResult)}));
5215
5233
  </script></body></html>`;
5216
5234
  return { action: 'challenge', score: finalScore, vector: suspicionVector, status: 404, body: html };
5217
5235
  }
@@ -385,9 +385,16 @@ class ProblemManager {
385
385
  case 'genetic_algorithm_generations':
386
386
  // VÉRIFICATION PAR ÉCHANTILLONNAGE pour équilibrer sécurité et performance.
387
387
  const fitnessFunction = FunctionRegistry['portfolio.calculateMetrics']; // Ou une fonction plus générique
388
- if (!fitnessFunction || !Array.isArray(solutionData.population) || solutionData.population.length === 0) {
389
- console.error(`[ProblemManager] Impossible de vérifier la population pour ${problemId}.`);
390
- return; // Ne rien faire si la vérification est impossible.
388
+ if (!solutionData || !Array.isArray(solutionData.population)) {
389
+ console.error(`[ProblemManager] Propriété 'population' manquante ou invalide dans solutionData pour ${problemId}.`);
390
+ return;
391
+ }
392
+ if (solutionData.population.length === 0) {
393
+ return; // Retour gracieux si la population est vide (ex: fallback de redirection)
394
+ }
395
+ if (!fitnessFunction) {
396
+ console.error(`[ProblemManager] Impossible de vérifier la population pour ${problemId} (fonction de fitness manquante).`);
397
+ return;
391
398
  }
392
399
 
393
400
  if (solutionData.population.length > 150) {
@@ -130,6 +130,31 @@ describe('FingerprintEngine Challenge Validation', () => {
130
130
  expect(await inMemoryStore.has(`secret:${nonce}`)).toBe(false);
131
131
  }, 20000);
132
132
 
133
+ it('should generate a combined challenge page containing the correct prefix for the memory seed', async () => {
134
+ const originalRequestContext = {
135
+ clientIp: '127.0.0.1',
136
+ path: '/sensitive-data',
137
+ cookies: {},
138
+ query: {},
139
+ headers: {
140
+ 'user-agent': 'A-Legit-Browser/1.0',
141
+ 'x-device-fingerprint': 'fingerprint-A'
142
+ },
143
+ isStatic: false,
144
+ rawReq: { headers: { accept: 'text/html' } }
145
+ };
146
+
147
+ // Force a high score to trigger a challenge
148
+ vi.spyOn(engine, 'calculateFinalScore').mockReturnValueOnce(20);
149
+
150
+ const challengeDecision = await engine.processRequest(originalRequestContext);
151
+
152
+ expect(challengeDecision.action).toBe('challenge');
153
+ const body = challengeDecision.body;
154
+ // Verify that the memory seed is constructed with the correct leading colon
155
+ expect(body).toContain('const memSeed = ":" + nonce + ":" + clientSecret;');
156
+ });
157
+
133
158
  it('should reject a challenge solution with a mismatched fingerprint', async () => {
134
159
  // --- 1. First request: Challenge is issued to "Machine A" ---
135
160
  const originalRequestContext = {
@@ -2414,7 +2414,7 @@ describe('Subnet Scoring (Node.js)', () => {
2414
2414
  deviceIds: deviceIds, // count = 20. score += (20-10)*5 = 50
2415
2415
  });
2416
2416
  ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2417
- expect(subnetScore).toBe(100);
2417
+ expect(subnetScore).toBe(94.4);
2418
2418
  });
2419
2419
  });
2420
2420
 
@@ -1134,7 +1134,7 @@ JS;
1134
1134
  }
1135
1135
  let memSolution = 0;
1136
1136
  try {
1137
- const memSeed = nonce + ":" + clientSecret;
1137
+ const memSeed = ":" + nonce + ":" + clientSecret;
1138
1138
  memSolution = await window.solveMemoryChallenge(memSeed, memDifficulty);
1139
1139
  } catch(e) {
1140
1140
  document.getElementById('loader').innerText = "Error: Insufficient memory. Please refresh.";