@fanboynz/network-scanner 2.0.55 → 2.0.57
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 +3 -4
- package/lib/browserhealth.js +207 -179
- package/lib/cloudflare.js +117 -65
- package/lib/ignore_similar.js +78 -209
- package/lib/post-processing.js +282 -356
- package/lib/smart-cache.js +347 -267
- package/nwss.js +53 -13
- package/package.json +3 -2
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.
|
|
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:
|
|
32
|
-
PAGE_EVALUATION_SAFE:
|
|
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:
|
|
36
|
-
TURNSTILE_COMPLETION:
|
|
37
|
-
TURNSTILE_COMPLETION_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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
189
|
-
baseDelay:
|
|
190
|
-
maxDelay:
|
|
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
|
|
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,
|
|
445
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
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
|
-
//
|
|
548
|
-
const
|
|
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')
|
|
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')
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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
|
|
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
|
-
//
|
|
1629
|
-
|
|
1630
|
-
|
|
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
|
-
|
|
1667
|
-
|
|
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
|
];
|