device-passport 1.0.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +110 -2
  3. package/assets/ego-profile-manager.png +0 -0
  4. package/dist/adapters/index.cjs +19 -0
  5. package/dist/adapters/index.d.cts +66 -0
  6. package/dist/adapters/index.d.mts +66 -0
  7. package/dist/adapters/index.d.ts +66 -0
  8. package/dist/adapters/index.mjs +3 -0
  9. package/dist/ego/index.cjs +22 -0
  10. package/dist/ego/index.d.cts +5 -0
  11. package/dist/ego/index.d.mts +5 -0
  12. package/dist/ego/index.d.ts +5 -0
  13. package/dist/ego/index.mjs +8 -0
  14. package/dist/evaluator/index.cjs +140 -0
  15. package/dist/evaluator/index.d.cts +204 -0
  16. package/dist/evaluator/index.d.mts +204 -0
  17. package/dist/evaluator/index.d.ts +204 -0
  18. package/dist/evaluator/index.mjs +119 -0
  19. package/dist/geo/index.cjs +50 -0
  20. package/dist/geo/index.d.cts +213 -0
  21. package/dist/geo/index.d.mts +213 -0
  22. package/dist/geo/index.d.ts +213 -0
  23. package/dist/geo/index.mjs +18 -0
  24. package/dist/index.cjs +110 -3940
  25. package/dist/index.d.cts +45 -1091
  26. package/dist/index.d.mts +45 -1091
  27. package/dist/index.d.ts +45 -1091
  28. package/dist/index.mjs +11 -3849
  29. package/dist/shared/device-passport.BGfRcQNh.mjs +565 -0
  30. package/dist/shared/device-passport.BKtmLzPt.cjs +1266 -0
  31. package/dist/shared/device-passport.BPQj0qdT.mjs +1238 -0
  32. package/dist/shared/device-passport.CDS7xrj8.d.ts +469 -0
  33. package/dist/shared/device-passport.C_F1vBE8.d.cts +79 -0
  34. package/dist/shared/device-passport.C_F1vBE8.d.mts +79 -0
  35. package/dist/shared/device-passport.C_F1vBE8.d.ts +79 -0
  36. package/dist/shared/device-passport.CfLes-lq.d.cts +469 -0
  37. package/dist/shared/device-passport.Cjxlv8xI.cjs +245 -0
  38. package/dist/shared/device-passport.CsOWqPIr.d.cts +77 -0
  39. package/dist/shared/device-passport.CyQtcZeD.mjs +237 -0
  40. package/dist/shared/device-passport.D7pEnyp2.d.ts +77 -0
  41. package/dist/shared/device-passport.D9xW6R0B.cjs +1571 -0
  42. package/dist/shared/device-passport.DirQmSd4.d.cts +47 -0
  43. package/dist/shared/device-passport.DirQmSd4.d.mts +47 -0
  44. package/dist/shared/device-passport.DirQmSd4.d.ts +47 -0
  45. package/dist/shared/device-passport.EK4RX7Xd.cjs +584 -0
  46. package/dist/shared/device-passport.ZBspe4ZW.d.mts +77 -0
  47. package/dist/shared/device-passport.dVEqzSEb.d.mts +469 -0
  48. package/dist/shared/device-passport.gNo82fQ5.mjs +1537 -0
  49. package/dist/shared/device-passport.kekU28UM.mjs +705 -0
  50. package/dist/shared/device-passport.qL4tjwc0.cjs +718 -0
  51. package/dist/storage/index.cjs +10 -0
  52. package/dist/storage/index.d.cts +97 -0
  53. package/dist/storage/index.d.mts +97 -0
  54. package/dist/storage/index.d.ts +97 -0
  55. package/dist/storage/index.mjs +3 -0
  56. package/dist/types/index.cjs +2 -0
  57. package/dist/types/index.d.cts +397 -0
  58. package/dist/types/index.d.mts +397 -0
  59. package/dist/types/index.d.ts +397 -0
  60. package/dist/types/index.mjs +1 -0
  61. package/package.json +32 -2
