@mack1ch/fingerprint-js 0.1.3 → 0.1.4
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/dist/index.cjs +45 -9
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +45 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -946,6 +946,20 @@ var VOLATILE_DEVICE_KEYS = /* @__PURE__ */ new Set([
|
|
|
946
946
|
"screenLeft",
|
|
947
947
|
"screenTop"
|
|
948
948
|
]);
|
|
949
|
+
var HARDWARE_DEVICE_KEYS = /* @__PURE__ */ new Set([
|
|
950
|
+
"hardwareConcurrency",
|
|
951
|
+
"deviceMemory",
|
|
952
|
+
"platform",
|
|
953
|
+
"unmaskedVendor",
|
|
954
|
+
"unmaskedRenderer",
|
|
955
|
+
"width",
|
|
956
|
+
"height",
|
|
957
|
+
"colorDepth",
|
|
958
|
+
"pixelDepth",
|
|
959
|
+
"timeZone",
|
|
960
|
+
"sampleRate",
|
|
961
|
+
"maxTouchPoints"
|
|
962
|
+
]);
|
|
949
963
|
function getSignalStability(key) {
|
|
950
964
|
if (STABLE_DEVICE_KEYS.has(key)) {
|
|
951
965
|
return "stable";
|
|
@@ -1004,6 +1018,16 @@ function toCoreCanonicalPayload(components) {
|
|
|
1004
1018
|
return false;
|
|
1005
1019
|
}).map((component) => `${component.key}=${component.value}`).join("|");
|
|
1006
1020
|
}
|
|
1021
|
+
function toHardwareCanonicalPayload(components) {
|
|
1022
|
+
return components.filter((component) => {
|
|
1023
|
+
if (component.kind === "required" && component.key === "required.merchantId") return true;
|
|
1024
|
+
if (component.kind === "device") {
|
|
1025
|
+
const key = component.key.replace(/^device\./, "");
|
|
1026
|
+
return HARDWARE_DEVICE_KEYS.has(key);
|
|
1027
|
+
}
|
|
1028
|
+
return false;
|
|
1029
|
+
}).map((component) => `${component.key}=${component.value}`).join("|");
|
|
1030
|
+
}
|
|
1007
1031
|
function computeConfidenceScore(components, timedOutCollectors, failedCollectorsCount) {
|
|
1008
1032
|
const weightedTotal = components.reduce((total, component) => total + component.weight, 0);
|
|
1009
1033
|
if (weightedTotal === 0) {
|
|
@@ -1053,10 +1077,12 @@ async function getFingerprint(options) {
|
|
|
1053
1077
|
deviceSignals: gathered.deviceSignals
|
|
1054
1078
|
});
|
|
1055
1079
|
const corePayload = toCoreCanonicalPayload(components);
|
|
1080
|
+
const hardwarePayload = toHardwareCanonicalPayload(components);
|
|
1056
1081
|
const fullPayload = toCanonicalPayload(components);
|
|
1057
1082
|
const digestAlgorithm = ((_a = options.hash) == null ? void 0 : _a.algorithm) ?? "SHA-256";
|
|
1058
|
-
const [coreHash, fullHash] = await Promise.all([
|
|
1083
|
+
const [coreHash, hardwareCoreHash, fullHash] = await Promise.all([
|
|
1059
1084
|
createDigest(corePayload, digestAlgorithm),
|
|
1085
|
+
createDigest(hardwarePayload, digestAlgorithm),
|
|
1060
1086
|
createDigest(fullPayload, digestAlgorithm)
|
|
1061
1087
|
]);
|
|
1062
1088
|
const completedAtMs = Date.now();
|
|
@@ -1064,6 +1090,7 @@ async function getFingerprint(options) {
|
|
|
1064
1090
|
const maskedRaw = applyPrivacy(gathered.rawItems, ((_c = options.privacy) == null ? void 0 : _c.maskSensitive) ?? false);
|
|
1065
1091
|
return {
|
|
1066
1092
|
coreHash,
|
|
1093
|
+
hardwareCoreHash,
|
|
1067
1094
|
fullHash,
|
|
1068
1095
|
hash: fullHash,
|
|
1069
1096
|
hashVersion: "fp_v1",
|
|
@@ -1104,10 +1131,12 @@ async function getFingerprintQuick(options) {
|
|
|
1104
1131
|
deviceSignals: gathered.deviceSignals
|
|
1105
1132
|
});
|
|
1106
1133
|
const corePayload = toCoreCanonicalPayload(components);
|
|
1134
|
+
const hardwarePayload = toHardwareCanonicalPayload(components);
|
|
1107
1135
|
const fullPayload = toCanonicalPayload(components);
|
|
1108
1136
|
const digestAlgorithm = ((_a = options.hash) == null ? void 0 : _a.algorithm) ?? "SHA-256";
|
|
1109
|
-
const [coreHash, fullHash] = await Promise.all([
|
|
1137
|
+
const [coreHash, hardwareCoreHash, fullHash] = await Promise.all([
|
|
1110
1138
|
createDigest(corePayload, digestAlgorithm),
|
|
1139
|
+
createDigest(hardwarePayload, digestAlgorithm),
|
|
1111
1140
|
createDigest(fullPayload, digestAlgorithm)
|
|
1112
1141
|
]);
|
|
1113
1142
|
const completedAtMs = Date.now();
|
|
@@ -1115,6 +1144,7 @@ async function getFingerprintQuick(options) {
|
|
|
1115
1144
|
const maskedRaw = applyPrivacy(gathered.rawItems, ((_c = options.privacy) == null ? void 0 : _c.maskSensitive) ?? false);
|
|
1116
1145
|
return {
|
|
1117
1146
|
coreHash,
|
|
1147
|
+
hardwareCoreHash,
|
|
1118
1148
|
fullHash,
|
|
1119
1149
|
hash: fullHash,
|
|
1120
1150
|
hashVersion: "fp_v1",
|
|
@@ -1200,8 +1230,9 @@ function compareFingerprints(current, reference) {
|
|
|
1200
1230
|
if (refComponent.stability === "volatile") changedVolatileKeys.push(refComponent.key);
|
|
1201
1231
|
}
|
|
1202
1232
|
const similarityScore = totalReferenceWeight === 0 ? 0 : round3(matchedWeight / totalReferenceWeight);
|
|
1233
|
+
const crossBrowserSimilarityScore = current.hardwareCoreHash === reference.hardwareCoreHash ? Math.max(similarityScore, 0.9) : similarityScore;
|
|
1203
1234
|
const reasons = [];
|
|
1204
|
-
let risk = 1 -
|
|
1235
|
+
let risk = 1 - crossBrowserSimilarityScore;
|
|
1205
1236
|
if (changedStableKeys.length >= 3) {
|
|
1206
1237
|
risk += 0.2;
|
|
1207
1238
|
reasons.push("Multiple stable identifiers changed");
|
|
@@ -1244,13 +1275,15 @@ function compareFingerprints(current, reference) {
|
|
|
1244
1275
|
const riskScore = round3(risk);
|
|
1245
1276
|
let verdict = "different_device";
|
|
1246
1277
|
if (similarityScore >= 0.85 && riskScore < 0.35) verdict = "same_device";
|
|
1278
|
+
else if (crossBrowserSimilarityScore >= 0.82 && riskScore < 0.45) verdict = "likely_same_cross_browser";
|
|
1247
1279
|
else if (similarityScore >= 0.7 && riskScore < 0.55) verdict = "likely_same";
|
|
1248
|
-
else if (
|
|
1280
|
+
else if (crossBrowserSimilarityScore >= 0.45) verdict = "suspicious";
|
|
1249
1281
|
if (!reasons.length) {
|
|
1250
1282
|
reasons.push("No meaningful drift between profiles");
|
|
1251
1283
|
}
|
|
1252
1284
|
return {
|
|
1253
1285
|
similarityScore,
|
|
1286
|
+
crossBrowserSimilarityScore,
|
|
1254
1287
|
riskScore,
|
|
1255
1288
|
verdict,
|
|
1256
1289
|
matchedWeight,
|
|
@@ -1398,7 +1431,10 @@ function assessPaymentRisk(input) {
|
|
|
1398
1431
|
var _a, _b, _c, _d;
|
|
1399
1432
|
const reasons = [...input.comparison.reasons];
|
|
1400
1433
|
let score = input.comparison.riskScore;
|
|
1401
|
-
const
|
|
1434
|
+
const effectiveSimilarity = Math.max(
|
|
1435
|
+
input.comparison.similarityScore,
|
|
1436
|
+
input.comparison.crossBrowserSimilarityScore
|
|
1437
|
+
);
|
|
1402
1438
|
if (input.history) {
|
|
1403
1439
|
if (input.history.aggregateRiskScore > 0.6) {
|
|
1404
1440
|
score += 0.1;
|
|
@@ -1428,14 +1464,14 @@ function assessPaymentRisk(input) {
|
|
|
1428
1464
|
}
|
|
1429
1465
|
const finalScore = Math.max(0, Math.min(1, Number(score.toFixed(3))));
|
|
1430
1466
|
let decision = "manual_review";
|
|
1431
|
-
if (finalScore < 0.35 &&
|
|
1432
|
-
else if (finalScore < 0.55 &&
|
|
1433
|
-
else if (finalScore >= 0.8 ||
|
|
1467
|
+
if (finalScore < 0.35 && effectiveSimilarity >= 0.82) decision = "allow";
|
|
1468
|
+
else if (finalScore < 0.55 && effectiveSimilarity >= 0.65) decision = "challenge";
|
|
1469
|
+
else if (finalScore >= 0.8 || effectiveSimilarity < 0.35) decision = "deny";
|
|
1434
1470
|
const output = {
|
|
1435
1471
|
decision,
|
|
1436
1472
|
score: finalScore,
|
|
1437
1473
|
reasons: Array.from(new Set(reasons)),
|
|
1438
|
-
similarityScore,
|
|
1474
|
+
similarityScore: effectiveSimilarity,
|
|
1439
1475
|
riskScore: input.comparison.riskScore
|
|
1440
1476
|
};
|
|
1441
1477
|
fetch("http://127.0.0.1:7417/ingest/67545057-d930-4022-a3c5-818008410ad2", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "ce1cd9" }, body: JSON.stringify({ sessionId: "ce1cd9", runId: "payment-risk", hypothesisId: "H4", location: "src/fingerprint/payment.ts:assessPaymentRisk", message: "payment risk assessed", data: { decision: output.decision, score: output.score, similarity: output.similarityScore, risk: output.riskScore, reasonsCount: output.reasons.length }, timestamp: Date.now() }) }).catch(() => {
|
package/dist/index.d.cts
CHANGED
|
@@ -59,6 +59,7 @@ type FingerprintMeta = {
|
|
|
59
59
|
};
|
|
60
60
|
type FingerprintResult = {
|
|
61
61
|
coreHash: string;
|
|
62
|
+
hardwareCoreHash: string;
|
|
62
63
|
fullHash: string;
|
|
63
64
|
hash: string;
|
|
64
65
|
hashVersion: 'fp_v1';
|
|
@@ -71,8 +72,9 @@ type FingerprintResult = {
|
|
|
71
72
|
};
|
|
72
73
|
type FingerprintComparison = {
|
|
73
74
|
similarityScore: number;
|
|
75
|
+
crossBrowserSimilarityScore: number;
|
|
74
76
|
riskScore: number;
|
|
75
|
-
verdict: 'same_device' | 'likely_same' | 'suspicious' | 'different_device';
|
|
77
|
+
verdict: 'same_device' | 'likely_same' | 'likely_same_cross_browser' | 'suspicious' | 'different_device';
|
|
76
78
|
matchedWeight: number;
|
|
77
79
|
totalReferenceWeight: number;
|
|
78
80
|
reasons: string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ type FingerprintMeta = {
|
|
|
59
59
|
};
|
|
60
60
|
type FingerprintResult = {
|
|
61
61
|
coreHash: string;
|
|
62
|
+
hardwareCoreHash: string;
|
|
62
63
|
fullHash: string;
|
|
63
64
|
hash: string;
|
|
64
65
|
hashVersion: 'fp_v1';
|
|
@@ -71,8 +72,9 @@ type FingerprintResult = {
|
|
|
71
72
|
};
|
|
72
73
|
type FingerprintComparison = {
|
|
73
74
|
similarityScore: number;
|
|
75
|
+
crossBrowserSimilarityScore: number;
|
|
74
76
|
riskScore: number;
|
|
75
|
-
verdict: 'same_device' | 'likely_same' | 'suspicious' | 'different_device';
|
|
77
|
+
verdict: 'same_device' | 'likely_same' | 'likely_same_cross_browser' | 'suspicious' | 'different_device';
|
|
76
78
|
matchedWeight: number;
|
|
77
79
|
totalReferenceWeight: number;
|
|
78
80
|
reasons: string[];
|
package/dist/index.js
CHANGED
|
@@ -913,6 +913,20 @@ var VOLATILE_DEVICE_KEYS = /* @__PURE__ */ new Set([
|
|
|
913
913
|
"screenLeft",
|
|
914
914
|
"screenTop"
|
|
915
915
|
]);
|
|
916
|
+
var HARDWARE_DEVICE_KEYS = /* @__PURE__ */ new Set([
|
|
917
|
+
"hardwareConcurrency",
|
|
918
|
+
"deviceMemory",
|
|
919
|
+
"platform",
|
|
920
|
+
"unmaskedVendor",
|
|
921
|
+
"unmaskedRenderer",
|
|
922
|
+
"width",
|
|
923
|
+
"height",
|
|
924
|
+
"colorDepth",
|
|
925
|
+
"pixelDepth",
|
|
926
|
+
"timeZone",
|
|
927
|
+
"sampleRate",
|
|
928
|
+
"maxTouchPoints"
|
|
929
|
+
]);
|
|
916
930
|
function getSignalStability(key) {
|
|
917
931
|
if (STABLE_DEVICE_KEYS.has(key)) {
|
|
918
932
|
return "stable";
|
|
@@ -971,6 +985,16 @@ function toCoreCanonicalPayload(components) {
|
|
|
971
985
|
return false;
|
|
972
986
|
}).map((component) => `${component.key}=${component.value}`).join("|");
|
|
973
987
|
}
|
|
988
|
+
function toHardwareCanonicalPayload(components) {
|
|
989
|
+
return components.filter((component) => {
|
|
990
|
+
if (component.kind === "required" && component.key === "required.merchantId") return true;
|
|
991
|
+
if (component.kind === "device") {
|
|
992
|
+
const key = component.key.replace(/^device\./, "");
|
|
993
|
+
return HARDWARE_DEVICE_KEYS.has(key);
|
|
994
|
+
}
|
|
995
|
+
return false;
|
|
996
|
+
}).map((component) => `${component.key}=${component.value}`).join("|");
|
|
997
|
+
}
|
|
974
998
|
function computeConfidenceScore(components, timedOutCollectors, failedCollectorsCount) {
|
|
975
999
|
const weightedTotal = components.reduce((total, component) => total + component.weight, 0);
|
|
976
1000
|
if (weightedTotal === 0) {
|
|
@@ -1020,10 +1044,12 @@ async function getFingerprint(options) {
|
|
|
1020
1044
|
deviceSignals: gathered.deviceSignals
|
|
1021
1045
|
});
|
|
1022
1046
|
const corePayload = toCoreCanonicalPayload(components);
|
|
1047
|
+
const hardwarePayload = toHardwareCanonicalPayload(components);
|
|
1023
1048
|
const fullPayload = toCanonicalPayload(components);
|
|
1024
1049
|
const digestAlgorithm = ((_a = options.hash) == null ? void 0 : _a.algorithm) ?? "SHA-256";
|
|
1025
|
-
const [coreHash, fullHash] = await Promise.all([
|
|
1050
|
+
const [coreHash, hardwareCoreHash, fullHash] = await Promise.all([
|
|
1026
1051
|
createDigest(corePayload, digestAlgorithm),
|
|
1052
|
+
createDigest(hardwarePayload, digestAlgorithm),
|
|
1027
1053
|
createDigest(fullPayload, digestAlgorithm)
|
|
1028
1054
|
]);
|
|
1029
1055
|
const completedAtMs = Date.now();
|
|
@@ -1031,6 +1057,7 @@ async function getFingerprint(options) {
|
|
|
1031
1057
|
const maskedRaw = applyPrivacy(gathered.rawItems, ((_c = options.privacy) == null ? void 0 : _c.maskSensitive) ?? false);
|
|
1032
1058
|
return {
|
|
1033
1059
|
coreHash,
|
|
1060
|
+
hardwareCoreHash,
|
|
1034
1061
|
fullHash,
|
|
1035
1062
|
hash: fullHash,
|
|
1036
1063
|
hashVersion: "fp_v1",
|
|
@@ -1071,10 +1098,12 @@ async function getFingerprintQuick(options) {
|
|
|
1071
1098
|
deviceSignals: gathered.deviceSignals
|
|
1072
1099
|
});
|
|
1073
1100
|
const corePayload = toCoreCanonicalPayload(components);
|
|
1101
|
+
const hardwarePayload = toHardwareCanonicalPayload(components);
|
|
1074
1102
|
const fullPayload = toCanonicalPayload(components);
|
|
1075
1103
|
const digestAlgorithm = ((_a = options.hash) == null ? void 0 : _a.algorithm) ?? "SHA-256";
|
|
1076
|
-
const [coreHash, fullHash] = await Promise.all([
|
|
1104
|
+
const [coreHash, hardwareCoreHash, fullHash] = await Promise.all([
|
|
1077
1105
|
createDigest(corePayload, digestAlgorithm),
|
|
1106
|
+
createDigest(hardwarePayload, digestAlgorithm),
|
|
1078
1107
|
createDigest(fullPayload, digestAlgorithm)
|
|
1079
1108
|
]);
|
|
1080
1109
|
const completedAtMs = Date.now();
|
|
@@ -1082,6 +1111,7 @@ async function getFingerprintQuick(options) {
|
|
|
1082
1111
|
const maskedRaw = applyPrivacy(gathered.rawItems, ((_c = options.privacy) == null ? void 0 : _c.maskSensitive) ?? false);
|
|
1083
1112
|
return {
|
|
1084
1113
|
coreHash,
|
|
1114
|
+
hardwareCoreHash,
|
|
1085
1115
|
fullHash,
|
|
1086
1116
|
hash: fullHash,
|
|
1087
1117
|
hashVersion: "fp_v1",
|
|
@@ -1167,8 +1197,9 @@ function compareFingerprints(current, reference) {
|
|
|
1167
1197
|
if (refComponent.stability === "volatile") changedVolatileKeys.push(refComponent.key);
|
|
1168
1198
|
}
|
|
1169
1199
|
const similarityScore = totalReferenceWeight === 0 ? 0 : round3(matchedWeight / totalReferenceWeight);
|
|
1200
|
+
const crossBrowserSimilarityScore = current.hardwareCoreHash === reference.hardwareCoreHash ? Math.max(similarityScore, 0.9) : similarityScore;
|
|
1170
1201
|
const reasons = [];
|
|
1171
|
-
let risk = 1 -
|
|
1202
|
+
let risk = 1 - crossBrowserSimilarityScore;
|
|
1172
1203
|
if (changedStableKeys.length >= 3) {
|
|
1173
1204
|
risk += 0.2;
|
|
1174
1205
|
reasons.push("Multiple stable identifiers changed");
|
|
@@ -1211,13 +1242,15 @@ function compareFingerprints(current, reference) {
|
|
|
1211
1242
|
const riskScore = round3(risk);
|
|
1212
1243
|
let verdict = "different_device";
|
|
1213
1244
|
if (similarityScore >= 0.85 && riskScore < 0.35) verdict = "same_device";
|
|
1245
|
+
else if (crossBrowserSimilarityScore >= 0.82 && riskScore < 0.45) verdict = "likely_same_cross_browser";
|
|
1214
1246
|
else if (similarityScore >= 0.7 && riskScore < 0.55) verdict = "likely_same";
|
|
1215
|
-
else if (
|
|
1247
|
+
else if (crossBrowserSimilarityScore >= 0.45) verdict = "suspicious";
|
|
1216
1248
|
if (!reasons.length) {
|
|
1217
1249
|
reasons.push("No meaningful drift between profiles");
|
|
1218
1250
|
}
|
|
1219
1251
|
return {
|
|
1220
1252
|
similarityScore,
|
|
1253
|
+
crossBrowserSimilarityScore,
|
|
1221
1254
|
riskScore,
|
|
1222
1255
|
verdict,
|
|
1223
1256
|
matchedWeight,
|
|
@@ -1365,7 +1398,10 @@ function assessPaymentRisk(input) {
|
|
|
1365
1398
|
var _a, _b, _c, _d;
|
|
1366
1399
|
const reasons = [...input.comparison.reasons];
|
|
1367
1400
|
let score = input.comparison.riskScore;
|
|
1368
|
-
const
|
|
1401
|
+
const effectiveSimilarity = Math.max(
|
|
1402
|
+
input.comparison.similarityScore,
|
|
1403
|
+
input.comparison.crossBrowserSimilarityScore
|
|
1404
|
+
);
|
|
1369
1405
|
if (input.history) {
|
|
1370
1406
|
if (input.history.aggregateRiskScore > 0.6) {
|
|
1371
1407
|
score += 0.1;
|
|
@@ -1395,14 +1431,14 @@ function assessPaymentRisk(input) {
|
|
|
1395
1431
|
}
|
|
1396
1432
|
const finalScore = Math.max(0, Math.min(1, Number(score.toFixed(3))));
|
|
1397
1433
|
let decision = "manual_review";
|
|
1398
|
-
if (finalScore < 0.35 &&
|
|
1399
|
-
else if (finalScore < 0.55 &&
|
|
1400
|
-
else if (finalScore >= 0.8 ||
|
|
1434
|
+
if (finalScore < 0.35 && effectiveSimilarity >= 0.82) decision = "allow";
|
|
1435
|
+
else if (finalScore < 0.55 && effectiveSimilarity >= 0.65) decision = "challenge";
|
|
1436
|
+
else if (finalScore >= 0.8 || effectiveSimilarity < 0.35) decision = "deny";
|
|
1401
1437
|
const output = {
|
|
1402
1438
|
decision,
|
|
1403
1439
|
score: finalScore,
|
|
1404
1440
|
reasons: Array.from(new Set(reasons)),
|
|
1405
|
-
similarityScore,
|
|
1441
|
+
similarityScore: effectiveSimilarity,
|
|
1406
1442
|
riskScore: input.comparison.riskScore
|
|
1407
1443
|
};
|
|
1408
1444
|
fetch("http://127.0.0.1:7417/ingest/67545057-d930-4022-a3c5-818008410ad2", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "ce1cd9" }, body: JSON.stringify({ sessionId: "ce1cd9", runId: "payment-risk", hypothesisId: "H4", location: "src/fingerprint/payment.ts:assessPaymentRisk", message: "payment risk assessed", data: { decision: output.decision, score: output.score, similarity: output.similarityScore, risk: output.riskScore, reasonsCount: output.reasons.length }, timestamp: Date.now() }) }).catch(() => {
|