Update server.js
Browse files
server.js
CHANGED
|
@@ -102,7 +102,8 @@ class BrowserPool {
|
|
| 102 |
'--disable-blink-features=AutomationControlled',
|
| 103 |
'--disable-background-timer-throttling',
|
| 104 |
'--disable-renderer-backgrounding'
|
| 105 |
-
]
|
|
|
|
| 106 |
});
|
| 107 |
console.log('🚀 Browser launched');
|
| 108 |
this.requestCount = 1;
|
|
@@ -523,7 +524,7 @@ async function performBlacklightScan(url) {
|
|
| 523 |
captureHar: true,
|
| 524 |
saveScreenshots: false,
|
| 525 |
headless: true,
|
| 526 |
-
defaultTimeout:
|
| 527 |
extraChromiumArgs: [
|
| 528 |
'--disable-blink-features=AutomationControlled',
|
| 529 |
'--no-sandbox',
|
|
@@ -531,15 +532,20 @@ async function performBlacklightScan(url) {
|
|
| 531 |
'--disable-dev-shm-usage',
|
| 532 |
'--disable-gpu',
|
| 533 |
'--ignore-certificate-errors'
|
| 534 |
-
]
|
|
|
|
|
|
|
|
|
|
| 535 |
};
|
| 536 |
const result = await collect(url, options);
|
| 537 |
// تنظيف الملفات المؤقتة بعد الفحص
|
| 538 |
-
|
| 539 |
-
|
|
|
|
|
|
|
| 540 |
return result;
|
| 541 |
} catch (e) {
|
| 542 |
-
console.error('Blacklight scan failed:', e.message);
|
| 543 |
return { hosts: {}, cookies: [], error: e.message };
|
| 544 |
}
|
| 545 |
}
|
|
@@ -611,7 +617,7 @@ function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade, securit
|
|
| 611 |
return { score, grade };
|
| 612 |
}
|
| 613 |
|
| 614 |
-
// ==================== نقطة نهاية API ====================
|
| 615 |
app.get('/api/scan', async (req, res) => {
|
| 616 |
const url = normalizeUrl(req.query.url);
|
| 617 |
if (!url) return res.status(400).json({ error: 'Invalid URL' });
|
|
@@ -624,7 +630,6 @@ app.get('/api/scan', async (req, res) => {
|
|
| 624 |
const startTime = Date.now();
|
| 625 |
|
| 626 |
try {
|
| 627 |
-
// تنفيذ جميع الفحوصات بالتوازي (مع حماية كل منها ضد الفشل)
|
| 628 |
const [
|
| 629 |
screenshotResult, hiddenStorageResult, cookieConsentResult,
|
| 630 |
fingerprintResult, redirectChainResult, blacklightResult,
|
|
@@ -775,6 +780,190 @@ app.get('/api/scan', async (req, res) => {
|
|
| 775 |
}
|
| 776 |
});
|
| 777 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 778 |
app.get('/health', (req, res) => res.json({ status: 'ok' }));
|
| 779 |
|
| 780 |
// ==================== بدء التشغيل ====================
|
|
|
|
| 102 |
'--disable-blink-features=AutomationControlled',
|
| 103 |
'--disable-background-timer-throttling',
|
| 104 |
'--disable-renderer-backgrounding'
|
| 105 |
+
],
|
| 106 |
+
protocolTimeout: 240000, // 4 دقائق لتفادي مشاكل الـ timeout
|
| 107 |
});
|
| 108 |
console.log('🚀 Browser launched');
|
| 109 |
this.requestCount = 1;
|
|
|
|
| 524 |
captureHar: true,
|
| 525 |
saveScreenshots: false,
|
| 526 |
headless: true,
|
| 527 |
+
defaultTimeout: 90000, // 90 ثانية
|
| 528 |
extraChromiumArgs: [
|
| 529 |
'--disable-blink-features=AutomationControlled',
|
| 530 |
'--no-sandbox',
|
|
|
|
| 532 |
'--disable-dev-shm-usage',
|
| 533 |
'--disable-gpu',
|
| 534 |
'--ignore-certificate-errors'
|
| 535 |
+
],
|
| 536 |
+
puppeteerOptions: {
|
| 537 |
+
protocolTimeout: 240000, // 4 دقائق
|
| 538 |
+
},
|
| 539 |
};
|
| 540 |
const result = await collect(url, options);
|
| 541 |
// تنظيف الملفات المؤقتة بعد الفحص
|
| 542 |
+
try {
|
| 543 |
+
const files = await readdir(tmpDir);
|
| 544 |
+
await Promise.all(files.map(f => unlink(path.join(tmpDir, f)).catch(() => {})));
|
| 545 |
+
} catch (e) {}
|
| 546 |
return result;
|
| 547 |
} catch (e) {
|
| 548 |
+
console.error('Blacklight scan failed, returning empty result:', e.message);
|
| 549 |
return { hosts: {}, cookies: [], error: e.message };
|
| 550 |
}
|
| 551 |
}
|
|
|
|
| 617 |
return { score, grade };
|
| 618 |
}
|
| 619 |
|
| 620 |
+
// ==================== نقطة نهاية API عادية (JSON) ====================
|
| 621 |
app.get('/api/scan', async (req, res) => {
|
| 622 |
const url = normalizeUrl(req.query.url);
|
| 623 |
if (!url) return res.status(400).json({ error: 'Invalid URL' });
|
|
|
|
| 630 |
const startTime = Date.now();
|
| 631 |
|
| 632 |
try {
|
|
|
|
| 633 |
const [
|
| 634 |
screenshotResult, hiddenStorageResult, cookieConsentResult,
|
| 635 |
fingerprintResult, redirectChainResult, blacklightResult,
|
|
|
|
| 780 |
}
|
| 781 |
});
|
| 782 |
|
| 783 |
+
// ==================== نقطة نهاية SSE (تقدم حي) ====================
|
| 784 |
+
app.get('/api/scan/live', async (req, res) => {
|
| 785 |
+
const url = normalizeUrl(req.query.url);
|
| 786 |
+
if (!url) {
|
| 787 |
+
res.status(400).send('Invalid URL');
|
| 788 |
+
return;
|
| 789 |
+
}
|
| 790 |
+
|
| 791 |
+
// إعداد رؤوس SSE
|
| 792 |
+
res.writeHead(200, {
|
| 793 |
+
'Content-Type': 'text/event-stream',
|
| 794 |
+
'Cache-Control': 'no-cache',
|
| 795 |
+
'Connection': 'keep-alive',
|
| 796 |
+
});
|
| 797 |
+
|
| 798 |
+
const sendEvent = (event, data) => {
|
| 799 |
+
res.write(`event: ${event}\n`);
|
| 800 |
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
| 801 |
+
};
|
| 802 |
+
|
| 803 |
+
sendEvent('start', { url, message: 'Scan started...' });
|
| 804 |
+
|
| 805 |
+
if (scanMutex.isLocked()) {
|
| 806 |
+
sendEvent('error', { error: 'Scanner is busy, please try again in a moment.' });
|
| 807 |
+
res.end();
|
| 808 |
+
return;
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
const release = await scanMutex.acquire();
|
| 812 |
+
const startTime = Date.now();
|
| 813 |
+
|
| 814 |
+
try {
|
| 815 |
+
// تنفيذ الفحوصات مع إرسال التقدم عند اكتمال كل خطوة
|
| 816 |
+
const promises = [
|
| 817 |
+
takeScreenshot(url).then(r => { sendEvent('progress', { step: 'screenshot', status: 'done' }); return r; }).catch(() => null),
|
| 818 |
+
checkHiddenStorage(url).then(r => { sendEvent('progress', { step: 'hidden_storage', status: 'done' }); return r; }).catch(() => null),
|
| 819 |
+
analyzeCookieConsent(url).then(r => { sendEvent('progress', { step: 'cookie_consent', status: 'done' }); return r; }).catch(() => null),
|
| 820 |
+
collectFingerprintRisks(url).then(r => { sendEvent('progress', { step: 'fingerprint_risks', status: 'done' }); return r; }).catch(() => null),
|
| 821 |
+
trackRedirectChain(url).then(r => { sendEvent('progress', { step: 'redirect_chain', status: 'done' }); return r; }).catch(() => null),
|
| 822 |
+
performBlacklightScan(url).then(r => { sendEvent('progress', { step: 'blacklight_scan', status: 'done' }); return r; }).catch(() => null),
|
| 823 |
+
getTosdrGrade(url).then(r => { sendEvent('progress', { step: 'tosdr', status: 'done' }); return r; }).catch(() => null),
|
| 824 |
+
performSecurityCheck(url).then(r => { sendEvent('progress', { step: 'security', status: 'done' }); return r; }).catch(() => null),
|
| 825 |
+
analyzePrivacyPolicy(url).then(r => { sendEvent('progress', { step: 'privacy_policy', status: 'done' }); return r; }).catch(() => null),
|
| 826 |
+
];
|
| 827 |
+
|
| 828 |
+
const results = await Promise.allSettled(promises);
|
| 829 |
+
|
| 830 |
+
const screenshotData = results[0].status === 'fulfilled' ? results[0].value : null;
|
| 831 |
+
const hiddenData = results[1].status === 'fulfilled' ? results[1].value : null;
|
| 832 |
+
const cookieConsentData = results[2].status === 'fulfilled' ? results[2].value : null;
|
| 833 |
+
const fingerprintData = results[3].status === 'fulfilled' ? results[3].value : null;
|
| 834 |
+
const redirectData = results[4].status === 'fulfilled' ? results[4].value : null;
|
| 835 |
+
const blacklightData = results[5].status === 'fulfilled' ? results[5].value : { hosts: {}, cookies: [] };
|
| 836 |
+
const tosdrData = results[6].status === 'fulfilled' ? results[6].value : null;
|
| 837 |
+
const securityData = results[7].status === 'fulfilled' ? results[7].value : { ssl: { valid: false } };
|
| 838 |
+
const privacyPolicyData = results[8].status === 'fulfilled' ? results[8].value : null;
|
| 839 |
+
|
| 840 |
+
// تجميع المتتبعين
|
| 841 |
+
const thirdPartyDomains = [
|
| 842 |
+
...(blacklightData.hosts?.thirdParty || []),
|
| 843 |
+
...(blacklightData.hosts?.requests?.third_party || [])
|
| 844 |
+
];
|
| 845 |
+
const uniqueDomains = [...new Set(thirdPartyDomains)];
|
| 846 |
+
const enrichedTrackers = [];
|
| 847 |
+
for (const domain of uniqueDomains) {
|
| 848 |
+
try {
|
| 849 |
+
const ddgInfo = getDDGInfo(domain);
|
| 850 |
+
const ghosteryInfo = await getGhosteryInfo(domain);
|
| 851 |
+
enrichedTrackers.push({
|
| 852 |
+
domain,
|
| 853 |
+
owner: ghosteryInfo?.organization || ddgInfo?.owner || getBaseDomain(domain),
|
| 854 |
+
category: ghosteryInfo?.category || ddgInfo?.category || 'unknown',
|
| 855 |
+
prevalence: ddgInfo?.prevalence || 0
|
| 856 |
+
});
|
| 857 |
+
} catch (e) {
|
| 858 |
+
enrichedTrackers.push({ domain, owner: getBaseDomain(domain), category: 'unknown', prevalence: 0 });
|
| 859 |
+
}
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
// Geo mapping
|
| 863 |
+
const uniqueIps = [...new Set((blacklightData.hosts?.requests?.third_party || []).map(r => r.ip_addr).filter(Boolean))];
|
| 864 |
+
const geoDestinations = [];
|
| 865 |
+
for (const ip of uniqueIps) {
|
| 866 |
+
try {
|
| 867 |
+
let geoData = null;
|
| 868 |
+
if (useMaxMind && geoReader) {
|
| 869 |
+
const mmGeo = geoReader.get(ip);
|
| 870 |
+
if (mmGeo) {
|
| 871 |
+
geoData = {
|
| 872 |
+
ip,
|
| 873 |
+
country: mmGeo.country?.names?.en || 'Unknown',
|
| 874 |
+
city: mmGeo.city?.names?.en || 'Unknown',
|
| 875 |
+
latitude: mmGeo.location?.latitude,
|
| 876 |
+
longitude: mmGeo.location?.longitude
|
| 877 |
+
};
|
| 878 |
+
}
|
| 879 |
+
}
|
| 880 |
+
if (!geoData) {
|
| 881 |
+
const geoLite = geoip.lookup(ip);
|
| 882 |
+
if (geoLite) {
|
| 883 |
+
geoData = {
|
| 884 |
+
ip,
|
| 885 |
+
country: geoLite.country,
|
| 886 |
+
city: geoLite.city,
|
| 887 |
+
latitude: geoLite.ll?.[0] || null,
|
| 888 |
+
longitude: geoLite.ll?.[1] || null
|
| 889 |
+
};
|
| 890 |
+
}
|
| 891 |
+
}
|
| 892 |
+
if (geoData) geoDestinations.push(geoData);
|
| 893 |
+
} catch (e) {}
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
// PII Leakage
|
| 897 |
+
const leakageAlerts = [];
|
| 898 |
+
for (const r of (blacklightData.hosts?.requests?.third_party || [])) {
|
| 899 |
+
if ((r.method === 'POST' || r.method === 'PUT') && r.body) {
|
| 900 |
+
try {
|
| 901 |
+
const detected = piiFilter.detect(r.body);
|
| 902 |
+
if (detected?.length > 0) {
|
| 903 |
+
leakageAlerts.push({
|
| 904 |
+
severity: 'high',
|
| 905 |
+
destination: r.url,
|
| 906 |
+
method: r.method,
|
| 907 |
+
types: detected.map(p => p.type),
|
| 908 |
+
message: 'Potential PII detected in request to third-party domain.'
|
| 909 |
+
});
|
| 910 |
+
}
|
| 911 |
+
} catch (e) {}
|
| 912 |
+
}
|
| 913 |
+
}
|
| 914 |
+
|
| 915 |
+
const { score, grade } = calculatePrivacyScore(
|
| 916 |
+
blacklightData, enrichedTrackers, tosdrData,
|
| 917 |
+
securityData, cookieConsentData, fingerprintData,
|
| 918 |
+
redirectData, privacyPolicyData
|
| 919 |
+
);
|
| 920 |
+
|
| 921 |
+
const finalResult = {
|
| 922 |
+
success: true,
|
| 923 |
+
url,
|
| 924 |
+
final_url: blacklightData.uri_dest || url,
|
| 925 |
+
scan_time_sec: (Date.now() - startTime) / 1000,
|
| 926 |
+
privacy_score: { score, grade },
|
| 927 |
+
trackers: { count: enrichedTrackers.length, list: enrichedTrackers.slice(0, 20) },
|
| 928 |
+
cookies: {
|
| 929 |
+
total: blacklightData.cookies?.length || 0,
|
| 930 |
+
third_party: blacklightData.cookies?.filter(c => c.thirdParty)?.length || 0
|
| 931 |
+
},
|
| 932 |
+
fingerprinting: {
|
| 933 |
+
canvas: !!(blacklightData.canvasFingerprinters?.length),
|
| 934 |
+
fonts: !!(blacklightData.canvasFontFingerprinters?.length),
|
| 935 |
+
live_risks: fingerprintData
|
| 936 |
+
},
|
| 937 |
+
session_recording: !!(blacklightData.sessionRecorders?.length),
|
| 938 |
+
key_logging: !!(blacklightData.keyLogging?.length),
|
| 939 |
+
hidden_storage: hiddenData ? {
|
| 940 |
+
localStorage: hiddenData.localStorage?.length || 0,
|
| 941 |
+
sessionStorage: hiddenData.sessionStorage?.length || 0,
|
| 942 |
+
indexedDB: hiddenData.indexedDB
|
| 943 |
+
} : null,
|
| 944 |
+
cookie_consent: cookieConsentData,
|
| 945 |
+
redirect_chain: redirectData,
|
| 946 |
+
security: securityData,
|
| 947 |
+
tosdr: tosdrData,
|
| 948 |
+
privacy_policy_analysis: privacyPolicyData,
|
| 949 |
+
geo_mapping: { data_destinations: geoDestinations },
|
| 950 |
+
leakage_detection: { alerts: leakageAlerts },
|
| 951 |
+
screenshot: screenshotData,
|
| 952 |
+
raw: blacklightData
|
| 953 |
+
};
|
| 954 |
+
|
| 955 |
+
sendEvent('result', finalResult);
|
| 956 |
+
sendEvent('end', { message: 'Scan completed.' });
|
| 957 |
+
|
| 958 |
+
} catch (e) {
|
| 959 |
+
console.error('Live scan error:', e);
|
| 960 |
+
sendEvent('error', { error: e.message });
|
| 961 |
+
} finally {
|
| 962 |
+
release();
|
| 963 |
+
res.end();
|
| 964 |
+
}
|
| 965 |
+
});
|
| 966 |
+
|
| 967 |
app.get('/health', (req, res) => res.json({ status: 'ok' }));
|
| 968 |
|
| 969 |
// ==================== بدء التشغيل ====================
|