@@ -0,0 +1,584 @@
1
+ 'use strict';
2
+
3
+ const COUNTRY_TIMEZONE_MAP = {
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
+ }
566
+
567
+ exports.BASELINE_OPTIMAL_ENTROPY = BASELINE_OPTIMAL_ENTROPY;
568
+ exports.BROWSER_BASELINE_FREQUENCIES = BROWSER_BASELINE_FREQUENCIES;
569
+ exports.COUNTRY_PRIMARY_LANGUAGES = COUNTRY_PRIMARY_LANGUAGES;
570
+ exports.COUNTRY_TIMEZONE_MAP = COUNTRY_TIMEZONE_MAP;
571
+ exports.MIN_STRUCTURAL_ENTROPY = MIN_STRUCTURAL_ENTROPY;
572
+ exports.OPTIMAL_CHAR_ENTROPY_MAX = OPTIMAL_CHAR_ENTROPY_MAX;
573
+ exports.OPTIMAL_CHAR_ENTROPY_MIN = OPTIMAL_CHAR_ENTROPY_MIN;
574
+ exports.OPTIMAL_ENTROPY_MAX = OPTIMAL_ENTROPY_MAX;
575
+ exports.OPTIMAL_ENTROPY_MIN = OPTIMAL_ENTROPY_MIN;
576
+ exports.analyzeHeaderEntropy = analyzeHeaderEntropy;
577
+ exports.calculateKLDivergence = calculateKLDivergence;
578
+ exports.calculateShannonEntropy = calculateShannonEntropy;
579
+ exports.calculateStructuralEntropy = calculateStructuralEntropy;
580
+ exports.detectAllContradictions = detectAllContradictions;
581
+ exports.detectClientHintsContradictions = detectClientHintsContradictions;
582
+ exports.detectGeoChronoContradictions = detectGeoChronoContradictions;
583
+ exports.detectHardwareContradictions = detectHardwareContradictions;
584
+ exports.detectOSBrowserContradictions = detectOSBrowserContradictions;
@@ -0,0 +1,77 @@
1
+ import { DevicePassport } from '../types/index.mjs';
2
+
3
+ interface PlaywrightContextOptions {
4
+ userAgent: string;
5
+ viewport: {
6
+ width: number;
7
+ height: number;
8
+ };
9
+ deviceScaleFactor: number;
10
+ locale: string;
11
+ timezoneId: string;
12
+ geolocation?: {
13
+ latitude: number;
14
+ longitude: number;
15
+ accuracy?: number;
16
+ };
17
+ permissions?: string[];
18
+ extraHTTPHeaders: Record<string, string>;
19
+ hasTouch: boolean;
20
+ isMobile: boolean;
21
+ colorScheme?: 'light' | 'dark' | 'no-preference';
22
+ }
23
+ /**
24
+ * Builds Playwright BrowserContext options directly from a DevicePassport.
25
+ * Ready for `browser.newContext(getPlaywrightContext(passport))`.
26
+ */
27
+ declare function getPlaywrightContext(passport: DevicePassport): PlaywrightContextOptions;
28
+ /**
29
+ * Generates an in-page stealth script to patch runtime JavaScript APIs
30
+ * (navigator.webdriver, hardwareConcurrency, deviceMemory, WebGL unmasked vendor/renderer,
31
+ * navigator.userAgentData Client Hints, and Screen dimensions).
32
+ *
33
+ * 🔴 API-level spoofing is not enough on its own: `canvas.toDataURL()` and `gl.readPixels()`
34
+ * still return the **real** framebuffer, so a headless browser exposes SwiftShader while
35
+ * its UA claims a discrete GPU — and every persona on one machine hashes identically.
36
+ * The pixel-protection script is therefore appended here, so existing users get
37
+ * read-back-safe personas without changing their call sites.
38
+ *
39
+ * The noise is seeded from the passport seed: stable per account, different per account.
40
+ */
41
+ declare function getStealthScript(passport: DevicePassport): string;
42
+ /**
43
+ * Applies stealth injections and extra headers to a Playwright BrowserContext.
44
+ *
45
+ * @param context Playwright BrowserContext instance
46
+ * @param passport DevicePassport instance
47
+ */
48
+ declare function applyToPlaywrightContext(context: any, passport: DevicePassport): Promise<void>;
49
+
50
+ interface PuppeteerEmulationOptions {
51
+ launchArgs: string[];
52
+ defaultViewport: {
53
+ width: number;
54
+ height: number;
55
+ deviceScaleFactor: number;
56
+ isMobile: boolean;
57
+ hasTouch: boolean;
58
+ };
59
+ userAgent: string;
60
+ locale: string;
61
+ timezoneId: string;
62
+ extraHTTPHeaders: Record<string, string>;
63
+ }
64
+ /**
65
+ * Returns Puppeteer launch args and page emulation parameters matching the DevicePassport.
66
+ */
67
+ declare function getPuppeteerArgs(passport: DevicePassport): PuppeteerEmulationOptions;
68
+ /**
69
+ * Applies stealth injections, headers, viewport, and timezone emulation to a Puppeteer Page.
70
+ *
71
+ * @param page Puppeteer Page instance
72
+ * @param passport DevicePassport instance
73
+ */
74
+ declare function applyToPuppeteerPage(page: any, passport: DevicePassport): Promise<void>;
75
+
76
+ export { applyToPlaywrightContext as b, applyToPuppeteerPage as c, getPuppeteerArgs as d, getStealthScript as e, getPlaywrightContext as g };
77
+ export type { PlaywrightContextOptions as P, PuppeteerEmulationOptions as a };