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