@fanboynz/network-scanner 2.0.55 → 2.0.56

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 (3) hide show
  1. package/lib/cloudflare.js +117 -65
  2. package/nwss.js +46 -12
  3. package/package.json +1 -1
package/lib/cloudflare.js CHANGED
@@ -1,5 +1,20 @@
1
1
  /**
2
2
  * Cloudflare bypass and challenge handling module - Optimized with smart detection and adaptive timeouts
3
+ * Version: 2.7.0 - Major fixes and performance overhaul
4
+ * - Fix: Challenge solvers had empty if-blocks (JS/Turnstile/Legacy never executed in non-debug mode)
5
+ * - Fix: a[href*="continue"] false positive removed (matched nearly every website)
6
+ * - Perf: Domain-level detection cache (was per-URL, now per-hostname)
7
+ * - Perf: Timeout outcome caching (domain times out once -> all subsequent URLs skip instantly)
8
+ * - Perf: Short-circuit quick detection (title/URL -> fast selectors -> slow text, early return at each stage)
9
+ * - Perf: Eliminated body.textContent in quick detection (was extracting entire DOM text tree)
10
+ * - Perf: Capped body.textContent to 2KB in analyzeCloudflareChallenge
11
+ * - Perf: No-indicator pages skip immediately regardless of config (was 10-15s wasted)
12
+ * - Perf: Quick detection timeout 4s->2s, retries 2->1
13
+ * - Perf: PAGE_EVALUATION timeout 12s->5s, detached frame delay 3s->1s
14
+ * - Perf: Inner timeouts tightened to fit within outer adaptive timeouts
15
+ * - Perf: CHALLENGE_SOLVING 30s->12s, TURNSTILE_COMPLETION 20s->10s, JS_CHALLENGE_BUFFER 26s->12s
16
+ * - Perf: MAX_RETRIES 3->2, baseDelay 1000->800ms, maxDelay 8000->5000ms
17
+ * - Perf: Parallel detection gated behind cloudflare config (was running on every URL)
3
18
  * Version: 2.6.3 - Fixes Cannot read properties of undefined (reading 'hasIndicators')
4
19
  * Version: 2.6.2 - Further detached Frame fixes
5
20
  * Version: 2.6.1 - timeoutId is not defined & race condition fix
@@ -20,7 +35,7 @@ const { formatLogMessage } = require('./colorize');
20
35
  /**
21
36
  * Module version information
22
37
  */
23
- const CLOUDFLARE_MODULE_VERSION = '2.6.3';
38
+ const CLOUDFLARE_MODULE_VERSION = '2.7.0';
24
39
 
25
40
  /**
26
41
  * Timeout constants for various operations (in milliseconds)
@@ -28,13 +43,13 @@ const CLOUDFLARE_MODULE_VERSION = '2.6.3';
28
43
  * All values tuned for maximum scanning speed while maintaining functionality
29
44
  */
