Update server.js
Browse files
server.js
CHANGED
|
@@ -11,6 +11,7 @@ import { parse } from 'tldts';
|
|
| 11 |
import { chromium } from 'playwright';
|
| 12 |
import axios from 'axios';
|
| 13 |
import cron from 'node-cron';
|
|
|
|
| 14 |
|
| 15 |
const __filename = fileURLToPath(import.meta.url);
|
| 16 |
const __dirname = path.dirname(__filename);
|
|
@@ -23,17 +24,12 @@ app.use(helmet({ contentSecurityPolicy: false, frameguard: false }));
|
|
| 23 |
app.use(cors());
|
| 24 |
app.use(express.json({ limit: '10mb' }));
|
| 25 |
|
| 26 |
-
|
| 27 |
-
windowMs: 15 * 60 * 1000,
|
| 28 |
-
max: 100,
|
| 29 |
-
standardHeaders: true,
|
| 30 |
-
legacyHeaders: false,
|
| 31 |
-
}));
|
| 32 |
-
|
| 33 |
let ghosteryDB = null;
|
| 34 |
let ddgTrackerRadar = { domains: {} };
|
| 35 |
let tosdrCache = new Map();
|
| 36 |
|
|
|
|
| 37 |
async function initGhosteryDB() {
|
| 38 |
try {
|
| 39 |
const enginePath = path.join(__dirname, 'node_modules', '@ghostery', 'trackerdb', 'dist', 'trackerdb.engine');
|
|
@@ -42,7 +38,9 @@ async function initGhosteryDB() {
|
|
| 42 |
ghosteryDB = await loadTrackerDB(engine);
|
| 43 |
console.log('✅ Ghostery TrackerDB initialized');
|
| 44 |
}
|
| 45 |
-
} catch (e) {
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
|
| 48 |
function loadDDGTrackerRadar() {
|
|
@@ -52,13 +50,11 @@ function loadDDGTrackerRadar() {
|
|
| 52 |
ddgTrackerRadar = JSON.parse(readFileSync(ddgPath, 'utf-8'));
|
| 53 |
console.log(`✅ DDG Tracker Radar: ${Object.keys(ddgTrackerRadar.domains).length} domains`);
|
| 54 |
}
|
| 55 |
-
} catch (e) {
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
|
| 58 |
-
cron.schedule('0 0 * * *', async () => {
|
| 59 |
-
await downloadDDGTrackerRadar();
|
| 60 |
-
});
|
| 61 |
-
|
| 62 |
async function downloadDDGTrackerRadar() {
|
| 63 |
try {
|
| 64 |
const response = await axios.get('https://downloads.vivaldi.com/ddg/tds-v2-current.json', { timeout: 30000 });
|
|
@@ -82,7 +78,11 @@ async function downloadDDGTrackerRadar() {
|
|
| 82 |
}
|
| 83 |
}
|
| 84 |
|
|
|
|
|
|
|
|
|
|
| 85 |
function normalizeUrl(inputUrl) {
|
|
|
|
| 86 |
let url = inputUrl.trim();
|
| 87 |
if (!url.startsWith('http://') && !url.startsWith('https://')) url = 'https://' + url;
|
| 88 |
try { return new URL(url).toString(); } catch { return null; }
|
|
@@ -117,6 +117,48 @@ function getDDGInfo(domain) {
|
|
| 117 |
return ddgTrackerRadar.domains[domain] || ddgTrackerRadar.domains[baseDomain] || null;
|
| 118 |
}
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
async function getTosdrGrade(url) {
|
| 121 |
try {
|
| 122 |
const hostname = new URL(url).hostname;
|
|
@@ -287,14 +329,14 @@ function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade, securit
|
|
| 287 |
return { score, grade };
|
| 288 |
}
|
| 289 |
|
|
|
|
| 290 |
app.get('/api/scan', async (req, res) => {
|
| 291 |
const url = normalizeUrl(req.query.url);
|
| 292 |
if (!url) return res.status(400).json({ error: 'Invalid URL' });
|
| 293 |
|
| 294 |
-
const format = req.query.format || 'json';
|
| 295 |
const mode = req.query.mode || 'deep';
|
| 296 |
|
| 297 |
-
// إذا كان الوضع سريع، نعود ببيانات محدودة
|
| 298 |
if (mode === 'fast') {
|
| 299 |
try {
|
| 300 |
const startTime = Date.now();
|
|
@@ -338,13 +380,7 @@ app.get('/api/scan', async (req, res) => {
|
|
| 338 |
}
|
| 339 |
}
|
| 340 |
|
| 341 |
-
// الوضع العميق (
|
| 342 |
-
if (format === 'sse') {
|
| 343 |
-
// ... (نفس كود SSE السابق)
|
| 344 |
-
return res.status(501).json({ error: 'SSE not implemented in this version' });
|
| 345 |
-
}
|
| 346 |
-
|
| 347 |
-
// تنسيق JSON (الافتراضي)
|
| 348 |
try {
|
| 349 |
const startTime = Date.now();
|
| 350 |
|
|
@@ -370,19 +406,41 @@ app.get('/api/scan', async (req, res) => {
|
|
| 370 |
];
|
| 371 |
const uniqueDomains = [...new Set(thirdPartyDomains)];
|
| 372 |
|
| 373 |
-
// إثراء بيانات المتتبعين
|
| 374 |
const enrichedTrackers = [];
|
| 375 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
const ddgInfo = getDDGInfo(domain);
|
| 377 |
const ghosteryInfo = await getGhosteryInfo(domain);
|
|
|
|
|
|
|
| 378 |
enrichedTrackers.push({
|
| 379 |
domain,
|
| 380 |
owner: ghosteryInfo?.organization || ddgInfo?.owner || getBaseDomain(domain),
|
| 381 |
category: ghosteryInfo?.category || ddgInfo?.category || 'unknown',
|
| 382 |
-
prevalence: ddgInfo?.prevalence || 0
|
|
|
|
| 383 |
});
|
| 384 |
}
|
| 385 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
const { score, grade } = calculatePrivacyScore(blacklightData, enrichedTrackers, tosdrData, securityData);
|
| 387 |
|
| 388 |
// بناء الملخص
|
|
@@ -409,10 +467,14 @@ app.get('/api/scan', async (req, res) => {
|
|
| 409 |
sessionStorage: hiddenData.sessionStorage?.length || 0,
|
| 410 |
indexedDB: hiddenData.indexedDB
|
| 411 |
} : null,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
security: securityData,
|
| 413 |
tosdr: tosdrData,
|
| 414 |
screenshot: screenshotData,
|
| 415 |
-
// تضمين البيانات الخام للمطورين
|
| 416 |
raw: blacklightData
|
| 417 |
};
|
| 418 |
|
|
@@ -427,11 +489,12 @@ app.get('/api/scan', async (req, res) => {
|
|
| 427 |
app.get('/health', (req, res) => res.json({ status: 'ok' }));
|
| 428 |
app.use(express.static('public'));
|
| 429 |
|
|
|
|
| 430 |
(async () => {
|
| 431 |
await initGhosteryDB();
|
| 432 |
loadDDGTrackerRadar();
|
| 433 |
if (Object.keys(ddgTrackerRadar.domains).length === 0) {
|
| 434 |
await downloadDDGTrackerRadar();
|
| 435 |
}
|
| 436 |
-
app.listen(PORT, '0.0.0.0', () => console.log(`🚀 Private Eye
|
| 437 |
})();
|
|
|
|
| 11 |
import { chromium } from 'playwright';
|
| 12 |
import axios from 'axios';
|
| 13 |
import cron from 'node-cron';
|
| 14 |
+
import dns from 'node:dns/promises';
|
| 15 |
|
| 16 |
const __filename = fileURLToPath(import.meta.url);
|
| 17 |
const __dirname = path.dirname(__filename);
|
|
|
|
| 24 |
app.use(cors());
|
| 25 |
app.use(express.json({ limit: '10mb' }));
|
| 26 |
|
| 27 |
+
// --- قواعد البيانات وذاكرة التخزين المؤقت ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
let ghosteryDB = null;
|
| 29 |
let ddgTrackerRadar = { domains: {} };
|
| 30 |
let tosdrCache = new Map();
|
| 31 |
|
| 32 |
+
// --- وظائف التهيئة ---
|
| 33 |
async function initGhosteryDB() {
|
| 34 |
try {
|
| 35 |
const enginePath = path.join(__dirname, 'node_modules', '@ghostery', 'trackerdb', 'dist', 'trackerdb.engine');
|
|
|
|
| 38 |
ghosteryDB = await loadTrackerDB(engine);
|
| 39 |
console.log('✅ Ghostery TrackerDB initialized');
|
| 40 |
}
|
| 41 |
+
} catch (e) {
|
| 42 |
+
console.error('❌ Ghostery init failed:', e.message);
|
| 43 |
+
}
|
| 44 |
}
|
| 45 |
|
| 46 |
function loadDDGTrackerRadar() {
|
|
|
|
| 50 |
ddgTrackerRadar = JSON.parse(readFileSync(ddgPath, 'utf-8'));
|
| 51 |
console.log(`✅ DDG Tracker Radar: ${Object.keys(ddgTrackerRadar.domains).length} domains`);
|
| 52 |
}
|
| 53 |
+
} catch (e) {
|
| 54 |
+
console.error('❌ DDG load failed:', e.message);
|
| 55 |
+
}
|
| 56 |
}
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
async function downloadDDGTrackerRadar() {
|
| 59 |
try {
|
| 60 |
const response = await axios.get('https://downloads.vivaldi.com/ddg/tds-v2-current.json', { timeout: 30000 });
|
|
|
|
| 78 |
}
|
| 79 |
}
|
| 80 |
|
| 81 |
+
cron.schedule('0 0 * * *', downloadDDGTrackerRadar);
|
| 82 |
+
|
| 83 |
+
// --- وظائف المساعدة ---
|
| 84 |
function normalizeUrl(inputUrl) {
|
| 85 |
+
if (!inputUrl) return null;
|
| 86 |
let url = inputUrl.trim();
|
| 87 |
if (!url.startsWith('http://') && !url.startsWith('https://')) url = 'https://' + url;
|
| 88 |
try { return new URL(url).toString(); } catch { return null; }
|
|
|
|
| 117 |
return ddgTrackerRadar.domains[domain] || ddgTrackerRadar.domains[baseDomain] || null;
|
| 118 |
}
|
| 119 |
|
| 120 |
+
// --- تحسين Geo Mapping (أداء محسّن) ---
|
| 121 |
+
async function getGeoInfo(domain) {
|
| 122 |
+
try {
|
| 123 |
+
const lookup = await dns.resolve4(domain, { timeout: 1000 }).catch(() => []);
|
| 124 |
+
if (!lookup || lookup.length === 0) return null;
|
| 125 |
+
const ip = lookup[0];
|
| 126 |
+
|
| 127 |
+
const geoRequest = axios.get(`http://ip-api.com/json/${ip}?fields=status,country,city,isp`, { timeout: 1500 });
|
| 128 |
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 1500));
|
| 129 |
+
const response = await Promise.race([geoRequest, timeoutPromise]);
|
| 130 |
+
|
| 131 |
+
if (response.data?.status === 'success') {
|
| 132 |
+
return {
|
| 133 |
+
ip: ip,
|
| 134 |
+
country: response.data.country,
|
| 135 |
+
city: response.data.city,
|
| 136 |
+
isp: response.data.isp
|
| 137 |
+
};
|
| 138 |
+
}
|
| 139 |
+
} catch (e) {
|
| 140 |
+
// فشل صامت، لا نريد إيقاف الفحص كله بسبب فشل الـ Geo
|
| 141 |
+
}
|
| 142 |
+
return { country: 'Unknown', city: 'Unknown', isp: 'Unknown' };
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
// --- تحسين اكتشاف تسريب البيانات (PII Leak Detection) ---
|
| 146 |
+
function detectPIILeak(dataString) {
|
| 147 |
+
if (!dataString) return [];
|
| 148 |
+
const leaks = [];
|
| 149 |
+
const patterns = {
|
| 150 |
+
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
| 151 |
+
phone: /(?:\+|00)\d{1,3}[\s.-]?\(?\d{1,4}\)?[\s.-]?\d{1,4}[\s.-]?\d{1,9}/g // نمط دولي أكثر دقة
|
| 152 |
+
};
|
| 153 |
+
for (const [type, regex] of Object.entries(patterns)) {
|
| 154 |
+
const matches = dataString.match(regex);
|
| 155 |
+
if (matches) {
|
| 156 |
+
leaks.push({ type, count: matches.length });
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
return leaks;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
async function getTosdrGrade(url) {
|
| 163 |
try {
|
| 164 |
const hostname = new URL(url).hostname;
|
|
|
|
| 329 |
return { score, grade };
|
| 330 |
}
|
| 331 |
|
| 332 |
+
// --- API Endpoint الرئيسي ---
|
| 333 |
app.get('/api/scan', async (req, res) => {
|
| 334 |
const url = normalizeUrl(req.query.url);
|
| 335 |
if (!url) return res.status(400).json({ error: 'Invalid URL' });
|
| 336 |
|
| 337 |
+
const format = req.query.format || 'json';
|
| 338 |
const mode = req.query.mode || 'deep';
|
| 339 |
|
|
|
|
| 340 |
if (mode === 'fast') {
|
| 341 |
try {
|
| 342 |
const startTime = Date.now();
|
|
|
|
| 380 |
}
|
| 381 |
}
|
| 382 |
|
| 383 |
+
// --- الوضع العميق (Deep Mode) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
try {
|
| 385 |
const startTime = Date.now();
|
| 386 |
|
|
|
|
| 406 |
];
|
| 407 |
const uniqueDomains = [...new Set(thirdPartyDomains)];
|
| 408 |
|
| 409 |
+
// إثراء بيانات المتتبعين مع Geo (معالجة متوازية)
|
| 410 |
const enrichedTrackers = [];
|
| 411 |
+
const geoPromises = uniqueDomains.map(domain => getGeoInfo(domain));
|
| 412 |
+
const geoResults = await Promise.allSettled(geoPromises);
|
| 413 |
+
|
| 414 |
+
for (let i = 0; i < uniqueDomains.length; i++) {
|
| 415 |
+
const domain = uniqueDomains[i];
|
| 416 |
const ddgInfo = getDDGInfo(domain);
|
| 417 |
const ghosteryInfo = await getGhosteryInfo(domain);
|
| 418 |
+
const geoData = geoResults[i]?.status === 'fulfilled' ? geoResults[i].value : { country: 'Unknown', city: 'Unknown', isp: 'Unknown' };
|
| 419 |
+
|
| 420 |
enrichedTrackers.push({
|
| 421 |
domain,
|
| 422 |
owner: ghosteryInfo?.organization || ddgInfo?.owner || getBaseDomain(domain),
|
| 423 |
category: ghosteryInfo?.category || ddgInfo?.category || 'unknown',
|
| 424 |
+
prevalence: ddgInfo?.prevalence || 0,
|
| 425 |
+
location: geoData
|
| 426 |
});
|
| 427 |
}
|
| 428 |
|
| 429 |
+
// اكتشاف تسريب البيانات (PII Leakage)
|
| 430 |
+
const leaks = [];
|
| 431 |
+
const rawTrackers = blacklightData.third_party_trackers || [];
|
| 432 |
+
rawTrackers.forEach(tracker => {
|
| 433 |
+
const foundInUrl = detectPIILeak(tracker.url);
|
| 434 |
+
const foundInPostData = detectPIILeak(JSON.stringify(tracker.data || {}));
|
| 435 |
+
|
| 436 |
+
if (foundInUrl.length > 0 || foundInPostData.length > 0) {
|
| 437 |
+
leaks.push({
|
| 438 |
+
target_domain: new URL(tracker.url).hostname,
|
| 439 |
+
leaks: [...foundInUrl, ...foundInPostData]
|
| 440 |
+
});
|
| 441 |
+
}
|
| 442 |
+
});
|
| 443 |
+
|
| 444 |
const { score, grade } = calculatePrivacyScore(blacklightData, enrichedTrackers, tosdrData, securityData);
|
| 445 |
|
| 446 |
// بناء الملخص
|
|
|
|
| 467 |
sessionStorage: hiddenData.sessionStorage?.length || 0,
|
| 468 |
indexedDB: hiddenData.indexedDB
|
| 469 |
} : null,
|
| 470 |
+
data_leaks: {
|
| 471 |
+
detected: leaks.length > 0,
|
| 472 |
+
count: leaks.length,
|
| 473 |
+
details: leaks
|
| 474 |
+
},
|
| 475 |
security: securityData,
|
| 476 |
tosdr: tosdrData,
|
| 477 |
screenshot: screenshotData,
|
|
|
|
| 478 |
raw: blacklightData
|
| 479 |
};
|
| 480 |
|
|
|
|
| 489 |
app.get('/health', (req, res) => res.json({ status: 'ok' }));
|
| 490 |
app.use(express.static('public'));
|
| 491 |
|
| 492 |
+
// --- بدء تشغيل الخادم ---
|
| 493 |
(async () => {
|
| 494 |
await initGhosteryDB();
|
| 495 |
loadDDGTrackerRadar();
|
| 496 |
if (Object.keys(ddgTrackerRadar.domains).length === 0) {
|
| 497 |
await downloadDDGTrackerRadar();
|
| 498 |
}
|
| 499 |
+
app.listen(PORT, '0.0.0.0', () => console.log(`🚀 Private Eye V9.0 running on ${PORT}`));
|
| 500 |
})();
|