@npm-pipl/device-intelligence 1.1.1 → 1.1.3

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Device Intelligence SDK v1.1.1
2
+ * Device Intelligence SDK v1.1.3
3
3
  * (c) 2026 Your Company
4
4
  * Released under the MIT License
5
5
  */
@@ -5521,7 +5521,19 @@ ZwIDAQAB
5521
5521
  // Check webdriver property
5522
5522
  if (nav.webdriver === true)
5523
5523
  return true;
5524
- // Check for automation-related properties
5524
+ // Detect navigator.webdriver tampering (stealth plugins redefine the getter)
5525
+ try {
5526
+ const desc = Object.getOwnPropertyDescriptor(Navigator.prototype, "webdriver");
5527
+ if (desc?.get) {
5528
+ const src = Function.prototype.toString.call(desc.get);
5529
+ if (!/\[native code\]/.test(src))
5530
+ return true;
5531
+ }
5532
+ }
5533
+ catch {
5534
+ /* ignore */
5535
+ }
5536
+ // Check for automation-related properties on window / document
5525
5537
  const automationProps = [
5526
5538
  "__selenium_unwrapped",
5527
5539
  "__webdriver_evaluate",
@@ -5540,12 +5552,22 @@ ZwIDAQAB
5540
5552
  "$chrome_asyncScriptInfo",
5541
5553
  "__nightmare",
5542
5554
  "__puppeteer_evaluation_script__",
5543
- "__playwright_evaluation_script__"
5555
+ "__playwright_evaluation_script__",
5556
+ "__playwright",
5557
+ "__pw_manual"
5544
5558
  ];
5545
5559
  for (const prop of automationProps) {
5546
5560
  if (prop in win || prop in document)
5547
5561
  return true;
5548
5562
  }
5563
+ // Dynamic $cdc_ scan — ChromeDriver injects keys with varying suffixes
5564
+ try {
5565
+ if (Object.keys(win).some((k) => k.startsWith("$cdc_")))
5566
+ return true;
5567
+ }
5568
+ catch {
5569
+ /* ignore */
5570
+ }
5549
5571
  // Check for PhantomJS
5550
5572
  if ("callPhantom" in win || "_phantom" in win || "phantom" in win) {
5551
5573
  return true;
@@ -5555,8 +5577,44 @@ ZwIDAQAB
5555
5577
  if (ua.includes("headless") || ua.includes("phantomjs")) {
5556
5578
  return true;
5557
5579
  }
5558
- // Check for CriOS (Chrome on iOS), which is legitimate
5559
- // but check for other weird UA patterns
5580
+ // WebGL renderer headless Chromium uses SwiftShader (software renderer)
5581
+ try {
5582
+ const canvas = document.createElement("canvas");
5583
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
5584
+ if (gl) {
5585
+ const dbg = gl.getExtension("WEBGL_debug_renderer_info");
5586
+ if (dbg) {
5587
+ const renderer = gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL);
5588
+ if (typeof renderer === "string" && /swiftshader/i.test(renderer)) {
5589
+ return true;
5590
+ }
5591
+ }
5592
+ }
5593
+ }
5594
+ catch {
5595
+ /* ignore */
5596
+ }
5597
+ // Plugins — headless browsers report 0 plugins; real Chrome always has >=1
5598
+ try {
5599
+ if (nav.plugins && nav.plugins.length === 0 && !ua.includes("firefox")) {
5600
+ return true;
5601
+ }
5602
+ }
5603
+ catch {
5604
+ /* ignore */
5605
+ }
5606
+ // Chrome object inconsistency — Chrome UA but missing window.chrome
5607
+ try {
5608
+ if (ua.includes("chrome") && !("chrome" in win)) {
5609
+ return true;
5610
+ }
5611
+ if (win.chrome && !win.chrome.runtime && !win.chrome.csi) {
5612
+ return true;
5613
+ }
5614
+ }
5615
+ catch {
5616
+ /* ignore */
5617
+ }
5560
5618
  return false;
5561
5619
  }
5562
5620
  };
