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