@fanboynz/network-scanner 2.0.18 → 2.0.20

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/README.md CHANGED
@@ -35,8 +35,8 @@ A Puppeteer-based tool for scanning websites to find third-party (or optionally
35
35
 
36
36
  | Argument | Description |
37
37
  |:---------------------------|:------------|
38
- | `--localhost` | Output as `127.0.0.1 domain.com` |
39
- | `--localhost-0.0.0.0` | Output as `0.0.0.0 domain.com` |
38
+ | `--localhost[=IP]` | Output as `IP domain.com` (default: 127.0.0.1) |
39
+ | | Examples: `--localhost`, `--localhost=0.0.0.0`, `--localhost=192.168.1.1` |
40
40
  | `--plain` | Output just domains (no adblock formatting) |
41
41
  | `--dnsmasq` | Output as `local=/domain.com/` (dnsmasq format) |
42
42
  | `--dnsmasq-old` | Output as `server=/domain.com/` (dnsmasq old format) |
@@ -253,6 +253,7 @@ When a page redirects to a new domain, first-party/third-party detection is base
253
253
  | `source` | Boolean | `false` | Save page source HTML after load |
254
254
  | `screenshot` | Boolean | `false` | Capture screenshot on load failure |
255
255
  | `headful` | Boolean | `false` | Launch browser with GUI for this site |
256
+ | `localhost` | String | - | Force custom IP format for this site (e.g., "127.0.0.1", "0.0.0.0", "192.168.1.1") |
256
257
  | `adblock_rules` | Boolean | `false` | Generate adblock filter rules with resource types for this site |
257
258
  | `interact_duration` | Milliseconds | `2000` | Duration of interaction simulation |
258
259
  | `interact_scrolling` | Boolean | `true` | Enable scrolling simulation |
package/lib/output.js CHANGED
@@ -93,8 +93,7 @@ function extractDomainFromRule(rule) {
93
93
  * Formats a domain according to the specified output mode
94
94
  * @param {string} domain - The domain to format
95
95
  * @param {object} options - Formatting options
96
- * @param {boolean} options.localhost - Use 127.0.0.1 format
97
- * @param {boolean} options.localhostAlt - Use 0.0.0.0 format
96
+ * @param {string|null} options.localhostIP - Use custom IP format (e.g., '127.0.0.1', '0.0.0.0')
98
97
  * @param {boolean} options.plain - Use plain domain format (no adblock syntax)
99
98
  * @param {boolean} options.adblockRules - Generate adblock filter rules with resource types
100
99
  * @param {boolean} options.dnsmasq - Use dnsmasq local format
@@ -106,7 +105,7 @@ function extractDomainFromRule(rule) {
106
105
  * @returns {string} The formatted domain
107
106
  */
108
107
  function formatDomain(domain, options = {}) {
109
- const { localhost = false, localhostAlt = false, plain = false, adblockRules = false, dnsmasq = false, dnsmasqOld = false, unbound = false, privoxy = false, pihole = false, resourceType = null } = options;
108
+ const { localhostIP = null, plain = false, adblockRules = false, dnsmasq = false, dnsmasqOld = false, unbound = false, privoxy = false, pihole = false, resourceType = null } = options;
110
109
 
111
110
 
112
111
  // Validate domain length and format
@@ -132,10 +131,8 @@ function formatDomain(domain, options = {}) {
132
131
  return `server=/${domain}/`;
133
132
  } else if (unbound) {
134
133
  return `local-zone: "${domain}." always_null`;
135
- } else if (localhost) {
136
- return `127.0.0.1 ${domain}`;
137
- } else if (localhostAlt) {
138
- return `0.0.0.0 ${domain}`;
134
+ } else if (localhostIP) {
135
+ return `${localhostIP} ${domain}`;
139
136
  } else if (adblockRules && resourceType) {
140
137
  // Generate adblock filter rules with resource type modifiers
141
138
  return `||${domain}^${resourceType}`;
@@ -178,8 +175,7 @@ function mapResourceTypeToAdblockModifier(resourceType) {
178
175
  */
179
176
  function formatRules(matchedDomains, siteConfig = {}, globalOptions = {}) {
180
177
  const {
181
- localhostMode = false,
182
- localhostModeAlt = false,
178
+ localhostIP = null,
183
179
  plainOutput = false,
184
180
  adblockRulesMode = false,
185
181
  dnsmasqMode = false,
@@ -190,8 +186,7 @@ function formatRules(matchedDomains, siteConfig = {}, globalOptions = {}) {
190
186
  } = globalOptions;
191
187
 
192
188
  // Site-level overrides
193
- const siteLocalhost = siteConfig.localhost === true;
194
- const siteLocalhostAlt = siteConfig.localhost_0_0_0_0 === true;
189
+ const siteLocalhostIP = siteConfig.localhost || null;
195
190
  const sitePlainSetting = siteConfig.plain === true;
196
191
  const siteAdblockRules = siteConfig.adblock_rules === true;
197
192
  const siteDnsmasq = siteConfig.dnsmasq === true;
@@ -208,16 +203,14 @@ function formatRules(matchedDomains, siteConfig = {}, globalOptions = {}) {
208
203
  privoxyMode || sitePrivoxy,
209
204
  piholeMode || sitePihole,
210
205
  adblockRulesMode || siteAdblockRules,
211
- localhostMode || siteLocalhost,
212
- localhostModeAlt || siteLocalhostAlt,
206
+ (localhostIP || siteLocalhostIP) ? true : false,
213
207
  plainOutput || sitePlainSetting
214
208
  ].filter(Boolean).length;
215
209
 
216
210
  if (activeFormats > 1) {
217
211
  // Multiple formats specified - fall back to standard adblock format
218
212
  const formatOptions = {
219
- localhost: false,
220
- localhostAlt: false,
213
+ localhostIP: null,
221
214
  plain: false,
222
215
  adblockRules: false,
223
216
  dnsmasq: false,
@@ -240,8 +233,7 @@ function formatRules(matchedDomains, siteConfig = {}, globalOptions = {}) {
240
233
 
241
234
  // Determine final formatting options
242
235
  const formatOptions = {
243
- localhost: localhostMode || siteLocalhost,
244
- localhostAlt: localhostModeAlt || siteLocalhostAlt,
236
+ localhostIP: siteLocalhostIP || localhostIP,
245
237
  plain: plainOutput || sitePlainSetting,
246
238
  adblockRules: adblockRulesMode || siteAdblockRules,
247
239
  dnsmasq: dnsmasqMode || siteDnsmasq,
@@ -338,8 +330,8 @@ function removeDuplicates(lines) {
338
330
  function buildOutputLines(results, options = {}) {
339
331
  const { showTitles = false, removeDupes = false, ignoreDomains = [], forLogFile = false } = options;
340
332
 
341
- // Filter and collect successful results with rules
342
- const finalSiteRules = [];
333
+ // Consolidate rules from all results, handling multiple results for same URL
334
+ const consolidatedRules = new Map(); // URL -> Set of rules
343
335
  let successfulPageLoads = 0;
344
336
 
345
337
  results.forEach(result => {
@@ -348,10 +340,29 @@ function buildOutputLines(results, options = {}) {
348
340
  successfulPageLoads++;
349
341
  }
350
342
  if (result.rules && result.rules.length > 0) {
351
- finalSiteRules.push({ url: result.url, rules: result.rules });
343
+ // Consolidate rules by URL to handle multiple site entries for same URL
344
+ if (!consolidatedRules.has(result.url)) {
345
+ consolidatedRules.set(result.url, new Set());
346
+ }
347
+
348
+ // Add all rules from this result to the consolidated set
349
+ result.rules.forEach(rule => {
350
+ consolidatedRules.get(result.url).add(rule);
351
+ });
352
352
  }
353
353
  }
354
354
  });
355
+
356
+ // Convert consolidated rules back to array format
357
+ const finalSiteRules = [];
358
+ consolidatedRules.forEach((rulesSet, url) => {
359
+ if (rulesSet.size > 0) {
360
+ finalSiteRules.push({
361
+ url: url,
362
+ rules: Array.from(rulesSet)
363
+ });
364
+ }
365
+ });
355
366
 
356
367
  // Build output lines
357
368
  const outputLines = [];
@@ -621,7 +632,7 @@ function handleOutput(results, config = {}) {
621
632
  * @returns {string} Human-readable format description
622
633
  */
623
634
  function getFormatDescription(options = {}) {
624
- const { localhost = false, localhostAlt = false, plain = false, adblockRules = false, dnsmasq = false, dnsmasqOld = false, unbound = false, privoxy = false, pihole = false } = options;
635
+ const { localhostIP = null, plain = false, adblockRules = false, dnsmasq = false, dnsmasqOld = false, unbound = false, privoxy = false, pihole = false } = options;
625
636
 
626
637
  // Plain always takes precedence
627
638
  if (plain) {
@@ -640,10 +651,8 @@ function getFormatDescription(options = {}) {
640
651
  return 'Unbound format (local-zone: "domain.com." always_null)';
641
652
  } else if (adblockRules) {
642
653
  return 'Adblock filter rules with resource type modifiers (||domain.com^$script)';
643
- } else if (localhost) {
644
- return 'Localhost format (127.0.0.1 domain.com)';
645
- } else if (localhostAlt) {
646
- return 'Localhost format (0.0.0.0 domain.com)';
654
+ } else if (localhostIP) {
655
+ return `Localhost format (${localhostIP} domain.com)`;
647
656
  } else {
648
657
  return 'Adblock format (||domain.com^)';
649
658
  }
package/nwss.1 CHANGED
@@ -33,12 +33,15 @@ Enable colored console output for status messages.
33
33
 
34
34
  .SS Output Format Options
35
35
  .TP
36
- .B \--localhost
37
- Output rules as \fB127.0.0.1 domain.com\fR format for hosts file.
36
+ .BR \--localhost [ =\fIIP\fR ]
37
+ Output rules as \fBIP domain.com\fR format for hosts file. If no IP is specified, defaults to \fB127.0.0.1\fR.
38
38
 
39
- .TP
40
- .B \--localhost-0.0.0.0
41
- Output rules as \fB0.0.0.0 domain.com\fR format for hosts file.
39
+ Examples:
40
+ .RS
41
+ \fB\--localhost\fR (uses 127.0.0.1)
42
+ \fB\--localhost=0.0.0.0\fR
43
+ \fB\--localhost=192.168.1.1\fR
44
+ .RE
42
45
 
43
46
  .TP
44
47
  .B \--plain
@@ -578,11 +581,9 @@ Number. Output full subdomains instead of root domains (1/0).
578
581
 
579
582
  .TP
580
583
  .B localhost
581
- Boolean. Force localhost output format (127.0.0.1) for this site.
584
+ String. Force custom IP format for this site. Examples: \fB"127.0.0.1"\fR, \fB"0.0.0.0"\fR, \fB"192.168.1.1"\fR, \fB"10.0.0.1"\fR.
582
585
 
583
- .TP
584
- .B localhost_0_0_0_0
585
- Boolean. Force localhost output format (0.0.0.0) for this site.
586
+ When set, overrides global \fB\--localhost\fR flag for this specific site.
586
587
 
587
588
  .TP
588
589
  .B dnsmasq
@@ -970,9 +971,11 @@ Format: \fB(^|\\.)domain\\.com$\fR
970
971
  For Pi-hole regex filters. Blocks domain and subdomains at DNS level.
971
972
 
972
973
  .SS Hosts File Formats
973
- Flags: \fB\--localhost\fR, \fB\--localhost-0.0.0.0\fR
974
+ Flag: \fB\--localhost[=IP]\fR
974
975
  .br
975
- Formats: \fB127.0.0.1 domain.com\fR, \fB0.0.0.0 domain.com\fR
976
+ Format: \fBIP domain.com\fR (default IP: 127.0.0.1)
977
+ .br
978
+ Examples: \fB\--localhost\fR, \fB\--localhost=0.0.0.0\fR, \fB\--localhost=10.0.0.1\fR
976
979
  .br
977
980
  For system hosts files.
978
981
 
@@ -988,6 +991,19 @@ Format: \fBdomain.com\fR
988
991
  .br
989
992
  Simple domain list without formatting.
990
993
 
994
+ .SS Custom IP Examples
995
+ .EX
996
+ # Standard localhost blocking
997
+ node nwss.js -o hosts.txt --localhost
998
+
999
+ # Block to null route
1000
+ node nwss.js -o hosts.txt --localhost=0.0.0.0
1001
+
1002
+ # Route to local server
1003
+ node nwss.js -o hosts.txt --localhost=192.168.1.100
1004
+ node nwss.js -o hosts.txt --localhost=10.0.0.1
1005
+ .EE
1006
+
991
1007
  .SH FILES
992
1008
 
993
1009
  .TP
@@ -1090,6 +1106,16 @@ Report bugs to the project repository or maintainer.
1090
1106
  .BR unbound (8),
1091
1107
  .BR privoxy (8)
1092
1108
 
1109
+ .SH EXAMPLES OF CONFIG WITH CUSTOM LOCALHOST IP
1110
+ .EX
1111
+ {
1112
+ "url": "https://example.com",
1113
+ "filterRegex": "ads|tracking",
1114
+ "localhost": "10.0.0.1"
1115
+ }
1116
+ .EE
1117
+ This configuration will output rules in the format \fB10.0.0.1 domain.com\fR for this specific site, overriding any global \fB\--localhost\fR flag.
1118
+
1093
1119
  .SH AUTHORS
1094
1120
  Written for malware research and network security analysis.
1095
1121
 
package/nwss.js CHANGED
@@ -1,4 +1,4 @@
1
- // === Network scanner script (nwss.js) v2.0.18 ===
1
+ // === Network scanner script (nwss.js) v2.0.20 ===
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
@@ -130,7 +130,7 @@ const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/r
130
130
  const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive, performGroupWindowCleanup, performRealtimeWindowCleanup, trackPageForRealtime, updatePageUsage, cleanupPageBeforeReload } = require('./lib/browserhealth');
131
131
 
132
132
  // --- Script Configuration & Constants ---
133
- const VERSION = '2.0.18'; // Script version
133
+ const VERSION = '2.0.20'; // Script version
134
134
 
135
135
  // get startTime
136
136
  const startTime = Date.now();
@@ -173,8 +173,12 @@ const silentMode = args.includes('--silent');
173
173
  const showTitles = args.includes('--titles');
174
174
  const dumpUrls = args.includes('--dumpurls');
175
175
  const subDomainsMode = args.includes('--sub-domains');
176
- const localhostMode = args.includes('--localhost');
177
- const localhostModeAlt = args.includes('--localhost-0.0.0.0');
176
+ // Parse --localhost with optional IP address
177
+ let localhostIP = null;
178
+ const localhostIndex = args.findIndex(arg => arg.startsWith('--localhost'));
179
+ if (localhostIndex !== -1) {
180
+ localhostIP = args[localhostIndex].includes('=') ? args[localhostIndex].split('=')[1] : '127.0.0.1';
181
+ }
178
182
  const disableInteract = args.includes('--no-interact');
179
183
  const plainOutput = args.includes('--plain');
180
184
  const enableCDP = args.includes('--cdp');
@@ -230,7 +234,7 @@ if (adblockRulesMode) {
230
234
  if (!outputFile) {
231
235
  if (forceDebug) console.log(formatLogMessage('debug', `--adblock-rules ignored: requires --output (-o) to specify an output file`));
232
236
  adblockRulesMode = false;
233
- } else if (localhostMode || localhostModeAlt || plainOutput || dnsmasqMode || dnsmasqOldMode || unboundMode || privoxyMode || piholeMode) {
237
+ } else if (localhostIP || plainOutput || dnsmasqMode || dnsmasqOldMode || unboundMode || privoxyMode || piholeMode) {
234
238
  if (forceDebug) console.log(formatLogMessage('debug', `--adblock-rules ignored: incompatible with localhost/plain output modes`));
235
239
  adblockRulesMode = false;
236
240
  }
@@ -238,7 +242,7 @@ if (adblockRulesMode) {
238
242
 
239
243
  // Validate --dnsmasq usage
240
244
  if (dnsmasqMode) {
241
- if (localhostMode || localhostModeAlt || plainOutput || adblockRulesMode || dnsmasqOldMode || unboundMode || privoxyMode || piholeMode) {
245
+ if (localhostIP || plainOutput || adblockRulesMode || dnsmasqOldMode || unboundMode || privoxyMode || piholeMode) {
242
246
  if (forceDebug) console.log(formatLogMessage('debug', `--dnsmasq-old ignored: incompatible with localhost/plain/adblock-rules/dnsmasq output modes`));
243
247
  dnsmasqMode = false;
244
248
  }
@@ -246,7 +250,7 @@ if (dnsmasqMode) {
246
250
 
247
251
  // Validate --dnsmasq-old usage
248
252
  if (dnsmasqOldMode) {
249
- if (localhostMode || localhostModeAlt || plainOutput || adblockRulesMode || dnsmasqMode || unboundMode || privoxyMode || piholeMode) {
253
+ if (localhostIP || plainOutput || adblockRulesMode || dnsmasqMode || unboundMode || privoxyMode || piholeMode) {
250
254
  if (forceDebug) console.log(formatLogMessage('debug', `--dnsmasq-old ignored: incompatible with localhost/plain/adblock-rules/dnsmasq output modes`));
251
255
  dnsmasqOldMode = false;
252
256
  }
@@ -254,7 +258,7 @@ if (dnsmasqOldMode) {
254
258
 
255
259
  // Validate --unbound usage
256
260
  if (unboundMode) {
257
- if (localhostMode || localhostModeAlt || plainOutput || adblockRulesMode || dnsmasqMode || dnsmasqOldMode || privoxyMode || piholeMode) {
261
+ if (localhostIP || plainOutput || adblockRulesMode || dnsmasqMode || dnsmasqOldMode || privoxyMode || piholeMode) {
258
262
  if (forceDebug) console.log(formatLogMessage('debug', `--unbound ignored: incompatible with localhost/plain/adblock-rules/dnsmasq output modes`));
259
263
  unboundMode = false;
260
264
  }
@@ -262,7 +266,7 @@ if (unboundMode) {
262
266
 
263
267
  // Validate --privoxy usage
264
268
  if (privoxyMode) {
265
- if (localhostMode || localhostModeAlt || plainOutput || adblockRulesMode || dnsmasqMode || dnsmasqOldMode || unboundMode || piholeMode) {
269
+ if (localhostIP || plainOutput || adblockRulesMode || dnsmasqMode || dnsmasqOldMode || unboundMode || piholeMode) {
266
270
  if (forceDebug) console.log(formatLogMessage('debug', `--privoxy ignored: incompatible with localhost/plain/adblock-rules/dnsmasq/unbound output modes`));
267
271
  privoxyMode = false;
268
272
  }
@@ -270,7 +274,7 @@ if (privoxyMode) {
270
274
 
271
275
  // Validate --pihole usage
272
276
  if (piholeMode) {
273
- if (localhostMode || localhostModeAlt || plainOutput || adblockRulesMode || dnsmasqMode || dnsmasqOldMode || unboundMode || privoxyMode) {
277
+ if (localhostIP || plainOutput || adblockRulesMode || dnsmasqMode || dnsmasqOldMode || unboundMode || privoxyMode) {
274
278
  if (forceDebug) console.log(formatLogMessage('debug', `--pihole ignored: incompatible with localhost/plain/adblock-rules/dnsmasq/unbound/privoxy output modes`));
275
279
  piholeMode = false;
276
280
  }
@@ -430,8 +434,8 @@ Options:
430
434
  --append Append new rules to output file instead of overwriting (requires -o)
431
435
 
432
436
  Output Format Options:
433
- --localhost Output as 127.0.0.1 domain.com
434
- --localhost-0.0.0.0 Output as 0.0.0.0 domain.com
437
+ --localhost[=IP] Output as IP domain.com (default: 127.0.0.1)
438
+ Examples: --localhost, --localhost=0.0.0.0, --localhost=192.168.1.1
435
439
  --plain Output just domains (no adblock formatting)
436
440
  --dnsmasq Output as local=/domain.com/ (dnsmasq format)
437
441
  --dnsmasq-old Output as server=/domain.com/ (dnsmasq old format)
@@ -1478,8 +1482,7 @@ function setupFrameHandling(page, forceDebug) {
1478
1482
  const allowFirstParty = siteConfig.firstParty === true || siteConfig.firstParty === 1;
1479
1483
  const allowThirdParty = siteConfig.thirdParty === undefined || siteConfig.thirdParty === true || siteConfig.thirdParty === 1;
1480
1484
  const perSiteSubDomains = siteConfig.subDomains === 1 ? true : subDomainsMode;
1481
- const siteLocalhost = siteConfig.localhost === true;
1482
- const siteLocalhostAlt = siteConfig.localhost_0_0_0_0 === true;
1485
+ const siteLocalhostIP = siteConfig.localhost || null;
1483
1486
  const cloudflarePhishBypass = siteConfig.cloudflare_phish === true;
1484
1487
  const cloudflareBypass = siteConfig.cloudflare_bypass === true;
1485
1488
  // Add redirect and same-page loop protection
@@ -3392,8 +3395,7 @@ function setupFrameHandling(page, forceDebug) {
3392
3395
  } else {
3393
3396
  // Format rules using the output module
3394
3397
  const globalOptions = {
3395
- localhostMode,
3396
- localhostModeAlt,
3398
+ localhostIP,
3397
3399
  plainOutput,
3398
3400
  adblockRulesMode,
3399
3401
  dnsmasqMode,
@@ -3434,8 +3436,7 @@ function setupFrameHandling(page, forceDebug) {
3434
3436
  // For other errors, preserve any matches we found before the error
3435
3437
  if (matchedDomains && (matchedDomains.size > 0 || (matchedDomains instanceof Map && matchedDomains.size > 0))) {
3436
3438
  const globalOptions = {
3437
- localhostMode,
3438
- localhostModeAlt,
3439
+ localhostIP,
3439
3440
  plainOutput,
3440
3441
  adblockRulesMode,
3441
3442
  dnsmasqMode,
@@ -3883,8 +3884,7 @@ function setupFrameHandling(page, forceDebug) {
3883
3884
  const detectedDomainsCount = getDetectedDomainsCount();
3884
3885
  if (forceDebug) {
3885
3886
  const globalOptions = {
3886
- localhostMode,
3887
- localhostModeAlt,
3887
+ localhostIP,
3888
3888
  plainOutput,
3889
3889
  adblockRules: adblockRulesMode,
3890
3890
  dnsmasq: dnsmasqMode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanboynz/network-scanner",
3
- "version": "2.0.18",
3
+ "version": "2.0.20",
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": {