@fanboynz/network-scanner 2.0.66 → 3.0.0
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/.github/workflows/npm-publish.yml +134 -10
- package/CHANGELOG.md +135 -0
- package/CLAUDE.md +18 -7
- package/README.md +12 -4
- package/lib/adblock-rust.js +23 -18
- package/lib/adblock.js +127 -82
- package/lib/browserexit.js +210 -200
- package/lib/browserhealth.js +84 -60
- package/lib/cdp.js +103 -81
- package/lib/clear_sitedata.js +61 -159
- package/lib/cloudflare.js +579 -409
- package/lib/colorize.js +29 -12
- package/lib/compare.js +16 -8
- package/lib/compress.js +2 -1
- package/lib/curl.js +287 -220
- package/lib/domain-cache.js +87 -40
- package/lib/dry-run.js +137 -194
- package/lib/fingerprint.js +20 -18
- package/lib/flowproxy.js +391 -188
- package/lib/ghost-cursor.js +8 -7
- package/lib/grep.js +248 -171
- package/lib/ignore_similar.js +70 -124
- package/lib/interaction.js +132 -235
- package/lib/nettools.js +309 -87
- package/lib/openvpn_vpn.js +12 -11
- package/lib/output.js +92 -59
- package/lib/post-processing.js +216 -162
- package/lib/redirect.js +46 -30
- package/lib/referrer.js +158 -165
- package/lib/searchstring.js +290 -381
- package/lib/smart-cache.js +141 -91
- package/lib/socks-relay.js +8 -7
- package/lib/spawn-async.js +137 -0
- package/lib/validate_rules.js +188 -176
- package/lib/wireguard_vpn.js +111 -117
- package/nwss.js +740 -156
- package/package.json +4 -4
package/lib/openvpn_vpn.js
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
const { execSync, spawn } = require('child_process');
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const { formatLogMessage } = require('./colorize');
|
|
14
|
+
const { formatLogMessage, messageColors } = require('./colorize');
|
|
15
|
+
const OPENVPN_TAG = messageColors.processing('[openvpn]');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Fetch external IP address through the active tunnel
|
|
@@ -263,7 +264,7 @@ function waitForConnection(child, logPath, timeout, forceDebug) {
|
|
|
263
264
|
const tunDevice = findTunDevice(child.pid);
|
|
264
265
|
if (forceDebug) {
|
|
265
266
|
console.log(formatLogMessage('debug',
|
|
266
|
-
|
|
267
|
+
`${OPENVPN_TAG} Connected (tun: ${tunDevice || 'unknown'}, ${Date.now() - startTime}ms)`
|
|
267
268
|
));
|
|
268
269
|
}
|
|
269
270
|
done({ connected: true, tunDevice });
|
|
@@ -352,7 +353,7 @@ async function startConnection(configPath, vpnConfig, forceDebug = false) {
|
|
|
352
353
|
|
|
353
354
|
if (activeConnections.has(connectionName)) {
|
|
354
355
|
if (forceDebug) {
|
|
355
|
-
console.log(formatLogMessage('debug',
|
|
356
|
+
console.log(formatLogMessage('debug', `${OPENVPN_TAG} ${connectionName} already active`));
|
|
356
357
|
}
|
|
357
358
|
const existing = activeConnections.get(connectionName);
|
|
358
359
|
return { success: true, connection: connectionName, tunDevice: existing.tunDevice, alreadyActive: true };
|
|
@@ -379,7 +380,7 @@ async function startConnection(configPath, vpnConfig, forceDebug = false) {
|
|
|
379
380
|
const args = buildArgs(configPath, vpnConfig, connectionName, logPath);
|
|
380
381
|
|
|
381
382
|
if (forceDebug) {
|
|
382
|
-
console.log(formatLogMessage('debug',
|
|
383
|
+
console.log(formatLogMessage('debug', `${OPENVPN_TAG} Starting: openvpn ${args.join(' ')}`));
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
// Spawn OpenVPN — it daemonizes itself via --daemon, but we spawn
|
|
@@ -458,7 +459,7 @@ function stopConnection(connectionName, forceDebug = false) {
|
|
|
458
459
|
} catch (killErr) {
|
|
459
460
|
// Process may already be dead
|
|
460
461
|
if (forceDebug) {
|
|
461
|
-
console.log(formatLogMessage('debug',
|
|
462
|
+
console.log(formatLogMessage('debug', `${OPENVPN_TAG} Kill error (may be already dead): ${killErr.message}`));
|
|
462
463
|
}
|
|
463
464
|
}
|
|
464
465
|
|
|
@@ -468,7 +469,7 @@ function stopConnection(connectionName, forceDebug = false) {
|
|
|
468
469
|
cleanupConnectionFiles(connectionName);
|
|
469
470
|
|
|
470
471
|
if (forceDebug) {
|
|
471
|
-
console.log(formatLogMessage('debug',
|
|
472
|
+
console.log(formatLogMessage('debug', `${OPENVPN_TAG} ${connectionName} stopped`));
|
|
472
473
|
}
|
|
473
474
|
|
|
474
475
|
return { success: true };
|
|
@@ -544,7 +545,7 @@ function checkConnection(connectionName, testHost = '1.1.1.1', forceDebug = fals
|
|
|
544
545
|
|
|
545
546
|
if (forceDebug) {
|
|
546
547
|
console.log(formatLogMessage('debug',
|
|
547
|
-
|
|
548
|
+
`${OPENVPN_TAG} ${connectionName} connected (${latencyMs ? latencyMs + 'ms' : 'ok'})`
|
|
548
549
|
));
|
|
549
550
|
}
|
|
550
551
|
|
|
@@ -552,7 +553,7 @@ function checkConnection(connectionName, testHost = '1.1.1.1', forceDebug = fals
|
|
|
552
553
|
} catch (error) {
|
|
553
554
|
if (forceDebug) {
|
|
554
555
|
console.log(formatLogMessage('debug',
|
|
555
|
-
|
|
556
|
+
`${OPENVPN_TAG} ${connectionName} health check failed: ${error.message.split('\n')[0]}`
|
|
556
557
|
));
|
|
557
558
|
}
|
|
558
559
|
return { connected: false, error: error.message.split('\n')[0] };
|
|
@@ -756,7 +757,7 @@ async function connectForSite(siteConfig, forceDebug = false) {
|
|
|
756
757
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
757
758
|
if (forceDebug && attempt > 1) {
|
|
758
759
|
console.log(formatLogMessage('debug',
|
|
759
|
-
|
|
760
|
+
`${OPENVPN_TAG} Retry ${attempt - 1}/${ovpnConfig.max_retries} for ${connectionName}`
|
|
760
761
|
));
|
|
761
762
|
}
|
|
762
763
|
|
|
@@ -833,7 +834,7 @@ function disconnectForSite(siteConfig, forceDebug = false) {
|
|
|
833
834
|
|
|
834
835
|
if (forceDebug) {
|
|
835
836
|
console.log(formatLogMessage('debug',
|
|
836
|
-
|
|
837
|
+
`${OPENVPN_TAG} ${connectionName} still used by ${info.sites.size} site(s), keeping up`
|
|
837
838
|
));
|
|
838
839
|
}
|
|
839
840
|
|
|
@@ -863,7 +864,7 @@ function disconnectAll(forceDebug = false) {
|
|
|
863
864
|
|
|
864
865
|
if (forceDebug && results.tornDown > 0) {
|
|
865
866
|
console.log(formatLogMessage('debug',
|
|
866
|
-
|
|
867
|
+
`${OPENVPN_TAG} Disconnected ${results.tornDown} connection(s)`
|
|
867
868
|
));
|
|
868
869
|
}
|
|
869
870
|
|
package/lib/output.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
// Import domain cache functions for statistics
|
|
4
|
-
const { getTotalDomainsSkipped } = require('./domain-cache');
|
|
5
3
|
const { loadComparisonRules, filterUniqueRules } = require('./compare');
|
|
6
|
-
const {
|
|
4
|
+
const { messageColors, formatLogMessage } = require('./colorize');
|
|
5
|
+
const OUTPUT_FILTER_TAG = messageColors.processing('[output-filter]');
|
|
7
6
|
|
|
8
7
|
// Cache for compiled wildcard regex patterns in matchesIgnoreDomain (capped to prevent memory leak)
|
|
9
8
|
const wildcardRegexCache = new Map();
|
|
@@ -40,15 +39,28 @@ function matchesIgnoreDomain(domain, ignorePatterns) {
|
|
|
40
39
|
const baseDomain = pattern.slice(0, -2); // Remove ".*"
|
|
41
40
|
return domain.startsWith(baseDomain + '.');
|
|
42
41
|
} else {
|
|
43
|
-
// Complex wildcard pattern (cached)
|
|
42
|
+
// Complex wildcard pattern (cached). Escape every regex meta-char
|
|
43
|
+
// EXCEPT '*' first, then expand '*' to '.*'. The old code only
|
|
44
|
+
// escaped '.', so a pattern containing '+', '(', '[', '?', etc.
|
|
45
|
+
// would either misbehave (e.g. 'foo+bar.com' would treat '+' as a
|
|
46
|
+
// quantifier) or throw synchronously (unmatched '(' / '[') and the
|
|
47
|
+
// exception would propagate out of .some(). Domain names can't
|
|
48
|
+
// legally contain those chars, but a typo in a user's ignore list
|
|
49
|
+
// would crash the output stage.
|
|
44
50
|
if (!wildcardRegexCache.has(pattern)) {
|
|
45
51
|
if (wildcardRegexCache.size >= WILDCARD_CACHE_MAX) {
|
|
46
52
|
wildcardRegexCache.delete(wildcardRegexCache.keys().next().value);
|
|
47
53
|
}
|
|
48
54
|
const regexPattern = pattern
|
|
49
|
-
.replace(
|
|
50
|
-
.replace(/\*/g, '.*');
|
|
51
|
-
|
|
55
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape all regex meta-chars except '*'
|
|
56
|
+
.replace(/\*/g, '.*'); // Now expand '*' to '.*'
|
|
57
|
+
try {
|
|
58
|
+
wildcardRegexCache.set(pattern, new RegExp(`^${regexPattern}$`));
|
|
59
|
+
} catch (_) {
|
|
60
|
+
// Defensive: a still-malformed regex (shouldn't happen after the
|
|
61
|
+
// escape above) becomes a never-match instead of a crash.
|
|
62
|
+
wildcardRegexCache.set(pattern, /(?!)/);
|
|
63
|
+
}
|
|
52
64
|
}
|
|
53
65
|
return wildcardRegexCache.get(pattern).test(domain);
|
|
54
66
|
}
|
|
@@ -389,7 +401,7 @@ function buildOutputLines(results, options = {}) {
|
|
|
389
401
|
if (domain && matchesIgnoreDomain(domain, ignoreDomains)) {
|
|
390
402
|
filteredOutCount++;
|
|
391
403
|
if (options.forceDebug) {
|
|
392
|
-
console.log(formatLogMessage('debug',
|
|
404
|
+
console.log(formatLogMessage('debug', `${OUTPUT_FILTER_TAG} Removed rule matching ignoreDomains: ${rule} (domain: ${domain})`));
|
|
393
405
|
} else if (!options.silentMode) {
|
|
394
406
|
console.log(formatLogMessage('info', `Filtered out: ${domain}`));
|
|
395
407
|
}
|
|
@@ -409,7 +421,7 @@ function buildOutputLines(results, options = {}) {
|
|
|
409
421
|
// Log filtered domains if any were removed
|
|
410
422
|
if (filteredOutCount > 0) {
|
|
411
423
|
if (options.forceDebug) {
|
|
412
|
-
console.log(formatLogMessage('debug',
|
|
424
|
+
console.log(formatLogMessage('debug', `${OUTPUT_FILTER_TAG} Total: ${filteredOutCount} rules filtered out matching ignoreDomains patterns`));
|
|
413
425
|
} else if (!options.silentMode) {
|
|
414
426
|
console.log(formatLogMessage('info', `${filteredOutCount} domains filtered out by ignoreDomains`));
|
|
415
427
|
}
|
|
@@ -456,7 +468,7 @@ function writeOutput(lines, outputFile = null, silentMode = false) {
|
|
|
456
468
|
}
|
|
457
469
|
return true;
|
|
458
470
|
} catch (error) {
|
|
459
|
-
console.error(
|
|
471
|
+
console.error(`Failed to write output: ${error.message}`);
|
|
460
472
|
return false;
|
|
461
473
|
}
|
|
462
474
|
}
|
|
@@ -478,39 +490,41 @@ function handleOutput(results, config = {}) {
|
|
|
478
490
|
dumpUrls = false,
|
|
479
491
|
adblockRulesLogFile = null,
|
|
480
492
|
forceDebug = false,
|
|
481
|
-
ignoreDomains = []
|
|
482
|
-
totalDomainsSkipped = null // Allow override or get from cache
|
|
493
|
+
ignoreDomains = []
|
|
483
494
|
} = config;
|
|
484
495
|
|
|
485
496
|
// Handle append mode
|
|
486
497
|
if (outputFile && appendMode) {
|
|
487
498
|
try {
|
|
488
|
-
// Build output lines first
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
499
|
+
// Build output lines first. buildOutputLines already applies
|
|
500
|
+
// removeDuplicates internally when removeDupes is true, so we don't
|
|
501
|
+
// need a second pass here.
|
|
502
|
+
const {
|
|
503
|
+
outputLines,
|
|
504
|
+
outputLinesWithTitles,
|
|
492
505
|
successfulPageLoads,
|
|
493
506
|
totalRules,
|
|
494
507
|
filteredOutCount
|
|
495
|
-
} = buildOutputLines(results, { showTitles, removeDupes, ignoreDomains
|
|
496
|
-
|
|
497
|
-
// Apply remove-dupes to new results if requested (before comparing to existing file)
|
|
498
|
-
const deduplicatedOutputLines = removeDupes ? removeDuplicates(outputLines) : outputLines;
|
|
499
|
-
if (removeDupes && forceDebug) console.log(formatLogMessage('debug', `Applied --remove-dupes to new scan results before append comparison`));
|
|
508
|
+
} = buildOutputLines(results, { showTitles, removeDupes, ignoreDomains, forceDebug });
|
|
509
|
+
const deduplicatedOutputLines = outputLines;
|
|
500
510
|
|
|
501
|
-
// Read existing file content
|
|
511
|
+
// Read existing file content via a single open() instead of stat+open
|
|
512
|
+
// (and avoid TOCTOU between an existsSync check and the read).
|
|
502
513
|
let existingContent = '';
|
|
503
|
-
|
|
514
|
+
try {
|
|
504
515
|
existingContent = fs.readFileSync(outputFile, 'utf8');
|
|
505
|
-
}
|
|
516
|
+
} catch (readErr) {
|
|
517
|
+
if (readErr.code !== 'ENOENT') throw readErr;
|
|
506
518
|
// File doesn't exist - append mode should create it
|
|
507
519
|
if (forceDebug) console.log(formatLogMessage('debug', `Append mode: Creating new file ${outputFile}`));
|
|
508
520
|
}
|
|
509
|
-
|
|
510
|
-
// Parse existing rules for comparison (exclude comments)
|
|
521
|
+
|
|
522
|
+
// Parse existing rules for comparison (exclude comments). Hoist the
|
|
523
|
+
// single .trim() into a local so we don't walk the file content twice.
|
|
511
524
|
const existingRules = new Set();
|
|
512
|
-
|
|
513
|
-
|
|
525
|
+
const trimmedExisting = existingContent.trim();
|
|
526
|
+
if (trimmedExisting) {
|
|
527
|
+
const lines = trimmedExisting.split('\n');
|
|
514
528
|
lines.forEach(line => {
|
|
515
529
|
const cleanLine = line.trim();
|
|
516
530
|
if (cleanLine && !cleanLine.startsWith('!') && !cleanLine.startsWith('#')) {
|
|
@@ -524,39 +538,47 @@ function handleOutput(results, config = {}) {
|
|
|
524
538
|
return rule.startsWith('!') || !existingRules.has(rule);
|
|
525
539
|
});
|
|
526
540
|
|
|
541
|
+
// Count non-comment rules once and reuse below (was three throwaway
|
|
542
|
+
// filter-array allocations: success log, else-branch log, return obj).
|
|
543
|
+
let newRuleCount = 0;
|
|
544
|
+
for (let i = 0; i < newRules.length; i++) {
|
|
545
|
+
if (!newRules[i].startsWith('!')) newRuleCount++;
|
|
546
|
+
}
|
|
547
|
+
|
|
527
548
|
if (newRules.length > 0) {
|
|
528
549
|
// Prepare content to append
|
|
529
550
|
let appendContent = '';
|
|
530
|
-
|
|
551
|
+
|
|
531
552
|
// Ensure there's a newline before appending if file has content
|
|
532
553
|
if (existingContent && !existingContent.endsWith('\n')) {
|
|
533
554
|
appendContent = '\n';
|
|
534
555
|
}
|
|
535
|
-
|
|
556
|
+
|
|
536
557
|
// Add new rules
|
|
537
558
|
appendContent += newRules.join('\n') + '\n';
|
|
538
|
-
|
|
559
|
+
|
|
539
560
|
// Append to file
|
|
540
561
|
fs.appendFileSync(outputFile, appendContent);
|
|
541
|
-
|
|
542
|
-
const newRuleCount = newRules.filter(rule => !rule.startsWith('!')).length;
|
|
562
|
+
|
|
543
563
|
if (!silentMode) {
|
|
544
|
-
console.log(`${messageColors.success('
|
|
564
|
+
console.log(`${messageColors.success('Appended')} ${newRuleCount} new rules to: ${outputFile} (${existingRules.size} rules already existed${removeDupes ? ', duplicates removed' : ''})`);
|
|
545
565
|
}
|
|
546
|
-
} else {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
566
|
+
} else if (!silentMode) {
|
|
567
|
+
// No new rules — report the dedup'd input count instead. Same loop
|
|
568
|
+
// pattern as above to avoid filter().length allocating an array.
|
|
569
|
+
let ruleCount = 0;
|
|
570
|
+
for (let i = 0; i < deduplicatedOutputLines.length; i++) {
|
|
571
|
+
if (!deduplicatedOutputLines[i].startsWith('!')) ruleCount++;
|
|
550
572
|
}
|
|
573
|
+
console.log(`${messageColors.info('No new rules')} to append - all ${ruleCount} rules already exist in: ${outputFile}`);
|
|
551
574
|
}
|
|
552
|
-
|
|
575
|
+
|
|
553
576
|
// Write log file output if --dumpurls is enabled
|
|
554
577
|
let logSuccess = true;
|
|
555
578
|
if (dumpUrls && adblockRulesLogFile) {
|
|
556
579
|
logSuccess = writeOutput(outputLinesWithTitles, adblockRulesLogFile, silentMode);
|
|
557
580
|
}
|
|
558
|
-
|
|
559
|
-
const newRuleCount = newRules.filter(rule => !rule.startsWith('!')).length;
|
|
581
|
+
|
|
560
582
|
return {
|
|
561
583
|
success: logSuccess,
|
|
562
584
|
outputFile,
|
|
@@ -571,34 +593,42 @@ function handleOutput(results, config = {}) {
|
|
|
571
593
|
};
|
|
572
594
|
|
|
573
595
|
} catch (appendErr) {
|
|
574
|
-
console.error(
|
|
596
|
+
console.error(`Failed to append to ${outputFile}: ${appendErr.message}`);
|
|
575
597
|
return { success: false };
|
|
576
598
|
}
|
|
577
599
|
}
|
|
578
600
|
|
|
579
601
|
// Build output lines
|
|
580
|
-
const {
|
|
581
|
-
outputLines,
|
|
582
|
-
outputLinesWithTitles,
|
|
602
|
+
const {
|
|
603
|
+
outputLines,
|
|
604
|
+
outputLinesWithTitles,
|
|
583
605
|
successfulPageLoads,
|
|
584
606
|
totalRules,
|
|
585
607
|
filteredOutCount
|
|
586
|
-
} = buildOutputLines(results, { showTitles, removeDupes, ignoreDomains
|
|
608
|
+
} = buildOutputLines(results, { showTitles, removeDupes, ignoreDomains, forceDebug });
|
|
587
609
|
|
|
588
610
|
// Apply comparison filtering if compareFile is specified
|
|
589
611
|
let filteredOutputLines = outputLines;
|
|
590
612
|
if (compareFile && outputLines.length > 0) {
|
|
591
613
|
try {
|
|
592
|
-
const comparisonRules = loadComparisonRules(compareFile,
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
614
|
+
const comparisonRules = loadComparisonRules(compareFile, forceDebug);
|
|
615
|
+
// Count non-comment lines once each side instead of building filter
|
|
616
|
+
// arrays just to read .length (was three allocations per log line).
|
|
617
|
+
let originalCount = 0;
|
|
618
|
+
for (let i = 0; i < outputLines.length; i++) {
|
|
619
|
+
if (!outputLines[i].startsWith('!')) originalCount++;
|
|
620
|
+
}
|
|
621
|
+
filteredOutputLines = filterUniqueRules(outputLines, comparisonRules, forceDebug);
|
|
598
622
|
|
|
623
|
+
if (!silentMode) {
|
|
624
|
+
let uniqueCount = 0;
|
|
625
|
+
for (let i = 0; i < filteredOutputLines.length; i++) {
|
|
626
|
+
if (!filteredOutputLines[i].startsWith('!')) uniqueCount++;
|
|
627
|
+
}
|
|
628
|
+
console.log(formatLogMessage('compare', `Filtered ${originalCount - uniqueCount} existing rules, ${uniqueCount} unique rules remaining`));
|
|
599
629
|
}
|
|
600
630
|
} catch (compareError) {
|
|
601
|
-
console.error(messageColors.error('
|
|
631
|
+
console.error(messageColors.error('Compare operation failed:') + ` ${compareError.message}`);
|
|
602
632
|
return { success: false, totalRules: 0, successfulPageLoads: 0 };
|
|
603
633
|
}
|
|
604
634
|
}
|
|
@@ -612,21 +642,24 @@ function handleOutput(results, config = {}) {
|
|
|
612
642
|
logSuccess = writeOutput(outputLinesWithTitles, adblockRulesLogFile, silentMode);
|
|
613
643
|
}
|
|
614
644
|
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
645
|
+
// Count non-comment lines once (used by totalRules below). Doing this with a
|
|
646
|
+
// single loop avoids the .filter().length pattern that allocates a throwaway
|
|
647
|
+
// array. Callers that want totalDomainsSkipped should call
|
|
648
|
+
// getTotalDomainsSkipped() from ./domain-cache directly.
|
|
649
|
+
let finalRuleCount = 0;
|
|
650
|
+
for (let i = 0; i < filteredOutputLines.length; i++) {
|
|
651
|
+
if (!filteredOutputLines[i].startsWith('!')) finalRuleCount++;
|
|
652
|
+
}
|
|
618
653
|
|
|
619
654
|
return {
|
|
620
655
|
success: mainSuccess && logSuccess,
|
|
621
656
|
outputFile,
|
|
622
657
|
adblockRulesLogFile,
|
|
623
658
|
successfulPageLoads,
|
|
624
|
-
totalRules:
|
|
659
|
+
totalRules: finalRuleCount,
|
|
625
660
|
filteredOutCount,
|
|
626
661
|
totalLines: filteredOutputLines.length,
|
|
627
662
|
outputLines: outputFile ? null : filteredOutputLines // Only return lines if not written to file
|
|
628
|
-
// Note: totalDomainsSkipped statistic is now available via getTotalDomainsSkipped()
|
|
629
|
-
// and doesn't need to be passed through the output handler
|
|
630
663
|
};
|
|
631
664
|
}
|
|
632
665
|
|