30
45
  const TIMEOUTS = {
31
- PAGE_EVALUATION: 12000, // Standard page evaluation timeout
32
- PAGE_EVALUATION_SAFE: 12000, // Safe page evaluation with extra buffer
46
+ PAGE_EVALUATION: 5000, // Standard page evaluation timeout (DOM queries are instant)
47
+ PAGE_EVALUATION_SAFE: 5000, // Safe page evaluation with extra buffer
33
48
  PHISHING_CLICK: 3000, // Timeout for clicking phishing continue button
34
49
  PHISHING_NAVIGATION: 8000, // Wait for navigation after phishing bypass
35
- JS_CHALLENGE_BUFFER: 26000, // JS challenge with safety buffer
36
- TURNSTILE_COMPLETION: 20000, // Turnstile completion check
37
- TURNSTILE_COMPLETION_BUFFER: 25000, // Turnstile completion with buffer
50
+ JS_CHALLENGE_BUFFER: 12000, // JS challenge -- must fit within 15s adaptive outer timeout
51
+ TURNSTILE_COMPLETION: 10000, // Turnstile completion check -- fits within adaptive timeout
52
+ TURNSTILE_COMPLETION_BUFFER: 12000, // Turnstile completion with buffer
38
53
  CLICK_TIMEOUT: 5000, // Standard click operation timeout
39
54
  CLICK_TIMEOUT_BUFFER: 1000, // Click timeout safety buffer
40
55
  NAVIGATION_TIMEOUT: 15000, // Standard navigation timeout
@@ -45,21 +60,21 @@ const TIMEOUTS = {
45
60
  ADAPTIVE_TIMEOUT_AUTO_WITHOUT_INDICATORS: 10000, // Adaptive timeout for auto-detected without indicators
46
61
  // New timeouts for enhanced functionality
47
62
  RETRY_DELAY: 1000, // Delay between retry attempts
48
- MAX_RETRIES: 3, // Maximum retry attempts for operations
63
+ MAX_RETRIES: 2, // Maximum retry attempts (only 2 fit within 25s outer timeout)
49
64
  CHALLENGE_POLL_INTERVAL: 500, // Interval for polling challenge completion
50
65
  CHALLENGE_MAX_POLLS: 20 // Maximum polling attempts
51
66
  };
52
67
 
53
68
  // Fast timeout constants - optimized for speed
54
69
  const FAST_TIMEOUTS = {
55
- QUICK_DETECTION: 4000, // Fast Cloudflare detection
70
+ QUICK_DETECTION: 2000, // Fast Cloudflare detection (DOM check, instant on loaded pages)
56
71
  PHISHING_WAIT: 1000, // Fast phishing check
57
72
  CHALLENGE_WAIT: 500, // Fast challenge detection
58
73
  ELEMENT_INTERACTION_DELAY: 250, // Fast element interactions
59
74
  SELECTOR_WAIT: 3000, // Fast selector waits
60
75
  TURNSTILE_OPERATION: 6000, // Fast Turnstile operations
61
76
  JS_CHALLENGE: 10000, // Fast JS challenge completion
62
- CHALLENGE_SOLVING: 30000, // Fast overall challenge solving
77
+ CHALLENGE_SOLVING: 12000, // Overall challenge solving -- fits within 15s adaptive outer
63
78
  CHALLENGE_COMPLETION: 8000 // Fast completion check
64
79
  };
65
80
 
@@ -68,7 +83,7 @@ const FAST_TIMEOUTS = {
68
83
  * Returns {found, clicked, x, y} - coordinates allow fallback mouse.click
69
84
  */
70
85
  async function clickInShadowDOM(context, selectors, forceDebug = false, waitMs = 1500) {
71
- // Try Puppeteer's pierce/ selector first handles CLOSED shadow roots via CDP
86
+ // Try Puppeteer's pierce/ selector first -- handles CLOSED shadow roots via CDP
72
87
  for (const selector of selectors) {
73
88
  try {
74
89
  // Wait for element to appear (handles delayed rendering)
@@ -77,7 +92,7 @@ async function clickInShadowDOM(context, selectors, forceDebug = false, waitMs =
77
92
  if (element) {
78
93
  const box = await element.boundingBox();
79
94
  if (box && box.width > 0 && box.height > 0) {
80
- if (forceDebug) console.log(formatLogMessage('cloudflare', `pierce/${selector} matched in ${Date.now() - start}ms box: ${box.width}x${box.height} at (${box.x},${box.y})`));
95
+ if (forceDebug) console.log(formatLogMessage('cloudflare', `pierce/${selector} matched in ${Date.now() - start}ms -- box: ${box.width}x${box.height} at (${box.x},${box.y})`));
81
96
  await element.click();
82
97
  await element.dispose();
83
98
  return { found: true, clicked: true, selector, x: box.x + box.width / 2, y: box.y + box.height / 2 };
@@ -185,9 +200,9 @@ function detectChallengeLoop(url, previousUrls = []) {
185
200
  * Retry configuration with exponential backoff
186
201
  */
187
202
  const RETRY_CONFIG = {
188
- maxAttempts: 3,
189
- baseDelay: 1000,
190
- maxDelay: 8000,
203
+ maxAttempts: 2, // Only 2 attempts fit within 25s outer timeout
204
+ baseDelay: 800, // Slightly faster retry delay
205
+ maxDelay: 5000, // Lower max delay cap
191
206
  backoffMultiplier: 2,
192
207
  retryableErrors: [ERROR_TYPES.NETWORK, ERROR_TYPES.TIMEOUT, ERROR_TYPES.ELEMENT_NOT_FOUND, ERROR_TYPES.DETACHED_FRAME]
193
208
  };
@@ -209,7 +224,7 @@ class CloudflareDetectionCache {
209
224
  getCacheKey(url) {
210
225
  try {
211
226
  const urlObj = new URL(url);
212
- return `${urlObj.hostname}${urlObj.pathname}`;
227
+ return urlObj.hostname; // Domain-level caching: all URLs from same host share one entry
213
228
  } catch {
214
229
  return url;
215
230
  }
@@ -441,8 +456,8 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
441
456
  if (forceDebug) {
442
457
  console.warn(formatLogMessage('cloudflare', `Detached frame detected on attempt ${attempt}/${maxRetries} - using longer delay`));
443
458
  }
444
- // For detached frames, wait longer
445
- await new Promise(resolve => setTimeout(resolve, 3000)); // Longer delay
459
+ // For detached frames, brief delay before retry
460
+ await new Promise(resolve => setTimeout(resolve, 1000));
446
461
 
447
462
  // For detached frames, only retry once more
448
463
  if (attempt >= 2) {
@@ -541,44 +556,67 @@ async function quickCloudflareDetection(page, forceDebug = false) {
541
556
  // Perform actual detection with enhanced error handling
542
557
  const quickCheck = await safePageEvaluate(page, () => {
543
558
  const title = document.title || '';
544
- const bodyText = document.body ? document.body.textContent.substring(0, 500) : '';
545
559
  const url = window.location.href;
546
560
 
547
- // Enhanced indicators with 2025 patterns
548
- const hasCloudflareIndicators =
561
+ // FAST PATH: Check title + URL first (string ops, no DOM traversal)
562
+ const titleMatch =
549
563
  title.includes('Just a moment') ||
550
564
  title.includes('Checking your browser') ||
551
565
  title.includes('Attention Required') ||
552
- title.includes('Security check') || // New pattern
566
+ title.includes('Security check');
567
+
568
+ const urlMatch =
569
+ url.includes('/cdn-cgi/challenge-platform/') ||
570
+ url.includes('cloudflare.com');
571
+
572
+ if (titleMatch || urlMatch) {
573
+ return { hasIndicators: true, title, url, bodySnippet: '' };
574
+ }
575
+
576
+ // MEDIUM PATH: Check a few fast selectors before expensive text extraction
577
+ const selectorMatch =
578
+ document.querySelector('[data-ray]') ||
579
+ document.querySelector('[data-cf-challenge]') ||
580
+ document.querySelector('.cf-challenge-running') ||
581
+ document.querySelector('.cf-turnstile') ||
582
+ document.querySelector('.cf-managed-challenge') ||
583
+ document.querySelector('[data-cf-managed]') ||
584
+ document.querySelector('script[src*="/cdn-cgi/challenge-platform/"]');
585
+
586
+ if (selectorMatch) {
587
+ return { hasIndicators: true, title, url, bodySnippet: '' };
588
+ }
589
+
590
+ // SLOW PATH: Extract limited body text only if fast checks failed
591
+ // Use body.innerText capped to first child nodes instead of full textContent
592
+ let bodyText = '';
593
+ if (document.body) {
594
+ const el = document.body.querySelector('.main-wrapper, .main-content, #challenge-body-text, .cf-challenge-container');
595
+ bodyText = el ? el.textContent.substring(0, 300) : (document.body.firstElementChild ? document.body.firstElementChild.textContent.substring(0, 300) : '');
596
+ }
597
+
598
+ const textMatch =
553
599
  bodyText.includes('Cloudflare') ||
554
600
  bodyText.includes('cf-ray') ||
555
601
  bodyText.includes('Verify you are human') ||
556
602
  bodyText.includes('This website has been reported for potential phishing') ||
557
603
  bodyText.includes('Please wait while we verify') ||
558
- bodyText.includes('Checking if the site connection is secure') || // New pattern
559
- url.includes('/cdn-cgi/challenge-platform/') ||
560
- url.includes('cloudflare.com') ||
561
- document.querySelector('[data-ray]') ||
562
- document.querySelector('[data-cf-challenge]') ||
563
- document.querySelector('.cf-challenge-running') ||
604
+ bodyText.includes('Checking if the site connection is secure');
605
+
606
+ // Remaining slower selectors
607
+ const slowSelectorMatch =
564
608
  document.querySelector('.cf-challenge-container') ||
565
- document.querySelector('.cf-turnstile') ||
566
609
  document.querySelector('.ctp-checkbox-container') ||
567
610
  document.querySelector('iframe[src*="challenges.cloudflare.com"]') ||
568
- document.querySelector('iframe[title*="Cloudflare security challenge"]') ||
569
- document.querySelector('script[src*="/cdn-cgi/challenge-platform/"]') ||
570
- document.querySelector('a[href*="continue"]') ||
571
- // New selectors for 2025
572
- document.querySelector('.cf-managed-challenge') ||
573
- document.querySelector('[data-cf-managed]');
611
+ document.querySelector('iframe[title*="Cloudflare security challenge"]');
574
612
 
575
613
  return {
576
- hasIndicators: hasCloudflareIndicators,
614
+ hasIndicators: !!(textMatch || slowSelectorMatch),
577
615
  title,
578
616
  url,
579
617
  bodySnippet: bodyText.substring(0, 200)
580
618
  };
581
- }, FAST_TIMEOUTS.QUICK_DETECTION, { maxRetries: 2, forceDebug });
619
+ }, FAST_TIMEOUTS.QUICK_DETECTION, { maxRetries: 1, forceDebug });
582
620
 
583
621
  // Cache the result
584
622
  detectionCache.set(currentPageUrl, quickCheck);
@@ -607,7 +645,7 @@ async function quickCloudflareDetection(page, forceDebug = false) {
607
645
  */
608
646
  async function analyzeCloudflareChallenge(page) {
609
647
  try {
610
- // CDP-level frame check bypasses closed shadow roots
648
+ // CDP-level frame check -- bypasses closed shadow roots
611
649
  const frames = page.frames();
612
650
  const hasChallengeFrame = frames.some(f => {
613
651
  const url = f.url();
@@ -616,7 +654,8 @@ async function analyzeCloudflareChallenge(page) {
616
654
 
617
655
  const result = await safePageEvaluate(page, () => {
618
656
  const title = document.title || '';
619
- const bodyText = document.body ? document.body.textContent : '';
657
+ // Cap text extraction -- on content-heavy pages body.textContent can be megabytes
658
+ const bodyText = document.body ? document.body.textContent.substring(0, 2000) : '';
620
659
 
621
660
  // Updated selectors for 2025 Cloudflare challenges
622
661
  const hasTurnstileIframe = document.querySelector('iframe[title*="Cloudflare security challenge"]') !== null ||
@@ -649,8 +688,7 @@ async function analyzeCloudflareChallenge(page) {
649
688
  bodyText.includes('Please wait while we verify');
650
689
 
651
690
  const hasPhishingWarning = bodyText.includes('This website has been reported for potential phishing') ||
652
- title.includes('Attention Required') ||
653
- document.querySelector('a[href*="continue"]') !== null;
691
+ title.includes('Attention Required');
654
692
 
655
693
  const hasTurnstileResponse = document.querySelector('input[name="cf-turnstile-response"]') !== null;
656
694
 
@@ -687,7 +725,7 @@ async function analyzeCloudflareChallenge(page) {
687
725
  };
688
726
  }, TIMEOUTS.PAGE_EVALUATION);
689
727
 
690
- // Merge CDP frame detection catches iframes behind closed shadow roots
728
+ // Merge CDP frame detection -- catches iframes behind closed shadow roots
691
729
  if (hasChallengeFrame && !result.hasTurnstileIframe) {
692
730
  result.hasTurnstileIframe = true;
693
731
  result.isTurnstile = true;
@@ -1088,20 +1126,10 @@ async function attemptChallengeSolve(page, currentUrl, challengeInfo, forceDebug
1088
1126
  if (jsResult.success) {
1089
1127
  // Wait for redirect after challenge completion
1090
1128
  try {
1091
- const startUrl = await page.url();
1092
- await page.waitForFunction(
1093
- (origUrl) => {
1094
- const bodyText = document.body?.textContent || '';
1095
- return document.title !== 'Just a moment...' ||
1096
- window.location.href !== origUrl ||
1097
- bodyText.includes('Verification successful');
1098
- },
1099
- { timeout: 10000 },
1100
- startUrl
1101
- );
1102
- if (forceDebug) console.log(formatLogMessage('cloudflare', `Challenge page cleared for ${currentUrl}`));
1103
- } catch (_) {
1104
- if (forceDebug) console.log(formatLogMessage('cloudflare', `Challenge page not cleared after 10s � continuing`));
1129
+ await page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 10000 });
1130
+ if (forceDebug) console.log(formatLogMessage('cloudflare', `Post-challenge redirect completed for ${currentUrl}`));
1131
+ } catch (navErr) {
1132
+ if (forceDebug) console.log(formatLogMessage('cloudflare', `Post-challenge redirect timeout (may already be on target page): ${navErr.message}`));
1105
1133
  }
1106
1134
  result.success = true;
1107
1135
  result.method = 'js_challenge_wait';
@@ -1183,7 +1211,7 @@ async function handleEmbeddedIframeChallenge(page, forceDebug = false) {
1183
1211
  try {
1184
1212
  if (forceDebug) console.log(formatLogMessage('cloudflare', `Checking for embedded iframe challenges`));
1185
1213
 
1186
- // Use CDP-level frame detection bypasses closed shadow roots
1214
+ // Use CDP-level frame detection -- bypasses closed shadow roots
1187
1215
  const frames = page.frames();
1188
1216
  if (forceDebug) {
1189
1217
  console.log(formatLogMessage('cloudflare', `Available frames (${frames.length}):`));
@@ -1624,10 +1652,11 @@ async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDeb
1624
1652
 
1625
1653
  // Early return structure when no Cloudflare indicators found
1626
1654
  // Sets attempted: false, success: true for both protection types
1627
-
1628
- // Only proceed if we have indicators OR explicit config enables Cloudflare handling
1629
- if (!quickDetection.hasIndicators && !cfPhishEnabled && !cfBypassEnabled) {
1630
- if (forceDebug) console.log(formatLogMessage('cloudflare', `No Cloudflare indicators found and no explicit config, skipping protection handling for ${currentUrl}`));
1655
+ // Skip immediately if no Cloudflare indicators detected
1656
+ // Trust the detection -- explicit config only matters when indicators ARE found
1657
+ // This avoids a 10s adaptive timeout on non-Cloudflare sites
1658
+ if (!quickDetection.hasIndicators) {
1659
+ if (forceDebug) console.log(formatLogMessage('cloudflare', `No Cloudflare indicators found, skipping protection handling for ${currentUrl}`));
1631
1660
  if (forceDebug) console.log(formatLogMessage('cloudflare', `Quick detection details: title="${quickDetection.title}", bodySnippet="${quickDetection.bodySnippet}"`));
1632
1661
  return {
1633
1662
  phishingWarning: { attempted: false, success: true },
@@ -1663,10 +1692,24 @@ async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDeb
1663
1692
  console.log(formatLogMessage('cloudflare', `Using adaptive timeout of ${adaptiveTimeout}ms for ${currentUrl} (indicators: ${quickDetection.hasIndicators}, explicit config: ${!!(siteConfig.cloudflare_phish || siteConfig.cloudflare_bypass)})`));
1664
1693
  }
1665
1694
 
1666
- return await Promise.race([
1667
- performCloudflareHandling(page, currentUrl, siteConfig, cfDebug),
1695
+ // Check if this domain already timed out -- skip immediately
1696
+ try {
1697
+ const outcomeCacheKey = 'outcome:' + new URL(currentUrl).hostname;
1698
+ const cachedOutcome = detectionCache.cache.get(outcomeCacheKey);
1699
+ if (cachedOutcome && cachedOutcome.data && cachedOutcome.data.timedOut && Date.now() - cachedOutcome.timestamp < detectionCache.ttl) {
1700
+ if (forceDebug) console.log(formatLogMessage('cloudflare', `Skipping ${currentUrl} -- domain already timed out on a previous URL`));
1701
+ return cachedOutcome.data;
1702
+ }
1703
+ } catch (e) { /* malformed URL, proceed normally */ }
1704
+
1705
+ let adaptiveTimeoutId = null;
1706
+ const handlingResult = await Promise.race([
1707
+ performCloudflareHandling(page, currentUrl, siteConfig, cfDebug).then(r => {
1708
+ if (adaptiveTimeoutId) clearTimeout(adaptiveTimeoutId);
1709
+ return r;
1710
+ }),
1668
1711
  new Promise((resolve) => {
1669
- setTimeout(() => {
1712
+ adaptiveTimeoutId = setTimeout(() => {
1670
1713
  console.warn(formatLogMessage('cloudflare', `Adaptive timeout (${adaptiveTimeout}ms) for ${currentUrl} - continuing with scan`));
1671
1714
  resolve({
1672
1715
  phishingWarning: { attempted: false, success: true },
@@ -1678,6 +1721,16 @@ async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDeb
1678
1721
  }, adaptiveTimeout);
1679
1722
  })
1680
1723
  ]);
1724
+
1725
+ // Cache timeout results at domain level so subsequent URLs skip immediately
1726
+ if (handlingResult.timedOut) {
1727
+ try {
1728
+ const setOutcomeKey = 'outcome:' + new URL(currentUrl).hostname;
1729
+ detectionCache.cache.set(setOutcomeKey, { data: handlingResult, timestamp: Date.now() });
1730
+ } catch (e) { /* malformed URL, skip caching */ }
1731
+ }
1732
+
1733
+ return handlingResult;
1681
1734
  } catch (error) {
1682
1735
  result.overallSuccess = false;
1683
1736
  result.errors.push(`Cloudflare handling failed: ${error.message}`);
@@ -1809,8 +1862,7 @@ async function parallelChallengeDetection(page, forceDebug = false) {
1809
1862
  { type: 'turnstile', detected: document.querySelector('.cf-turnstile') !== null ||
1810
1863
  document.querySelector('iframe[src*="challenges.cloudflare.com"]') !== null ||
1811
1864
  document.querySelector('.ctp-checkbox-container') !== null },
1812
- { type: 'phishing', detected: bodyText.includes('This website has been reported for potential phishing') ||
1813
- document.querySelector('a[href*="continue"]') !== null },
1865
+ { type: 'phishing', detected: bodyText.includes('This website has been reported for potential phishing') },
1814
1866
  { type: 'managed', detected: document.querySelector('.cf-managed-challenge') !== null ||
1815
1867
  document.querySelector('[data-cf-managed]') !== null }
1816
1868
  ];
package/nwss.js CHANGED
@@ -20,7 +20,8 @@ const {
20
20
  handleCloudflareProtection,
21
21
  getCacheStats,
22
22
  clearDetectionCache,
23
- parallelChallengeDetection
23
+ parallelChallengeDetection,
24
+ cleanup: cleanupCloudflareCache
24
25
  } = require('./lib/cloudflare');
25
26
  // FP Bypass
26
27
  const { handleFlowProxyProtection, getFlowProxyTimeouts } = require('./lib/flowproxy');
@@ -84,8 +85,8 @@ const TIMEOUTS = Object.freeze({
84
85
  });
85
86
 
86
87
  const CACHE_LIMITS = Object.freeze({
87
- DISK_CACHE_SIZE: 52428800, // 50MB
88
- MEDIA_CACHE_SIZE: 52428800, // 50MB
88
+ DISK_CACHE_SIZE: 1, // Effectively disabled — forcereload clears cache between loads
89
+ MEDIA_CACHE_SIZE: 1, // Effectively disabled — no media caching needed for scanning
89
90
  DEFAULT_CACHE_PATH: '.cache',
90
91
  DEFAULT_MAX_SIZE: 5000
91
92
  });
@@ -1020,11 +1021,28 @@ function flushLogBuffers() {
1020
1021
  for (const [filePath, entries] of _logBuffers) {
1021
1022
  if (entries.length > 0) {
1022
1023
  try {
1023
- fs.appendFileSync(filePath, entries.join(''));
1024
+ const data = entries.join('');
1025
+ entries.length = 0; // Clear buffer immediately
1026
+ fs.writeFile(filePath, data, { flag: 'a' }, (err) => {
1027
+ if (err) {
1028
+ console.warn(formatLogMessage('warn', `Failed to flush log buffer to ${filePath}: ${err.message}`));
1029
+ }
1030
+ });
1024
1031
  } catch (err) {
1025
1032
  console.warn(formatLogMessage('warn', `Failed to flush log buffer to ${filePath}: ${err.message}`));
1026
1033
  }
1027
- entries.length = 0; // Clear buffer
1034
+ }
1035
+ }
1036
+ }
1037
+
1038
+ // Synchronous flush for exit handlers — guarantees data is written before process exits
1039
+ function flushLogBuffersSync() {
1040
+ for (const [filePath, entries] of _logBuffers) {
1041
+ if (entries.length > 0) {
1042
+ try {
1043
+ fs.appendFileSync(filePath, entries.join(''));
1044
+ } catch (err) { /* best effort on exit */ }
1045
+ entries.length = 0;
1028
1046
  }
1029
1047
  }
1030
1048
  }
@@ -1426,12 +1444,19 @@ function setupFrameHandling(page, forceDebug) {
1426
1444
  '--use-mock-keychain',
1427
1445
  '--disable-client-side-phishing-detection',
1428
1446
  '--enable-features=NetworkService',
1429
- // Disk space controls - 50MB cache limits
1430
- `--disk-cache-size=${CACHE_LIMITS.DISK_CACHE_SIZE}`, // 50MB disk cache
1431
- `--media-cache-size=${CACHE_LIMITS.MEDIA_CACHE_SIZE}`, // 50MB media cache
1447
+ // Disk space controls - minimal cache for scanning workloads
1448
+ `--disk-cache-size=${CACHE_LIMITS.DISK_CACHE_SIZE}`,
1449
+ `--media-cache-size=${CACHE_LIMITS.MEDIA_CACHE_SIZE}`,
1432
1450
  '--disable-application-cache',
1433
1451
  '--disable-offline-load-stale-cache',
1434
1452
  '--disable-background-downloads',
1453
+ // DISK I/O REDUCTION: Eliminate unnecessary Chrome disk writes
1454
+ '--disable-breakpad', // No crash dump files
1455
+ '--disable-component-update', // No component update downloads
1456
+ '--disable-logging', // No Chrome internal log files
1457
+ '--log-level=3', // Fatal errors only (suppresses verbose disk logging)
1458
+ '--no-service-autorun', // No background service disk activity
1459
+ '--disable-domain-reliability', // No reliability monitor disk writes
1435
1460
  // PERFORMANCE: Enhanced Puppeteer 23.x optimizations
1436
1461
  '--disable-features=AudioServiceOutOfProcess,VizDisplayCompositor',
1437
1462
  '--disable-features=TranslateUI,BlinkGenPropertyTrees,Translate',
@@ -1466,6 +1491,12 @@ function setupFrameHandling(page, forceDebug) {
1466
1491
  '--disable-background-timer-throttling',
1467
1492
  '--disable-features=site-per-process', // Better for single-site scanning
1468
1493
  '--no-zygote', // Better process isolation
1494
+ // PERFORMANCE: Process and memory reduction for high concurrency
1495
+ '--renderer-process-limit=10', // Cap renderer processes (default: unlimited)
1496
+ '--disable-accelerated-2d-canvas', // Software canvas only (we spoof it anyway)
1497
+ '--disable-hang-monitor', // Remove per-renderer hang check overhead
1498
+ '--disable-features=PaintHolding', // Don't hold frames in renderer memory
1499
+ '--js-flags=--max-old-space-size=512', // Cap V8 heap per renderer to 512MB
1469
1500
  ...extraArgs,
1470
1501
  ],
1471
1502
  // Optimized timeouts for Puppeteer 23.x performance
@@ -1517,7 +1548,7 @@ function setupFrameHandling(page, forceDebug) {
1517
1548
  // Set up cleanup on process termination
1518
1549
  process.on('SIGINT', async () => {
1519
1550
  if (forceDebug) console.log(formatLogMessage('debug', 'SIGINT received, performing cleanup...'));
1520
- flushLogBuffers();
1551
+ flushLogBuffersSync();
1521
1552
  if (_logFlushTimer) clearInterval(_logFlushTimer);
1522
1553
  await performEmergencyCleanup();
1523
1554
  process.exit(0);
@@ -1525,7 +1556,7 @@ function setupFrameHandling(page, forceDebug) {
1525
1556
 
1526
1557
  process.on('SIGTERM', async () => {
1527
1558
  if (forceDebug) console.log(formatLogMessage('debug', 'SIGTERM received, performing cleanup...'));
1528
- flushLogBuffers();
1559
+ flushLogBuffersSync();
1529
1560
  if (_logFlushTimer) clearInterval(_logFlushTimer);
1530
1561
  await performEmergencyCleanup();
1531
1562
  process.exit(0);
@@ -1556,6 +1587,7 @@ function setupFrameHandling(page, forceDebug) {
1556
1587
  }
1557
1588
  wgDisconnectAll(forceDebug);
1558
1589
  ovpnDisconnectAll(forceDebug);
1590
+ cleanupCloudflareCache();
1559
1591
  }
1560
1592
 
1561
1593
  let siteCounter = 0;
@@ -3219,7 +3251,9 @@ function setupFrameHandling(page, forceDebug) {
3219
3251
  siteCounter++;
3220
3252
 
3221
3253
  // Enhanced Cloudflare handling with parallel detection
3222
- if (siteConfig.cloudflare_parallel_detection !== false) { // Enable by default
3254
+ // Only run parallel detection if cloudflare handling is explicitly configured
3255
+ const hasCloudflareConfig = siteConfig.cloudflare_bypass || siteConfig.cloudflare_phish;
3256
+ if (hasCloudflareConfig && siteConfig.cloudflare_parallel_detection !== false) {
3223
3257
  try {
3224
3258
  const parallelResult = await parallelChallengeDetection(page, forceDebug);
3225
3259
  if (parallelResult.hasAnyChallenge && forceDebug) {
@@ -4429,7 +4463,7 @@ function setupFrameHandling(page, forceDebug) {
4429
4463
  }
4430
4464
 
4431
4465
  // Flush any remaining buffered log entries before compression/exit
4432
- flushLogBuffers();
4466
+ flushLogBuffersSync();
4433
4467
  if (_logFlushTimer) {
4434
4468
  clearInterval(_logFlushTimer);
4435
4469
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanboynz/network-scanner",
3
- "version": "2.0.55",
3
+ "version": "2.0.56",
4
4
  "description": "A Puppeteer-based network scanner for analyzing web traffic, generating adblock filter rules, and identifying third-party requests. Features include fingerprint spoofing, Cloudflare bypass, content analysis with curl/grep, and multiple output formats.",
5
5
  "main": "nwss.js",
6
6
  "scripts": {