@fanboynz/network-scanner 1.0.80 → 1.0.82

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.
package/lib/colorize.js CHANGED
@@ -40,7 +40,10 @@ const colors = {
40
40
  brightBlue: enableColors ? '\x1b[94m' : '',
41
41
  brightMagenta: enableColors ? '\x1b[95m' : '',
42
42
  brightCyan: enableColors ? '\x1b[96m' : '',
43
- brightWhite: enableColors ? '\x1b[97m' : ''
43
+ brightWhite: enableColors ? '\x1b[97m' : '',
44
+
45
+ // Custom orange colors (using 256-color palette)
46
+ orange: enableColors ? '\x1b[38;5;208m' : ''
44
47
  };
45
48
 
46
49
  /**
@@ -79,7 +82,10 @@ const messageColors = {
79
82
 
80
83
  // File operations
81
84
  fileOp: (text) => colorize(text, colors.magenta),
82
- compression: (text) => colorize(text, colors.cyan)
85
+ compression: (text) => colorize(text, colors.cyan),
86
+
87
+ // Cloudflare specific
88
+ cloudflare: (text) => colorize(text, colors.orange)
83
89
  };
84
90
 
85
91
  /**
@@ -102,7 +108,8 @@ const tags = {
102
108
  warn: createTag('warn', colors.yellow),
103
109
  error: createTag('error', colors.red),
104
110
  match: createTag('match', colors.green),
105
- compare: createTag('compare', colors.blue)
111
+ compare: createTag('compare', colors.blue),
112
+ cloudflare: createTag('cloudflare', colors.orange)
106
113
  };
107
114
 
108
115
  /**
package/nwss.js CHANGED
@@ -1,4 +1,4 @@
1
- // === Network scanner script (nwss.js) v1.0.80 ===
1
+ // === Network scanner script (nwss.js) v1.0.82 ===
2
2
 
3
3
  // puppeteer for browser automation, fs for file system operations, psl for domain parsing.
4
4
  // const pLimit = require('p-limit'); // Will be dynamically imported
@@ -123,7 +123,7 @@ const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/r
123
123
  const { monitorBrowserHealth, isBrowserHealthy } = require('./lib/browserhealth');
124
124
 
125
125
  // --- Script Configuration & Constants ---
126
- const VERSION = '1.0.80'; // Script version
126
+ const VERSION = '1.0.82'; // Script version
127
127
 
128
128
  // get startTime
129
129
  const startTime = Date.now();
@@ -534,6 +534,8 @@ Cloudflare Protection Options:
534
534
  cloudflare_parallel_detection: true/false Use parallel detection for faster Cloudflare checks (default: true)
535
535
  cloudflare_max_retries: <number> Maximum retry attempts for Cloudflare operations (default: 3)
536
536
  cloudflare_cache_ttl: <milliseconds> TTL for Cloudflare detection cache (default: 300000 - 5 minutes)
537
+ cloudflare_retry_on_error: true/false Enable retry logic for Cloudflare operations (default: true)
538
+ Note: Automatically detects and exits on redirect loops to prevent endless loading
537
539
  cloudflare_retry_on_error: true/false Enable retry logic for Cloudflare operations (default: true)
538
540
 
539
541
  FlowProxy Protection Options:
@@ -1348,10 +1350,7 @@ function setupFrameHandling(page, forceDebug) {
1348
1350
  }
1349
1351
 
1350
1352
  let siteCounter = 0;
1351
- const totalUrls = sites.reduce((sum, site) => {
1352
- const urls = Array.isArray(site.url) ? site.url.length : 1;
1353
- return sum + urls;
1354
- }, 0);
1353
+ // totalUrls now calculated from allTasks.length after URL flattening
1355
1354
 
1356
1355
  // --- Global CDP (Chrome DevTools Protocol) Session --- [COMMENT RE-ADDED PREVIOUSLY, relevant to old logic]
1357
1356
  // NOTE: This CDP session is attached to the initial browser page (e.g., about:blank).
@@ -1383,6 +1382,14 @@ function setupFrameHandling(page, forceDebug) {
1383
1382
  const siteLocalhostAlt = siteConfig.localhost_0_0_0_0 === true;
1384
1383
  const cloudflarePhishBypass = siteConfig.cloudflare_phish === true;
1385
1384
  const cloudflareBypass = siteConfig.cloudflare_bypass === true;
1385
+ // Add redirect and same-page loop protection
1386
+ const MAX_REDIRECT_DEPTH = siteConfig.max_redirects || 10;
1387
+ const redirectHistory = new Set();
1388
+ let redirectCount = 0;
1389
+ const pageLoadHistory = new Map(); // Track same-page reloads
1390
+ const MAX_SAME_PAGE_LOADS = 3;
1391
+ let currentPageUrl = currentUrl;
1392
+
1386
1393
  const sitePrivoxy = siteConfig.privoxy === true;
1387
1394
  const sitePihole = siteConfig.pihole === true;
1388
1395
  const flowproxyDetection = siteConfig.flowproxy_detection === true;
@@ -1564,6 +1571,31 @@ function setupFrameHandling(page, forceDebug) {
1564
1571
  }
1565
1572
  try {
1566
1573
  await page.evaluateOnNewDocument(() => {
1574
+ // Prevent infinite reload loops
1575
+ let reloadCount = 0;
1576
+ const MAX_RELOADS = 2;
1577
+ const originalReload = window.location.reload;
1578
+ const originalReplace = window.location.replace;
1579
+ const originalAssign = window.location.assign;
1580
+
1581
+ window.location.reload = function() {
1582
+ if (++reloadCount > MAX_RELOADS) {
1583
+ console.log('[loop-protection] Blocked excessive reload attempt');
1584
+ return;
1585
+ }
1586
+ return originalReload.apply(this, arguments);
1587
+ };
1588
+
1589
+ // Also protect against location.replace/assign to same URL
1590
+ const currentHref = window.location.href;
1591
+ window.location.replace = function(url) {
1592
+ if (url === currentHref && ++reloadCount > MAX_RELOADS) {
1593
+ console.log('[loop-protection] Blocked same-page replace attempt');
1594
+ return;
1595
+ }
1596
+ return originalReplace.apply(this, arguments);
1597
+ };
1598
+
1567
1599
  // This script intercepts and logs Fetch and XHR requests
1568
1600
  // from within the page context at the earliest possible moment.
1569
1601
  const originalFetch = window.fetch;
@@ -2542,11 +2574,44 @@ function setupFrameHandling(page, forceDebug) {
2542
2574
 
2543
2575
  const { finalUrl, redirected, redirectChain, originalUrl, redirectDomains } = navigationResult;
2544
2576
 
2577
+ // Check for same-page reload loops BEFORE redirect processing
2578
+ const loadCount = pageLoadHistory.get(currentUrl) || 0;
2579
+ pageLoadHistory.set(currentUrl, loadCount + 1);
2580
+
2581
+ if (loadCount >= MAX_SAME_PAGE_LOADS) {
2582
+ const samePageError = `Same page loaded ${loadCount + 1} times: ${currentUrl}`;
2583
+ console.warn(`⚠ ${samePageError} - possible infinite reload loop`);
2584
+ throw new Error(`Same-page loop detected: ${samePageError}`);
2585
+ }
2586
+
2587
+ currentPageUrl = finalUrl || currentUrl;
2588
+
2545
2589
  // Handle redirect to new domain
2546
2590
  if (redirected) {
2547
2591
  const originalDomain = safeGetDomain(originalUrl);
2548
2592
  const finalDomain = safeGetDomain(finalUrl);
2549
2593
 
2594
+ // Increment redirect counter
2595
+ redirectCount++;
2596
+
2597
+ // Check for redirect loops
2598
+ if (redirectHistory.has(finalUrl)) {
2599
+ const loopError = `Redirect loop detected: ${finalUrl} already visited in chain`;
2600
+ console.warn(`⚠ ${loopError} for ${currentUrl}`);
2601
+ throw new Error(loopError);
2602
+ }
2603
+
2604
+ // Check redirect depth
2605
+ if (redirectCount > MAX_REDIRECT_DEPTH) {
2606
+ const depthError = `Maximum redirect depth (${MAX_REDIRECT_DEPTH}) exceeded`;
2607
+ console.warn(`⚠ ${depthError} for ${currentUrl}`);
2608
+ throw new Error(`${depthError}: ${redirectCount} redirects`);
2609
+ }
2610
+
2611
+ // Add URLs to history
2612
+ redirectHistory.add(currentUrl);
2613
+ redirectHistory.add(finalUrl);
2614
+
2550
2615
  // Add redirect destination to first-party domains immediately
2551
2616
  if (finalDomain) {
2552
2617
  firstPartyDomains.add(finalDomain);
@@ -2617,6 +2682,16 @@ function setupFrameHandling(page, forceDebug) {
2617
2682
 
2618
2683
  // Handle all Cloudflare protections using the enhanced module
2619
2684
  const cloudflareResult = await handleCloudflareProtection(page, currentUrl, siteConfig, forceDebug);
2685
+
2686
+ // Check if Cloudflare handling exceeded max retries and should terminate processing
2687
+ if (!cloudflareResult.overallSuccess &&
2688
+ (cloudflareResult.phishingWarning?.maxRetriesExceeded ||
2689
+ cloudflareResult.verificationChallenge?.maxRetriesExceeded ||
2690
+ cloudflareResult.phishingWarning?.loopDetected ||
2691
+ cloudflareResult.verificationChallenge?.loopDetected)) {
2692
+ throw new Error(`Cloudflare protection handling failed: ${cloudflareResult.errors.join('; ')}`);
2693
+ }
2694
+
2620
2695
  // Check for retry recommendations
2621
2696
  if (cloudflareResult.errors && cloudflareResult.errors.length > 0) {
2622
2697
  const hasRetryableErrors = cloudflareResult.errors.some(err =>
@@ -2628,6 +2703,11 @@ function setupFrameHandling(page, forceDebug) {
2628
2703
  }
2629
2704
  }
2630
2705
 
2706
+ // Log retry information if debug mode is enabled
2707
+ if (forceDebug && (cloudflareResult.phishingWarning?.attempts > 1 || cloudflareResult.verificationChallenge?.attempts > 1)) {
2708
+ console.log(formatLogMessage('debug', `[cloudflare] Total attempts - Phishing: ${cloudflareResult.phishingWarning?.attempts || 0}, Challenge: ${cloudflareResult.verificationChallenge?.attempts || 0}`));
2709
+ }
2710
+
2631
2711
  if (!cloudflareResult.overallSuccess) {
2632
2712
  console.warn(`⚠ [cloudflare] Protection handling failed for ${currentUrl}:`);
2633
2713
  cloudflareResult.errors.forEach(error => {
@@ -2917,57 +2997,72 @@ function setupFrameHandling(page, forceDebug) {
2917
2997
  // Temporarily store the pLimit function
2918
2998
  const originalLimit = limit;
2919
2999
 
2920
- // Group URLs by site to respect site boundaries during cleanup
2921
- const siteGroups = [];
2922
- let currentUrlCount = 0;
2923
-
3000
+ // Create a flat list of all URL tasks with their site configs for true concurrency
3001
+ const allTasks = [];
2924
3002
  for (const site of sites) {
2925
-
2926
3003
  const urlsToProcess = Array.isArray(site.url) ? site.url : [site.url];
2927
- siteGroups.push({
2928
- config: site,
2929
- urls: urlsToProcess
3004
+ urlsToProcess.forEach(url => {
3005
+ allTasks.push({
3006
+ url,
3007
+ config: site,
3008
+ taskId: allTasks.length // For tracking
3009
+ });
2930
3010
  });
2931
- currentUrlCount += urlsToProcess.length;
2932
- }
2933
- if (!silentMode && currentUrlCount > 0) {
2934
- console.log(`\n${messageColors.processing('Processing')} ${currentUrlCount} URLs across ${siteGroups.length} sites with concurrency ${MAX_CONCURRENT_SITES}...`);
2935
- if (currentUrlCount > RESOURCE_CLEANUP_INTERVAL) {
2936
- console.log(messageColors.processing('Browser will restart every') + ` ~${RESOURCE_CLEANUP_INTERVAL} URLs to free resources`);
2937
- }
2938
3011
  }
3012
+
3013
+ const totalUrls = allTasks.length;
2939
3014
 
2940
3015
  let results = [];
2941
3016
  let processedUrlCount = 0;
2942
3017
  let urlsSinceLastCleanup = 0;
2943
3018
 
2944
- // Process sites one by one, but restart browser when hitting URL limits
2945
- for (let siteIndex = 0; siteIndex < siteGroups.length; siteIndex++) {
2946
- const siteGroup = siteGroups[siteIndex];
3019
+ if (!silentMode && totalUrls > 0) {
3020
+ console.log(`\n${messageColors.processing('Processing')} ${totalUrls} URLs with TRUE concurrency ${MAX_CONCURRENT_SITES}...`);
3021
+ if (totalUrls > RESOURCE_CLEANUP_INTERVAL) {
3022
+ console.log(messageColors.processing('Browser will restart every') + ` ~${RESOURCE_CLEANUP_INTERVAL} URLs to free resources`);
3023
+ }
3024
+ }
3025
+
3026
+ // Hang detection for debugging concurrency issues
3027
+ let currentBatchInfo = { batchStart: 0, batchSize: 0 };
3028
+ const hangDetectionInterval = setInterval(() => {
3029
+ const currentBatch = Math.floor(currentBatchInfo.batchStart / RESOURCE_CLEANUP_INTERVAL) + 1;
3030
+ const totalBatches = Math.ceil(totalUrls / RESOURCE_CLEANUP_INTERVAL);
3031
+ console.log(formatLogMessage('debug', `[HANG CHECK] Processed: ${processedUrlCount}/${totalUrls} URLs, Batch: ${currentBatch}/${totalBatches}, Current batch size: ${currentBatchInfo.batchSize}`));
3032
+ console.log(formatLogMessage('debug', `[HANG CHECK] URLs since cleanup: ${urlsSinceLastCleanup}, Recent failures: ${results.slice(-3).filter(r => !r.success).length}/3`));
3033
+ }, 30000); // Check every 30 seconds
3034
+
3035
+ // Process URLs in batches to maintain concurrency while allowing browser restarts
3036
+ for (let batchStart = 0; batchStart < totalUrls; batchStart += RESOURCE_CLEANUP_INTERVAL) {
3037
+ const batchEnd = Math.min(batchStart + RESOURCE_CLEANUP_INTERVAL, totalUrls);
3038
+ const currentBatch = allTasks.slice(batchStart, batchEnd);
2947
3039
 
2948
3040
  // Check browser health before processing each site
2949
3041
  const healthCheck = await monitorBrowserHealth(browser, {}, {
2950
- siteIndex,
2951
- totalSites: siteGroups.length,
3042
+ siteIndex: Math.floor(batchStart / RESOURCE_CLEANUP_INTERVAL),
3043
+ totalSites: Math.ceil(totalUrls / RESOURCE_CLEANUP_INTERVAL),
2952
3044
  urlsSinceCleanup: urlsSinceLastCleanup,
2953
3045
  cleanupInterval: RESOURCE_CLEANUP_INTERVAL,
2954
3046
  forceDebug,
2955
3047
  silentMode
2956
3048
  });
2957
3049
 
2958
- // Also check if browser was unhealthy during recent processing
3050
+ // Check if browser was unhealthy during recent processing
2959
3051
  const recentResults = results.slice(-3);
2960
3052
  const hasRecentFailures = recentResults.filter(r => !r.success).length >= 2;
2961
- const shouldRestartFromFailures = hasRecentFailures && urlsSinceLastCleanup > 3; // More aggressive restart
3053
+ const shouldRestartFromFailures = hasRecentFailures && urlsSinceLastCleanup > 3;
2962
3054
 
2963
- const siteUrlCount = siteGroup.urls.length;
3055
+ const batchSize = currentBatch.length;
2964
3056
 
3057
+ // Update hang detection info
3058
+ currentBatchInfo = { batchStart, batchSize };
3059
+
2965
3060
  // Check if processing this entire site would exceed cleanup interval OR health check suggests restart
2966
- const wouldExceedLimit = urlsSinceLastCleanup + siteUrlCount >= Math.min(RESOURCE_CLEANUP_INTERVAL, 100); // More frequent restarts
2967
- const isNotLastSite = siteIndex < siteGroups.length - 1;
3061
+ const wouldExceedLimit = urlsSinceLastCleanup + batchSize >= Math.min(RESOURCE_CLEANUP_INTERVAL, 100);
3062
+ const isNotLastBatch = batchEnd < totalUrls;
2968
3063
 
2969
3064
  // Restart browser if we've processed enough URLs, health check suggests it, and this isn't the last site
2970
- if ((wouldExceedLimit || healthCheck.shouldRestart || shouldRestartFromFailures) && urlsSinceLastCleanup > 0 && isNotLastSite) {
3065
+ if ((wouldExceedLimit || healthCheck.shouldRestart || shouldRestartFromFailures) && urlsSinceLastCleanup > 0 && isNotLastBatch) {
2971
3066
 
2972
3067
  let restartReason = 'Unknown';
2973
3068
  if (healthCheck.shouldRestart) {
@@ -3023,7 +3118,7 @@ function setupFrameHandling(page, forceDebug) {
3023
3118
 
3024
3119
  // Create new browser for next batch
3025
3120
  browser = await createBrowser();
3026
- if (forceDebug) console.log(formatLogMessage('debug', `New browser instance created for site ${siteIndex + 1}`));
3121
+ if (forceDebug) console.log(formatLogMessage('debug', `New browser instance created for batch ${Math.floor(batchStart / RESOURCE_CLEANUP_INTERVAL) + 1}`));
3027
3122
 
3028
3123
  // Reset cleanup counter and add delay
3029
3124
  urlsSinceLastCleanup = 0;
@@ -3031,19 +3126,29 @@ function setupFrameHandling(page, forceDebug) {
3031
3126
  }
3032
3127
 
3033
3128
  if (forceDebug) {
3034
- console.log(formatLogMessage('debug', `Processing site ${siteIndex + 1}/${siteGroups.length}: ${siteUrlCount} URL(s) (total processed: ${processedUrlCount})`));
3129
+ console.log(formatLogMessage('debug', `Processing batch ${Math.floor(batchStart / RESOURCE_CLEANUP_INTERVAL) + 1}: ${batchSize} URL(s) (total processed: ${processedUrlCount})`));
3130
+ }
3131
+
3132
+ // Log start of concurrent processing for hang detection
3133
+ if (forceDebug) {
3134
+ console.log(formatLogMessage('debug', `[CONCURRENCY] Starting ${batchSize} concurrent tasks with limit ${MAX_CONCURRENT_SITES}`));
3035
3135
  }
3036
3136
 
3037
- // Create tasks with current browser instance and process them
3038
- const siteTasks = siteGroup.urls.map(url => originalLimit(() => processUrl(url, siteGroup.config, browser)));
3039
- const siteResults = await Promise.all(siteTasks);
3137
+ // Create tasks with current browser instance and process them with TRUE concurrency
3138
+ const batchTasks = currentBatch.map(task => originalLimit(() => processUrl(task.url, task.config, browser)));
3139
+ const batchResults = await Promise.all(batchTasks);
3040
3140
 
3041
3141
  // Check if any results indicate immediate restart is needed
3042
- const needsImmediateRestart = siteResults.some(r => r.needsImmediateRestart);
3142
+ const needsImmediateRestart = batchResults.some(r => r.needsImmediateRestart);
3143
+
3144
+ // Log completion of concurrent processing
3145
+ if (forceDebug) {
3146
+ console.log(formatLogMessage('debug', `[CONCURRENCY] Completed ${batchSize} concurrent tasks, ${batchResults.filter(r => r.success).length} successful`));
3147
+ }
3043
3148
 
3044
3149
  // Enhanced error reporting for Puppeteer 23.x
3045
3150
  if (forceDebug) {
3046
- const errorSummary = siteResults.reduce((acc, result) => {
3151
+ const errorSummary = batchResults.reduce((acc, result) => {
3047
3152
  if (!result.success && result.errorType) {
3048
3153
  acc[result.errorType] = (acc[result.errorType] || 0) + 1;
3049
3154
  }
@@ -3051,20 +3156,20 @@ function setupFrameHandling(page, forceDebug) {
3051
3156
  }, {});
3052
3157
 
3053
3158
  if (Object.keys(errorSummary).length > 0) {
3054
- console.log(formatLogMessage('debug', `Site ${siteIndex + 1} error summary:`));
3159
+ console.log(formatLogMessage('debug', `Batch ${Math.floor(batchStart / RESOURCE_CLEANUP_INTERVAL) + 1} error summary:`));
3055
3160
  Object.entries(errorSummary).forEach(([errorType, count]) => {
3056
3161
  console.log(formatLogMessage('debug', ` ${errorType}: ${count} error(s)`));
3057
3162
  });
3058
3163
  }
3059
3164
  }
3060
3165
 
3061
- results.push(...siteResults);
3166
+ results.push(...batchResults);
3062
3167
 
3063
- processedUrlCount += siteUrlCount;
3064
- urlsSinceLastCleanup += siteUrlCount;
3168
+ processedUrlCount += batchSize;
3169
+ urlsSinceLastCleanup += batchSize;
3065
3170
 
3066
3171
  // Force browser restart if any URL had critical errors
3067
- if (needsImmediateRestart && siteIndex < siteGroups.length - 1) {
3172
+ if (needsImmediateRestart && isNotLastBatch) {
3068
3173
  if (!silentMode) {
3069
3174
  console.log(`\n${messageColors.fileOp('🔄 Emergency browser restart:')} Critical browser errors detected`);
3070
3175
  }
@@ -3084,7 +3189,7 @@ function setupFrameHandling(page, forceDebug) {
3084
3189
  try {
3085
3190
  // Enhanced emergency restart for Puppeteer 23.x
3086
3191
  if (forceDebug) {
3087
- console.log(formatLogMessage('debug', `Emergency restart triggered by errors: ${siteResults.filter(r => r.needsImmediateRestart).map(r => r.error).join(', ')}`));
3192
+ console.log(formatLogMessage('debug', `Emergency restart triggered by errors: ${batchResults.filter(r => r.needsImmediateRestart).map(r => r.error).join(', ')}`));
3088
3193
  }
3089
3194
 
3090
3195
  // Try to gracefully close all pages first
@@ -3113,6 +3218,9 @@ function setupFrameHandling(page, forceDebug) {
3113
3218
  }
3114
3219
  }
3115
3220
 
3221
+ // Clear hang detection interval
3222
+ clearInterval(hangDetectionInterval);
3223
+
3116
3224
  // === POST-SCAN PROCESSING ===
3117
3225
  // Clean up first-party domains and validate results
3118
3226
  if (!dryRunMode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanboynz/network-scanner",
3
- "version": "1.0.80",
3
+ "version": "1.0.82",
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": {