device-passport 1.1.0 → 1.2.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 +10 -7
- package/README.md +41 -0
- package/assets/ego-profile-manager.png +0 -0
- package/dist/adapters/index.cjs +2 -2
- package/dist/adapters/index.d.cts +4 -94
- package/dist/adapters/index.d.mts +4 -94
- package/dist/adapters/index.d.ts +4 -94
- package/dist/adapters/index.mjs +2 -2
- package/dist/ego/index.cjs +22 -0
- package/dist/ego/index.d.cts +5 -0
- package/dist/ego/index.d.mts +5 -0
- package/dist/ego/index.d.ts +5 -0
- package/dist/ego/index.mjs +8 -0
- package/dist/evaluator/index.cjs +22 -584
- package/dist/evaluator/index.mjs +3 -564
- package/dist/geo/index.cjs +1 -1
- package/dist/geo/index.d.cts +4 -0
- package/dist/geo/index.d.mts +4 -0
- package/dist/geo/index.d.ts +4 -0
- package/dist/geo/index.mjs +2 -2
- package/dist/index.cjs +41 -441
- package/dist/index.d.cts +10 -198
- package/dist/index.d.mts +10 -198
- package/dist/index.d.ts +10 -198
- package/dist/index.mjs +7 -418
- package/dist/shared/device-passport.BGfRcQNh.mjs +565 -0
- package/dist/shared/{device-passport.BPd2-w8e.cjs → device-passport.BKtmLzPt.cjs} +4 -3
- package/dist/shared/{device-passport.BBf9OKap.mjs → device-passport.BPQj0qdT.mjs} +4 -3
- package/dist/shared/device-passport.CDS7xrj8.d.ts +469 -0
- package/dist/shared/device-passport.CfLes-lq.d.cts +469 -0
- package/dist/shared/device-passport.CsOWqPIr.d.cts +77 -0
- package/dist/shared/device-passport.D7pEnyp2.d.ts +77 -0
- package/dist/shared/{device-passport.CLaBZWUV.cjs → device-passport.D9xW6R0B.cjs} +4 -2
- package/dist/shared/device-passport.DirQmSd4.d.cts +47 -0
- package/dist/shared/device-passport.DirQmSd4.d.mts +47 -0
- package/dist/shared/device-passport.DirQmSd4.d.ts +47 -0
- package/dist/shared/device-passport.EK4RX7Xd.cjs +584 -0
- package/dist/shared/device-passport.ZBspe4ZW.d.mts +77 -0
- package/dist/shared/device-passport.dVEqzSEb.d.mts +469 -0
- package/dist/shared/{device-passport.NHw_f6HT.mjs → device-passport.gNo82fQ5.mjs} +5 -3
- package/dist/shared/device-passport.kekU28UM.mjs +705 -0
- package/dist/shared/device-passport.qL4tjwc0.cjs +718 -0
- package/dist/storage/index.d.cts +3 -34
- package/dist/storage/index.d.mts +3 -34
- package/dist/storage/index.d.ts +3 -34
- package/dist/types/index.d.cts +183 -1
- package/dist/types/index.d.mts +183 -1
- package/dist/types/index.d.ts +183 -1
- package/package.json +6 -1
package/dist/evaluator/index.cjs
CHANGED
|
@@ -1,571 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
US: [
|
|
5
|
-
"America/New_York",
|
|
6
|
-
"America/Chicago",
|
|
7
|
-
"America/Denver",
|
|
8
|
-
"America/Los_Angeles",
|
|
9
|
-
"America/Anchorage",
|
|
10
|
-
"America/Phoenix",
|
|
11
|
-
"America/Detroit",
|
|
12
|
-
"America/Indiana/Indianapolis",
|
|
13
|
-
"Pacific/Honolulu"
|
|
14
|
-
],
|
|
15
|
-
CA: [
|
|
16
|
-
"America/Toronto",
|
|
17
|
-
"America/Vancouver",
|
|
18
|
-
"America/Montreal",
|
|
19
|
-
"America/Edmonton",
|
|
20
|
-
"America/Winnipeg",
|
|
21
|
-
"America/Halifax",
|
|
22
|
-
"America/St_Johns"
|
|
23
|
-
],
|
|
24
|
-
GB: ["Europe/London"],
|
|
25
|
-
UK: ["Europe/London"],
|
|
26
|
-
DE: ["Europe/Berlin"],
|
|
27
|
-
FR: ["Europe/Paris"],
|
|
28
|
-
JP: ["Asia/Tokyo"],
|
|
29
|
-
CN: ["Asia/Shanghai", "Asia/Chongqing", "Asia/Urumqi", "Asia/Harbin"],
|
|
30
|
-
AU: [
|
|
31
|
-
"Australia/Sydney",
|
|
32
|
-
"Australia/Melbourne",
|
|
33
|
-
"Australia/Brisbane",
|
|
34
|
-
"Australia/Perth",
|
|
35
|
-
"Australia/Adelaide"
|
|
36
|
-
],
|
|
37
|
-
SG: ["Asia/Singapore"],
|
|
38
|
-
HK: ["Asia/Hong_Kong"],
|
|
39
|
-
TW: ["Asia/Taipei"],
|
|
40
|
-
KR: ["Asia/Seoul"],
|
|
41
|
-
NL: ["Europe/Amsterdam"],
|
|
42
|
-
SE: ["Europe/Stockholm"],
|
|
43
|
-
CH: ["Europe/Zurich"],
|
|
44
|
-
BR: ["America/Sao_Paulo", "America/Manaus", "America/Fortaleza"],
|
|
45
|
-
IN: ["Asia/Kolkata"],
|
|
46
|
-
RU: ["Europe/Moscow", "Asia/Yekaterinburg", "Asia/Novosibirsk", "Asia/Vladivostok"]
|
|
47
|
-
};
|
|
48
|
-
const COUNTRY_PRIMARY_LANGUAGES = {
|
|
49
|
-
US: ["en", "en-US"],
|
|
50
|
-
CA: ["en", "en-CA", "fr", "fr-CA"],
|
|
51
|
-
GB: ["en", "en-GB"],
|
|
52
|
-
UK: ["en", "en-GB"],
|
|
53
|
-
DE: ["de", "de-DE"],
|
|
54
|
-
FR: ["fr", "fr-FR"],
|
|
55
|
-
JP: ["ja", "ja-JP"],
|
|
56
|
-
CN: ["zh", "zh-CN", "zh-Hans"],
|
|
57
|
-
TW: ["zh", "zh-TW", "zh-Hant"],
|
|
58
|
-
HK: ["zh", "zh-HK", "en"],
|
|
59
|
-
KR: ["ko", "ko-KR"],
|
|
60
|
-
SG: ["en", "en-SG", "zh", "zh-SG"],
|
|
61
|
-
AU: ["en", "en-AU"],
|
|
62
|
-
NL: ["nl", "nl-NL", "en"],
|
|
63
|
-
SE: ["sv", "sv-SE", "en"],
|
|
64
|
-
CH: ["de", "de-CH", "fr", "fr-CH", "it", "it-CH"],
|
|
65
|
-
BR: ["pt", "pt-BR"],
|
|
66
|
-
IN: ["en", "en-IN", "hi", "hi-IN"],
|
|
67
|
-
RU: ["ru", "ru-RU"]
|
|
68
|
-
};
|
|
69
|
-
function detectOSBrowserContradictions(passport) {
|
|
70
|
-
const contradictions = [];
|
|
71
|
-
const { browser, os, headers, clientHints } = passport;
|
|
72
|
-
if (browser.name === "Safari" && os.platform !== "macOS") {
|
|
73
|
-
contradictions.push({
|
|
74
|
-
id: "ERR_SAFARI_NON_APPLE_PLATFORM",
|
|
75
|
-
category: "os_browser",
|
|
76
|
-
severity: "fatal",
|
|
77
|
-
description: `Safari modern releases are exclusive to macOS/iOS; cannot run on ${os.platform}`,
|
|
78
|
-
fields: ["browser.name", "os.platform"],
|
|
79
|
-
actual: { browser: browser.name, platform: os.platform },
|
|
80
|
-
expected: "macOS",
|
|
81
|
-
impactPenalty: 35
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
if (browser.name === "Chrome" || browser.name === "Edge") {
|
|
85
|
-
if (browser.majorVersion >= 110 && os.release) {
|
|
86
|
-
const isLegacyWin = /Windows (7|8|XP|Vista)/i.test(os.release);
|
|
87
|
-
if (isLegacyWin) {
|
|
88
|
-
contradictions.push({
|
|
89
|
-
id: "ERR_CHROME_LEGACY_WIN_UNSUPPORTED",
|
|
90
|
-
category: "os_browser",
|
|
91
|
-
severity: "fatal",
|
|
92
|
-
description: `Chromium ${browser.majorVersion} requires Windows 10 or later; unsupported on ${os.release}`,
|
|
93
|
-
fields: ["browser.majorVersion", "os.release"],
|
|
94
|
-
actual: { browserVersion: browser.majorVersion, osRelease: os.release },
|
|
95
|
-
expected: "Windows 10 or Windows 11",
|
|
96
|
-
impactPenalty: 30
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (browser.majorVersion >= 120 && os.platform === "macOS") {
|
|
101
|
-
const match = os.version.match(/^(\d+)/);
|
|
102
|
-
if (match && parseInt(match[1], 10) < 11) {
|
|
103
|
-
contradictions.push({
|
|
104
|
-
id: "ERR_CHROME_LEGACY_MACOS_UNSUPPORTED",
|
|
105
|
-
category: "os_browser",
|
|
106
|
-
severity: "high",
|
|
107
|
-
description: `Chromium ${browser.majorVersion} requires macOS 11.0 (Big Sur) or higher; got macOS ${os.version}`,
|
|
108
|
-
fields: ["browser.majorVersion", "os.version"],
|
|
109
|
-
actual: os.version,
|
|
110
|
-
expected: ">= 11.0.0",
|
|
111
|
-
impactPenalty: 25
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const ua = browser.userAgent || headers["user-agent"] || "";
|
|
117
|
-
if (os.platform === "macOS" && !/Macintosh|Mac OS X/i.test(ua)) {
|
|
118
|
-
contradictions.push({
|
|
119
|
-
id: "ERR_UA_OS_MACOS_MISMATCH",
|
|
120
|
-
category: "os_browser",
|
|
121
|
-
severity: "fatal",
|
|
122
|
-
description: "Operating system is macOS but User-Agent string does not indicate Macintosh architecture",
|
|
123
|
-
fields: ["os.platform", "browser.userAgent"],
|
|
124
|
-
actual: ua,
|
|
125
|
-
expected: 'Containing "Macintosh; Intel Mac OS X..."',
|
|
126
|
-
impactPenalty: 35
|
|
127
|
-
});
|
|
128
|
-
} else if (os.platform === "Windows" && !/Windows NT/i.test(ua)) {
|
|
129
|
-
contradictions.push({
|
|
130
|
-
id: "ERR_UA_OS_WINDOWS_MISMATCH",
|
|
131
|
-
category: "os_browser",
|
|
132
|
-
severity: "fatal",
|
|
133
|
-
description: "Operating system is Windows but User-Agent string does not contain Windows NT tag",
|
|
134
|
-
fields: ["os.platform", "browser.userAgent"],
|
|
135
|
-
actual: ua,
|
|
136
|
-
expected: 'Containing "Windows NT..."',
|
|
137
|
-
impactPenalty: 35
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
if (clientHints?.platformVersion && os.platform === "Windows") {
|
|
141
|
-
const pvMajor = parseInt(clientHints.platformVersion.split(".")[0], 10);
|
|
142
|
-
const isWin11 = /Windows 11/i.test(os.release);
|
|
143
|
-
const isWin10 = /Windows 10/i.test(os.release);
|
|
144
|
-
if (isWin11 && pvMajor < 13) {
|
|
145
|
-
contradictions.push({
|
|
146
|
-
id: "ERR_WIN11_PLATFORM_VERSION_MISMATCH",
|
|
147
|
-
category: "os_browser",
|
|
148
|
-
severity: "high",
|
|
149
|
-
description: `Windows 11 reports Client-Hints platformVersion >= 13.0.0 (e.g. 15.0.0), but received "${clientHints.platformVersion}"`,
|
|
150
|
-
fields: ["os.release", "clientHints.platformVersion"],
|
|
151
|
-
actual: clientHints.platformVersion,
|
|
152
|
-
expected: ">= 13.0.0 (typically 14.0.0 or 15.0.0)",
|
|
153
|
-
impactPenalty: 20
|
|
154
|
-
});
|
|
155
|
-
} else if (isWin10 && pvMajor >= 13) {
|
|
156
|
-
contradictions.push({
|
|
157
|
-
id: "ERR_WIN10_PLATFORM_VERSION_MISMATCH",
|
|
158
|
-
category: "os_browser",
|
|
159
|
-
severity: "high",
|
|
160
|
-
description: `Windows 10 reports Client-Hints platformVersion <= 10.0.0, but received Windows 11 platformVersion "${clientHints.platformVersion}"`,
|
|
161
|
-
fields: ["os.release", "clientHints.platformVersion"],
|
|
162
|
-
actual: clientHints.platformVersion,
|
|
163
|
-
expected: "10.0.0 (or 1.0.0 - 10.0.0)",
|
|
164
|
-
impactPenalty: 20
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return contradictions;
|
|
169
|
-
}
|
|
170
|
-
function detectHardwareContradictions(passport) {
|
|
171
|
-
const contradictions = [];
|
|
172
|
-
const { hardware, os, clientHints } = passport;
|
|
173
|
-
if (os.platform === "macOS") {
|
|
174
|
-
const isAppleSiliconGpu = /Apple M\d/i.test(hardware.gpu.renderer);
|
|
175
|
-
if (isAppleSiliconGpu && hardware.cpu.architecture === "x86" && clientHints?.architecture === "x86") {
|
|
176
|
-
contradictions.push({
|
|
177
|
-
id: "ERR_APPLE_SILICON_X86_CONTRADICTION",
|
|
178
|
-
category: "hardware_system",
|
|
179
|
-
severity: "high",
|
|
180
|
-
description: `Apple Silicon GPU detected (${hardware.gpu.renderer}) but native CPU architecture and Client Hint reported as x86`,
|
|
181
|
-
fields: ["hardware.gpu.renderer", "hardware.cpu.architecture", "clientHints.architecture"],
|
|
182
|
-
actual: { gpu: hardware.gpu.renderer, arch: hardware.cpu.architecture },
|
|
183
|
-
expected: "arm",
|
|
184
|
-
impactPenalty: 20
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
const gpuRenderer = hardware.gpu.renderer || "";
|
|
189
|
-
if (os.platform === "macOS") {
|
|
190
|
-
if (/GeForce RTX|GTX 16|RTX 30|RTX 40/i.test(gpuRenderer)) {
|
|
191
|
-
contradictions.push({
|
|
192
|
-
id: "ERR_NVIDIA_MODERN_ON_MACOS",
|
|
193
|
-
category: "hardware_system",
|
|
194
|
-
severity: "fatal",
|
|
195
|
-
description: `NVIDIA modern GPUs (${gpuRenderer}) have no driver support on modern macOS`,
|
|
196
|
-
fields: ["hardware.gpu.renderer", "os.platform"],
|
|
197
|
-
actual: gpuRenderer,
|
|
198
|
-
expected: "Apple M-Series GPU or AMD Radeon Pro (legacy Intel Mac)",
|
|
199
|
-
impactPenalty: 35
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
if (/Direct3D|D3D11|D3D12/i.test(gpuRenderer)) {
|
|
203
|
-
contradictions.push({
|
|
204
|
-
id: "ERR_DIRECT3D_ON_MACOS",
|
|
205
|
-
category: "hardware_system",
|
|
206
|
-
severity: "fatal",
|
|
207
|
-
description: `Direct3D/DirectX renderer (${gpuRenderer}) cannot exist on macOS WebGL pipeline`,
|
|
208
|
-
fields: ["hardware.gpu.renderer", "os.platform"],
|
|
209
|
-
actual: gpuRenderer,
|
|
210
|
-
expected: "Metal or OpenGL ANGLE backend",
|
|
211
|
-
impactPenalty: 30
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
} else if (os.platform === "Windows") {
|
|
215
|
-
if (/Apple M\d/i.test(gpuRenderer)) {
|
|
216
|
-
contradictions.push({
|
|
217
|
-
id: "ERR_APPLE_GPU_ON_WINDOWS",
|
|
218
|
-
category: "hardware_system",
|
|
219
|
-
severity: "fatal",
|
|
220
|
-
description: `Apple Silicon integrated graphics (${gpuRenderer}) cannot run under Microsoft Windows`,
|
|
221
|
-
fields: ["hardware.gpu.renderer", "os.platform"],
|
|
222
|
-
actual: gpuRenderer,
|
|
223
|
-
expected: "Intel UHD/Iris, NVIDIA GeForce, or AMD Radeon",
|
|
224
|
-
impactPenalty: 35
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (clientHints?.architecture && hardware.cpu.architecture) {
|
|
229
|
-
if (clientHints.architecture !== hardware.cpu.architecture) {
|
|
230
|
-
contradictions.push({
|
|
231
|
-
id: "ERR_CH_ARCH_HARDWARE_MISMATCH",
|
|
232
|
-
category: "hardware_system",
|
|
233
|
-
severity: "fatal",
|
|
234
|
-
description: `sec-ch-ua-arch ("${clientHints.architecture}") does not match CPU hardware concurrency profile ("${hardware.cpu.architecture}")`,
|
|
235
|
-
fields: ["clientHints.architecture", "hardware.cpu.architecture"],
|
|
236
|
-
actual: clientHints.architecture,
|
|
237
|
-
expected: hardware.cpu.architecture,
|
|
238
|
-
impactPenalty: 30
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
const dm = hardware.memory.deviceMemory;
|
|
243
|
-
if (dm !== void 0) {
|
|
244
|
-
const validDeviceMemoryValues = [0.25, 0.5, 1, 2, 4, 8];
|
|
245
|
-
if (dm <= 0 || !validDeviceMemoryValues.includes(dm)) {
|
|
246
|
-
if (dm > 8) {
|
|
247
|
-
contradictions.push({
|
|
248
|
-
id: "WARN_DEVICE_MEMORY_UNCAPPED",
|
|
249
|
-
category: "hardware_system",
|
|
250
|
-
severity: "medium",
|
|
251
|
-
description: `navigator.deviceMemory reported as ${dm} GiB; standard Chromium caps deviceMemory at 8 GiB`,
|
|
252
|
-
fields: ["hardware.memory.deviceMemory"],
|
|
253
|
-
actual: dm,
|
|
254
|
-
expected: "Capped at 8 (one of [0.25, 0.5, 1, 2, 4, 8])",
|
|
255
|
-
impactPenalty: 10
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
const { screen } = hardware;
|
|
261
|
-
if (screen.width > 0 && screen.height > 0) {
|
|
262
|
-
if (screen.availWidth > screen.width || screen.availHeight > screen.height) {
|
|
263
|
-
contradictions.push({
|
|
264
|
-
id: "ERR_SCREEN_AVAILABLE_EXCEEDS_TOTAL",
|
|
265
|
-
category: "hardware_system",
|
|
266
|
-
severity: "high",
|
|
267
|
-
description: `Screen available dimensions (${screen.availWidth}x${screen.availHeight}) exceed screen total bounds (${screen.width}x${screen.height})`,
|
|
268
|
-
fields: ["hardware.screen.availWidth", "hardware.screen.width"],
|
|
269
|
-
actual: { avail: `${screen.availWidth}x${screen.availHeight}`, total: `${screen.width}x${screen.height}` },
|
|
270
|
-
expected: "availWidth <= width && availHeight <= height",
|
|
271
|
-
impactPenalty: 20
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
if (passport.context?.viewport) {
|
|
275
|
-
const vp = passport.context.viewport;
|
|
276
|
-
if (vp.width > screen.width || vp.height > screen.height) {
|
|
277
|
-
contradictions.push({
|
|
278
|
-
id: "ERR_VIEWPORT_EXCEEDS_SCREEN",
|
|
279
|
-
category: "hardware_system",
|
|
280
|
-
severity: "fatal",
|
|
281
|
-
description: `Browser viewport (${vp.width}x${vp.height}) exceeds physical display screen (${screen.width}x${screen.height})`,
|
|
282
|
-
fields: ["context.viewport", "hardware.screen"],
|
|
283
|
-
actual: `${vp.width}x${vp.height}`,
|
|
284
|
-
expected: `<= ${screen.width}x${screen.height}`,
|
|
285
|
-
impactPenalty: 30
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return contradictions;
|
|
291
|
-
}
|
|
292
|
-
function detectGeoChronoContradictions(passport) {
|
|
293
|
-
const contradictions = [];
|
|
294
|
-
const { locale, headers } = passport;
|
|
295
|
-
const country = (locale.country || "").toUpperCase();
|
|
296
|
-
const timezone = locale.timezone || "";
|
|
297
|
-
if (country && timezone && COUNTRY_TIMEZONE_MAP[country]) {
|
|
298
|
-
const validTimezones = COUNTRY_TIMEZONE_MAP[country];
|
|
299
|
-
if (!validTimezones.includes(timezone)) {
|
|
300
|
-
contradictions.push({
|
|
301
|
-
id: "ERR_GEO_TIMEZONE_COUNTRY_MISMATCH",
|
|
302
|
-
category: "geo_chrono",
|
|
303
|
-
severity: "fatal",
|
|
304
|
-
description: `Timezone "${timezone}" does not belong to geographic territory "${country}"`,
|
|
305
|
-
fields: ["locale.timezone", "locale.country"],
|
|
306
|
-
actual: { country, timezone },
|
|
307
|
-
expected: validTimezones.join(", "),
|
|
308
|
-
impactPenalty: 40
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
const acceptLangHeader = headers["accept-language"] || "";
|
|
313
|
-
if (country && COUNTRY_PRIMARY_LANGUAGES[country]) {
|
|
314
|
-
const expectedLanguages = COUNTRY_PRIMARY_LANGUAGES[country];
|
|
315
|
-
const primaryLocaleLang = (locale.language || "").toLowerCase().split("-")[0];
|
|
316
|
-
const countryExpectsMatch = expectedLanguages.some(
|
|
317
|
-
(el) => primaryLocaleLang === el.toLowerCase().split("-")[0]
|
|
318
|
-
);
|
|
319
|
-
if (!countryExpectsMatch && primaryLocaleLang) {
|
|
320
|
-
contradictions.push({
|
|
321
|
-
id: "WARN_GEO_PRIMARY_LANGUAGE_MISMATCH",
|
|
322
|
-
category: "geo_chrono",
|
|
323
|
-
severity: "high",
|
|
324
|
-
description: `Primary locale language "${locale.language}" is anomalous for country code "${country}"`,
|
|
325
|
-
fields: ["locale.language", "locale.country"],
|
|
326
|
-
actual: locale.language,
|
|
327
|
-
expected: expectedLanguages.join(", "),
|
|
328
|
-
impactPenalty: 15
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
if (acceptLangHeader) {
|
|
332
|
-
const hasAnyExpectedInHeader = expectedLanguages.some(
|
|
333
|
-
(lang) => acceptLangHeader.toLowerCase().includes(lang.toLowerCase())
|
|
334
|
-
);
|
|
335
|
-
if (!hasAnyExpectedInHeader && !acceptLangHeader.toLowerCase().includes("en")) {
|
|
336
|
-
contradictions.push({
|
|
337
|
-
id: "WARN_ACCEPT_LANGUAGE_GEO_DETACHED",
|
|
338
|
-
category: "geo_chrono",
|
|
339
|
-
severity: "medium",
|
|
340
|
-
description: `Accept-Language header "${acceptLangHeader}" contains neither local language for "${country}" nor global fallback (en)`,
|
|
341
|
-
fields: ["headers.accept-language", "locale.country"],
|
|
342
|
-
actual: acceptLangHeader,
|
|
343
|
-
expected: `Includes one of: ${expectedLanguages.join(", ")} or en`,
|
|
344
|
-
impactPenalty: 12
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return contradictions;
|
|
350
|
-
}
|
|
351
|
-
function detectClientHintsContradictions(passport) {
|
|
352
|
-
const contradictions = [];
|
|
353
|
-
const { clientHints, os } = passport;
|
|
354
|
-
if (!clientHints) return contradictions;
|
|
355
|
-
if (clientHints.mobile === true && (os.platform === "macOS" || os.platform === "Windows")) {
|
|
356
|
-
contradictions.push({
|
|
357
|
-
id: "ERR_CH_MOBILE_ON_DESKTOP",
|
|
358
|
-
category: "client_hints",
|
|
359
|
-
severity: "fatal",
|
|
360
|
-
description: `Client-Hints indicates mobile=true (?1) on desktop operating system ${os.platform}`,
|
|
361
|
-
fields: ["clientHints.mobile", "os.platform"],
|
|
362
|
-
actual: { mobile: clientHints.mobile, platform: os.platform },
|
|
363
|
-
expected: "mobile: false (?0)",
|
|
364
|
-
impactPenalty: 35
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
if (clientHints.platform) {
|
|
368
|
-
const chPlat = clientHints.platform.toLowerCase();
|
|
369
|
-
const osPlat = os.platform.toLowerCase();
|
|
370
|
-
if (chPlat !== osPlat) {
|
|
371
|
-
contradictions.push({
|
|
372
|
-
id: "ERR_CH_PLATFORM_OS_MISMATCH",
|
|
373
|
-
category: "client_hints",
|
|
374
|
-
severity: "fatal",
|
|
375
|
-
description: `sec-ch-ua-platform "${clientHints.platform}" contradicts OS platform "${os.platform}"`,
|
|
376
|
-
fields: ["clientHints.platform", "os.platform"],
|
|
377
|
-
actual: clientHints.platform,
|
|
378
|
-
expected: os.platform,
|
|
379
|
-
impactPenalty: 35
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if ((os.platform === "macOS" || os.platform === "Windows") && clientHints.model && clientHints.model.trim() !== "") {
|
|
384
|
-
contradictions.push({
|
|
385
|
-
id: "ERR_CH_NON_EMPTY_DESKTOP_MODEL",
|
|
386
|
-
category: "client_hints",
|
|
387
|
-
severity: "high",
|
|
388
|
-
description: `Desktop Chromium mandates empty sec-ch-ua-model (""), received "${clientHints.model}"`,
|
|
389
|
-
fields: ["clientHints.model", "os.platform"],
|
|
390
|
-
actual: clientHints.model,
|
|
391
|
-
expected: '"" (empty string for desktop)',
|
|
392
|
-
impactPenalty: 25
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
if (clientHints.bitness && clientHints.bitness !== "64") {
|
|
396
|
-
if (os.platform === "macOS") {
|
|
397
|
-
contradictions.push({
|
|
398
|
-
id: "ERR_CH_32BIT_ON_MODERN_MACOS",
|
|
399
|
-
category: "client_hints",
|
|
400
|
-
severity: "fatal",
|
|
401
|
-
description: `macOS has discontinued 32-bit binaries since macOS 10.15; bitness reported as "${clientHints.bitness}"`,
|
|
402
|
-
fields: ["clientHints.bitness", "os.platform"],
|
|
403
|
-
actual: clientHints.bitness,
|
|
404
|
-
expected: '"64"',
|
|
405
|
-
impactPenalty: 30
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return contradictions;
|
|
410
|
-
}
|
|
411
|
-
function detectAllContradictions(passport) {
|
|
412
|
-
return [
|
|
413
|
-
...detectOSBrowserContradictions(passport),
|
|
414
|
-
...detectHardwareContradictions(passport),
|
|
415
|
-
...detectGeoChronoContradictions(passport),
|
|
416
|
-
...detectClientHintsContradictions(passport)
|
|
417
|
-
];
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const BROWSER_BASELINE_FREQUENCIES = {
|
|
421
|
-
"sec-ch-ua": 0.12,
|
|
422
|
-
"sec-ch-ua-mobile": 0.04,
|
|
423
|
-
"sec-ch-ua-platform": 0.08,
|
|
424
|
-
"upgrade-insecure-requests": 0.04,
|
|
425
|
-
"user-agent": 0.22,
|
|
426
|
-
"accept": 0.14,
|
|
427
|
-
"sec-fetch-site": 0.05,
|
|
428
|
-
"sec-fetch-mode": 0.05,
|
|
429
|
-
"sec-fetch-user": 0.03,
|
|
430
|
-
"sec-fetch-dest": 0.05,
|
|
431
|
-
"accept-encoding": 0.08,
|
|
432
|
-
"accept-language": 0.1
|
|
433
|
-
};
|
|
434
|
-
const OPTIMAL_CHAR_ENTROPY_MIN = 4.6;
|
|
435
|
-
const OPTIMAL_CHAR_ENTROPY_MAX = 5.6;
|
|
436
|
-
const OPTIMAL_ENTROPY_MIN = OPTIMAL_CHAR_ENTROPY_MIN;
|
|
437
|
-
const OPTIMAL_ENTROPY_MAX = OPTIMAL_CHAR_ENTROPY_MAX;
|
|
438
|
-
const BASELINE_OPTIMAL_ENTROPY = 5.25;
|
|
439
|
-
const MIN_STRUCTURAL_ENTROPY = 3.5;
|
|
440
|
-
function calculateShannonEntropy(data) {
|
|
441
|
-
if (!data || data.length === 0) return 0;
|
|
442
|
-
const frequencies = /* @__PURE__ */ new Map();
|
|
443
|
-
const total = data.length;
|
|
444
|
-
for (let i = 0; i < total; i++) {
|
|
445
|
-
const char = data[i];
|
|
446
|
-
frequencies.set(char, (frequencies.get(char) || 0) + 1);
|
|
447
|
-
}
|
|
448
|
-
let entropy = 0;
|
|
449
|
-
for (const count of frequencies.values()) {
|
|
450
|
-
const p = count / total;
|
|
451
|
-
if (p > 0) {
|
|
452
|
-
entropy -= p * Math.log2(p);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
return Number(entropy.toFixed(4));
|
|
456
|
-
}
|
|
457
|
-
function calculateStructuralEntropy(headers) {
|
|
458
|
-
const normalizedKeys = Object.keys(headers).map((k) => k.toLowerCase());
|
|
459
|
-
const baselineKeys = Object.keys(BROWSER_BASELINE_FREQUENCIES);
|
|
460
|
-
if (normalizedKeys.length === 0) return 0;
|
|
461
|
-
let presentCount = 0;
|
|
462
|
-
for (const key of baselineKeys) {
|
|
463
|
-
if (normalizedKeys.includes(key)) {
|
|
464
|
-
presentCount++;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
const presenceRatio = presentCount / baselineKeys.length;
|
|
468
|
-
const countFactor = Math.min(normalizedKeys.length / 10, 1);
|
|
469
|
-
return Number((presenceRatio * 3 + countFactor * 2).toFixed(4));
|
|
470
|
-
}
|
|
471
|
-
function calculateKLDivergence(headers) {
|
|
472
|
-
const normalizedHeaders = {};
|
|
473
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
474
|
-
normalizedHeaders[k.toLowerCase()] = v;
|
|
475
|
-
}
|
|
476
|
-
const baselineKeys = Object.keys(BROWSER_BASELINE_FREQUENCIES);
|
|
477
|
-
let totalLength = 0;
|
|
478
|
-
const observedLengths = {};
|
|
479
|
-
for (const key of baselineKeys) {
|
|
480
|
-
const val = normalizedHeaders[key] || "";
|
|
481
|
-
const len = val.length + key.length;
|
|
482
|
-
observedLengths[key] = len;
|
|
483
|
-
totalLength += len;
|
|
484
|
-
}
|
|
485
|
-
if (totalLength === 0) return 3.5;
|
|
486
|
-
let klDiv = 0;
|
|
487
|
-
const epsilon = 1e-4;
|
|
488
|
-
for (const key of baselineKeys) {
|
|
489
|
-
const p = (observedLengths[key] || 0) / totalLength + epsilon;
|
|
490
|
-
const q = BROWSER_BASELINE_FREQUENCIES[key] || epsilon;
|
|
491
|
-
klDiv += p * Math.log2(p / q);
|
|
492
|
-
}
|
|
493
|
-
return Number(Math.max(0, klDiv).toFixed(4));
|
|
494
|
-
}
|
|
495
|
-
function analyzeHeaderEntropy(headers) {
|
|
496
|
-
if (!headers || Object.keys(headers).length === 0) {
|
|
497
|
-
return {
|
|
498
|
-
entropyScore: 0,
|
|
499
|
-
estimatedBits: 0,
|
|
500
|
-
klDivergence: 4,
|
|
501
|
-
anomaly: "too_low",
|
|
502
|
-
collisionRisk: "high",
|
|
503
|
-
verdict: "Completely static or empty headers; trivially detectable as automated bot.",
|
|
504
|
-
details: {
|
|
505
|
-
characterEntropy: 0,
|
|
506
|
-
structuralEntropy: 0,
|
|
507
|
-
baselineEntropy: BASELINE_OPTIMAL_ENTROPY,
|
|
508
|
-
deltaFromOptimal: BASELINE_OPTIMAL_ENTROPY
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
const serialized = Object.entries(headers).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k.toLowerCase()}: ${v}`).join("\r\n");
|
|
513
|
-
const charEntropy = calculateShannonEntropy(serialized);
|
|
514
|
-
const structEntropy = calculateStructuralEntropy(headers);
|
|
515
|
-
const klDiv = calculateKLDivergence(headers);
|
|
516
|
-
const headerCount = Object.keys(headers).length;
|
|
517
|
-
let anomaly = "optimal";
|
|
518
|
-
let penalty = 0;
|
|
519
|
-
if (headerCount < 4 || structEntropy < MIN_STRUCTURAL_ENTROPY || charEntropy < OPTIMAL_CHAR_ENTROPY_MIN) {
|
|
520
|
-
anomaly = "too_low";
|
|
521
|
-
const structDeficit = Math.max(0, MIN_STRUCTURAL_ENTROPY - structEntropy);
|
|
522
|
-
penalty += structDeficit * 15;
|
|
523
|
-
const charDeficit = Math.max(0, OPTIMAL_CHAR_ENTROPY_MIN - charEntropy);
|
|
524
|
-
penalty += charDeficit * 25;
|
|
525
|
-
if (headerCount < 4) {
|
|
526
|
-
penalty += (4 - headerCount) * 15;
|
|
527
|
-
}
|
|
528
|
-
} else if (charEntropy > OPTIMAL_CHAR_ENTROPY_MAX) {
|
|
529
|
-
anomaly = "too_high";
|
|
530
|
-
const excess = charEntropy - OPTIMAL_CHAR_ENTROPY_MAX;
|
|
531
|
-
penalty += excess * 30;
|
|
532
|
-
}
|
|
533
|
-
penalty += Math.min(klDiv * 12, 30);
|
|
534
|
-
const rawScore = 100 - penalty;
|
|
535
|
-
const entropyScore = Number(Math.max(0, Math.min(100, rawScore)).toFixed(1));
|
|
536
|
-
let collisionRisk = "very_low";
|
|
537
|
-
let verdict = "Realistic entropy distribution matching authentic browser telemetry.";
|
|
538
|
-
if (anomaly === "too_low") {
|
|
539
|
-
collisionRisk = "high";
|
|
540
|
-
verdict = "Under-randomized or missing essential browser headers; high risk of bot detection.";
|
|
541
|
-
} else if (anomaly === "too_high") {
|
|
542
|
-
collisionRisk = "moderate";
|
|
543
|
-
verdict = "Hyper-randomized noise detected; synthetic fingerprint spoofing risk.";
|
|
544
|
-
} else if (entropyScore >= 85) {
|
|
545
|
-
collisionRisk = "very_low";
|
|
546
|
-
verdict = "Optimal information entropy perfectly consistent with real Chromium/Safari sessions.";
|
|
547
|
-
} else {
|
|
548
|
-
collisionRisk = "low";
|
|
549
|
-
verdict = "Acceptable entropy band with minor structural divergence.";
|
|
550
|
-
}
|
|
551
|
-
return {
|
|
552
|
-
entropyScore,
|
|
553
|
-
estimatedBits: charEntropy,
|
|
554
|
-
klDivergence: klDiv,
|
|
555
|
-
anomaly,
|
|
556
|
-
collisionRisk,
|
|
557
|
-
verdict,
|
|
558
|
-
details: {
|
|
559
|
-
characterEntropy: charEntropy,
|
|
560
|
-
structuralEntropy: structEntropy,
|
|
561
|
-
baselineEntropy: BASELINE_OPTIMAL_ENTROPY,
|
|
562
|
-
deltaFromOptimal: Number(Math.abs(charEntropy - BASELINE_OPTIMAL_ENTROPY).toFixed(4))
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
}
|
|
3
|
+
const entropy = require('../shared/device-passport.EK4RX7Xd.cjs');
|
|
566
4
|
|
|
567
5
|
function evaluateGeoAlignment(passport) {
|
|
568
|
-
const geoContradictions = detectGeoChronoContradictions(passport);
|
|
6
|
+
const geoContradictions = entropy.detectGeoChronoContradictions(passport);
|
|
569
7
|
let score = 100;
|
|
570
8
|
for (const c of geoContradictions) {
|
|
571
9
|
score -= c.impactPenalty;
|
|
@@ -581,9 +19,9 @@ function evaluateGeoAlignment(passport) {
|
|
|
581
19
|
return Math.max(0, Math.min(100, score));
|
|
582
20
|
}
|
|
583
21
|
function evaluateCoherence(passport) {
|
|
584
|
-
const contradictions = detectAllContradictions(passport);
|
|
22
|
+
const contradictions = entropy.detectAllContradictions(passport);
|
|
585
23
|
const geoAlignmentScore = evaluateGeoAlignment(passport);
|
|
586
|
-
const entropyResult = analyzeHeaderEntropy(passport.headers || {});
|
|
24
|
+
const entropyResult = entropy.analyzeHeaderEntropy(passport.headers || {});
|
|
587
25
|
const entropyScore = entropyResult.entropyScore;
|
|
588
26
|
let totalPenalty = 0;
|
|
589
27
|
let fatalCount = 0;
|
|
@@ -679,24 +117,24 @@ function evaluatePassport(passport) {
|
|
|
679
117
|
return evaluateCoherence(passport);
|
|
680
118
|
}
|
|
681
119
|
|
|
682
|
-
exports.BASELINE_OPTIMAL_ENTROPY = BASELINE_OPTIMAL_ENTROPY;
|
|
683
|
-
exports.BROWSER_BASELINE_FREQUENCIES = BROWSER_BASELINE_FREQUENCIES;
|
|
684
|
-
exports.COUNTRY_PRIMARY_LANGUAGES = COUNTRY_PRIMARY_LANGUAGES;
|
|
685
|
-
exports.COUNTRY_TIMEZONE_MAP = COUNTRY_TIMEZONE_MAP;
|
|
686
|
-
exports.MIN_STRUCTURAL_ENTROPY = MIN_STRUCTURAL_ENTROPY;
|
|
687
|
-
exports.OPTIMAL_CHAR_ENTROPY_MAX = OPTIMAL_CHAR_ENTROPY_MAX;
|
|
688
|
-
exports.OPTIMAL_CHAR_ENTROPY_MIN = OPTIMAL_CHAR_ENTROPY_MIN;
|
|
689
|
-
exports.OPTIMAL_ENTROPY_MAX = OPTIMAL_ENTROPY_MAX;
|
|
690
|
-
exports.OPTIMAL_ENTROPY_MIN = OPTIMAL_ENTROPY_MIN;
|
|
691
|
-
exports.analyzeHeaderEntropy = analyzeHeaderEntropy;
|
|
692
|
-
exports.calculateKLDivergence = calculateKLDivergence;
|
|
693
|
-
exports.calculateShannonEntropy = calculateShannonEntropy;
|
|
694
|
-
exports.calculateStructuralEntropy = calculateStructuralEntropy;
|
|
695
|
-
exports.detectAllContradictions = detectAllContradictions;
|
|
696
|
-
exports.detectClientHintsContradictions = detectClientHintsContradictions;
|
|
697
|
-
exports.detectGeoChronoContradictions = detectGeoChronoContradictions;
|
|
698
|
-
exports.detectHardwareContradictions = detectHardwareContradictions;
|
|
699
|
-
exports.detectOSBrowserContradictions = detectOSBrowserContradictions;
|
|
120
|
+
exports.BASELINE_OPTIMAL_ENTROPY = entropy.BASELINE_OPTIMAL_ENTROPY;
|
|
121
|
+
exports.BROWSER_BASELINE_FREQUENCIES = entropy.BROWSER_BASELINE_FREQUENCIES;
|
|
122
|
+
exports.COUNTRY_PRIMARY_LANGUAGES = entropy.COUNTRY_PRIMARY_LANGUAGES;
|
|
123
|
+
exports.COUNTRY_TIMEZONE_MAP = entropy.COUNTRY_TIMEZONE_MAP;
|
|
124
|
+
exports.MIN_STRUCTURAL_ENTROPY = entropy.MIN_STRUCTURAL_ENTROPY;
|
|
125
|
+
exports.OPTIMAL_CHAR_ENTROPY_MAX = entropy.OPTIMAL_CHAR_ENTROPY_MAX;
|
|
126
|
+
exports.OPTIMAL_CHAR_ENTROPY_MIN = entropy.OPTIMAL_CHAR_ENTROPY_MIN;
|
|
127
|
+
exports.OPTIMAL_ENTROPY_MAX = entropy.OPTIMAL_ENTROPY_MAX;
|
|
128
|
+
exports.OPTIMAL_ENTROPY_MIN = entropy.OPTIMAL_ENTROPY_MIN;
|
|
129
|
+
exports.analyzeHeaderEntropy = entropy.analyzeHeaderEntropy;
|
|
130
|
+
exports.calculateKLDivergence = entropy.calculateKLDivergence;
|
|
131
|
+
exports.calculateShannonEntropy = entropy.calculateShannonEntropy;
|
|
132
|
+
exports.calculateStructuralEntropy = entropy.calculateStructuralEntropy;
|
|
133
|
+
exports.detectAllContradictions = entropy.detectAllContradictions;
|
|
134
|
+
exports.detectClientHintsContradictions = entropy.detectClientHintsContradictions;
|
|
135
|
+
exports.detectGeoChronoContradictions = entropy.detectGeoChronoContradictions;
|
|
136
|
+
exports.detectHardwareContradictions = entropy.detectHardwareContradictions;
|
|
137
|
+
exports.detectOSBrowserContradictions = entropy.detectOSBrowserContradictions;
|
|
700
138
|
exports.evaluateCoherence = evaluateCoherence;
|
|
701
139
|
exports.evaluateGeoAlignment = evaluateGeoAlignment;
|
|
702
140
|
exports.evaluatePassport = evaluatePassport;
|