@@ -5718,67 +5776,103 @@ ZwIDAQAB
5718
5776
  const incognitoMode = {
5719
5777
  category: "risk",
5720
5778
  key: "incognito_mode",
5721
- version: 1,
5779
+ version: 2,
5722
5780
  cacheTtlMs: 300000,
5723
- timeoutMs: 2000,
5781
+ timeoutMs: 3000,
5724
5782
  collect: async () => {
5725
- // Safari: Check storage quota (with timeout to prevent hanging)
5783
+ const ua = (navigator.userAgent || "").toLowerCase();
5784
+ const isChromium = ua.includes("chrome") || ua.includes("chromium");
5785
+ const isFirefox = ua.includes("firefox");
5786
+ // ── Storage quota ──────────────────────────────────────────────────
5787
+ // Normal mode: quota ≈ 50–60 % of disk (tens to hundreds of GB).
5788
+ // Incognito: quota is RAM-based and much smaller.
5789
+ // • Safari / Firefox private: typically < 200 MB
5790
+ // • Chrome incognito: scales with device RAM (1–16 GB)
5726
5791
  try {
5727
- const storage = await withTimeout(navigator.storage.estimate(), 1000);
5728
- if (storage &&
5729
- storage.quota !== undefined &&
5730
- storage.quota < 120000000) {
5731
- return {
5732
- is_private: true,
5733
- confidence: "high",
5734
- method: "storage_quota"
5735
- };
5792
+ const est = await withTimeout(navigator.storage.estimate(), 1500);
5793
+ if (est && est.quota !== undefined) {
5794
+ const quota = est.quota;
5795
+ // Tier 1: very small quota catches Safari & Firefox private
5796
+ if (quota < 200000000) {
5797
+ return {
5798
+ is_private: true,
5799
+ confidence: "high",
5800
+ method: "storage_quota"
5801
+ };
5802
+ }
5803
+ if (isChromium) {
5804
+ // Tier 2: compare quota to device memory (most reliable for Chrome).
5805
+ // In incognito, quota is RAM-based → quota ≤ ~RAM.
5806
+ // In normal mode, quota is disk-based → quota >> RAM.
5807
+ // navigator.deviceMemory is capped at 8 (GB) and coarsened,
5808
+ // so we use a 3× multiplier to cover the gap between the
5809
+ // capped value and real RAM. Even on a 64 GB RAM machine
5810
+ // (deviceMemory=8), incognito quota (~16 GB) < 3×8 GB = 24 GB,
5811
+ // while normal quota on a 128 GB SSD (~64 GB) > 24 GB.
5812
+ const deviceMemGB = navigator.deviceMemory;
5813
+ if (deviceMemGB && quota < deviceMemGB * 3 * 1024 * 1024 * 1024) {
5814
+ return {
5815
+ is_private: true,
5816
+ confidence: "medium",
5817
+ method: "storage_quota_vs_memory"
5818
+ };
5819
+ }
5820
+ // Tier 3: absolute fallback when deviceMemory is unavailable.
5821
+ // 16 GB catches incognito on most machines (up to ~32 GB RAM)
5822
+ // while staying below normal-mode quota on any disk ≥ 64 GB.
5823
+ if (!deviceMemGB && quota < 16000000000) {
5824
+ return {
5825
+ is_private: true,
5826
+ confidence: "medium",
5827
+ method: "storage_quota_chrome"
5828
+ };
5829
+ }
5830
+ }
5736
5831
  }
5737
5832
  }
5738
5833
  catch {
5739
- // Not supported
5834
+ // API not supported
5740
5835
  }
5741
- // Firefox: Check IndexedDB behavior (with timeout to prevent hanging)
5836
+ // ── webkitTemporaryStorage (Chromium legacy) ───────────────────────
5837
+ // Still present in some Chromium builds; incognito returns a small
5838
+ // quota (often ≈ 120 MB) vs normal (multiple GB).
5742
5839
  try {
5743
- const dbResult = await withTimeout(new Promise((resolve, reject) => {
5744
- const db = indexedDB.open("test");
5745
- db.onerror = () => reject(new Error("IndexedDB error"));
5746
- db.onsuccess = () => {
5747
- db.result.close();
5748
- resolve(true);
5749
- };
5750
- }), 1000);
5751
- // If timeout occurred (null), treat as inconclusive, not private
5752
- if (dbResult === null) {
5753
- // Timeout - can't determine, continue to next check
5840
+ const tmpStorage = navigator.webkitTemporaryStorage;
5841
+ if (tmpStorage?.queryUsageAndQuota) {
5842
+ const quota = await withTimeout(new Promise((resolve, reject) => {
5843
+ tmpStorage.queryUsageAndQuota((_usage, q) => resolve(q), () => reject(new Error("quota query failed")));
5844
+ }), 1000);
5845
+ if (quota !== null && quota < 200000000) {
5846
+ return {
5847
+ is_private: true,
5848
+ confidence: "medium",
5849
+ method: "webkit_temporary_storage"
5850
+ };
5851
+ }
5754
5852
  }
5755
5853
  }
5756
5854
  catch {
5757
- return {
5758
- is_private: true,
5759
- confidence: "medium",
5760
- method: "indexeddb"
5761
- };
5855
+ // Not available
5762
5856
  }
5763
- // Chrome: Check filesystem API (deprecated but still works)
5857
+ // ── IndexedDB ────────────────────────────────────────────────────────
5858
+ // Firefox private mode rejects indexedDB.open(). Some Chrome/Edge
5859
+ // incognito edge-cases (enterprise policies, older builds) also fail
5860
+ // here, so we keep this as a universal late fallback.
5764
5861
  try {
5765
- const fs = window.webkitRequestFileSystem;
5766
- if (fs) {
5767
- const fsResult = await withTimeout(new Promise((resolve, reject) => {
5768
- fs(0, // TEMPORARY
5769
- 1, () => resolve(true), () => reject(new Error("FileSystem error")));
5770
- }), 1000);
5771
- // If timeout occurred, continue
5772
- if (fsResult === null) {
5773
- // Timeout - can't determine
5774
- }
5775
- }
5862
+ await withTimeout(new Promise((resolve, reject) => {
5863
+ const req = indexedDB.open("_di_priv_check");
5864
+ req.onerror = () => reject(new Error("blocked"));
5865
+ req.onsuccess = () => {
5866
+ req.result.close();
5867
+ resolve(true);
5868
+ };
5869
+ }), 1000);
5776
5870
  }
5777
5871
  catch {
5778
5872
  return {
5779
5873
  is_private: true,
5780
- confidence: "medium",
5781
- method: "filesystem"
5874
+ confidence: isFirefox ? "medium" : "low",
5875
+ method: "indexeddb"
5782
5876
  };
5783
5877
  }
5784
5878
  return {
@@ -5932,7 +6026,7 @@ ZwIDAQAB
5932
6026
  return getAllCollectors();
5933
6027
  }
5934
6028
 
5935
- var version = "1.1.1";
6029
+ var version = "1.1.3";
5936
6030
  var packageJson = {
5937
6031
  version: version};
5938
6032