@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.
- package/CHANGELOG.md +16 -0
- package/README.md +15 -2
- package/package.json +1 -1
- package/public/fp.js +1 -1
- package/public/fp.wasm +0 -0
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +27 -9
- package/src/js/problem-manager.js +10 -3
- package/src/js/tests/fingerprint.engine.test.js +25 -0
- package/src/js/tests/fingerprint.test.js +1 -1
- package/src/php/Challenge/ChallengeUtils.php +1 -1
- package/src/php/FingerprintEngine.php +1880 -1879
- package/src/php/ProblemManager.php +83 -0
- package/src/php/Utils/RequestUtils.php +9 -1
package/src/js/fingerprint.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
|
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({
|
|
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 (!
|
|
389
|
-
console.error(`[ProblemManager]
|
|
390
|
-
return;
|
|
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
|
-
|
|
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.";
|