"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.XdrScanner = void 0;
const logger_1 = require("../logger");
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
// Known suspicious process names / patterns
const SUSPICIOUS_PROCESS_PATTERNS = [
    { pattern: /\bminerd?\b|\bcryptominer\b|\bxmrig\b|\bmonero\b|\bnheqminer\b/i, reason: 'Cryptocurrency miner', severity: 'critical', score: 95 },
    { pattern: /\bnetcat\b|\bnc\.exe\b|\bnc\b/i, reason: 'Netcat - common in reverse shells', severity: 'high', score: 75 },
    { pattern: /\bmimikatz\b|\blsadump\b|\bpwdump\b|\bfgdump\b/i, reason: 'Credential dumping tool', severity: 'critical', score: 98 },
    { pattern: /\bcobaltstrike\b|\bbeacon\b/i, reason: 'C2 framework indicator', severity: 'critical', score: 97 },
    { pattern: /\bmetasploit\b|\bmsfconsole\b|\bmsfvenom\b/i, reason: 'Metasploit framework', severity: 'critical', score: 97 },
    { pattern: /\bnmap\b/i, reason: 'Network scanner (unexpected on endpoint)', severity: 'medium', score: 45 },
    { pattern: /\bhacktools?\b|\brat\b/i, reason: 'Known hacking tool name', severity: 'high', score: 80 },
    { pattern: /\bkeylogger\b|\bkeylog\b/i, reason: 'Potential keylogger', severity: 'critical', score: 96 },
    { pattern: /\btorsocks\b|\btorify\b/i, reason: 'Tor anonymization (data exfil indicator)', severity: 'medium', score: 60 },
    { pattern: /\bpython[23]?\s+-c\b|\bperl\s+-e\b|\bphp\s+-r\b/i, reason: 'Inline interpreter execution', severity: 'medium', score: 55 },
    { pattern: /\bwget\s+.*http|\bcurl\s+.*http/i, reason: 'Remote download invocation', severity: 'low', score: 35 },
    { pattern: /\bsudo\s+su\b|\bsudo\s+-i\b|\bsudo\s+bash\b/i, reason: 'Privilege escalation attempt', severity: 'high', score: 70 },
];
// Known suspicious outbound ports
const SUSPICIOUS_PORTS = {
    4444: { reason: 'Default Metasploit listener port', severity: 'critical', score: 90 },
    1337: { reason: 'Common hacker/C2 port', severity: 'high', score: 80 },
    31337: { reason: 'Classic backdoor port (elite)', severity: 'critical', score: 92 },
    6666: { reason: 'Common IRC/C2 port', severity: 'high', score: 78 },
    6667: { reason: 'IRC - potential C2 channel', severity: 'medium', score: 55 },
    9001: { reason: 'Tor default ORPort', severity: 'medium', score: 50 },
    9050: { reason: 'Tor SOCKS proxy', severity: 'medium', score: 55 },
    4443: { reason: 'Alt HTTPS (C2 evasion)', severity: 'low', score: 30 },
    8443: { reason: 'Alt HTTPS (C2 evasion)', severity: 'low', score: 25 },
    2222: { reason: 'Alt SSH (lateral movement)', severity: 'medium', score: 50 },
};
// Known AV/EDR process names
const KNOWN_AV_PROCESSES = [
    { pattern: /crowdstrike|csagent|csfalcon/i, name: 'CrowdStrike Falcon' },
    { pattern: /sentinelone|sentinel_one/i, name: 'SentinelOne' },
    { pattern: /cortex.*xdr|traps/i, name: 'Palo Alto Cortex XDR' },
    { pattern: /carbonblack|cb\.exe|cbdefense/i, name: 'Carbon Black' },
    { pattern: /cybereason/i, name: 'Cybereason' },
    { pattern: /defender|msmpeng|msseces|securityhealthservice/i, name: 'Windows Defender' },
    { pattern: /clamd|clamav/i, name: 'ClamAV' },
    { pattern: /avg.*\b|avgtoolbar/i, name: 'AVG' },
    { pattern: /kaspersky|kav|klnagent/i, name: 'Kaspersky' },
    { pattern: /sophos/i, name: 'Sophos' },
    { pattern: /malwarebytes/i, name: 'Malwarebytes' },
    { pattern: /eset|nod32/i, name: 'ESET NOD32' },
    { pattern: /symantec|sep\.exe|navapsvc/i, name: 'Symantec' },
    { pattern: /bitdefender|bdagent/i, name: 'Bitdefender' },
];
function generateId() {
    return `xdr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
function runCommand(cmd, args, timeout = 10000) {
    return new Promise((resolve) => {
        (0, child_process_1.execFile)(cmd, args, { timeout, maxBuffer: 4 * 1024 * 1024 }, (err, stdout) => {
            resolve(err ? '' : stdout);
        });
    });
}
function runShell(cmd, timeout = 10000) {
    return new Promise((resolve) => {
        (0, child_process_1.exec)(cmd, { timeout, maxBuffer: 4 * 1024 * 1024 }, (err, stdout) => {
            resolve(err ? '' : (stdout || ''));
        });
    });
}
class XdrScanner {
    constructor() {
        this.scanning = false;
        this.isWindows = process.platform === 'win32';
        this.isLinux = process.platform === 'linux';
        this.isMac = process.platform === 'darwin';
    }
    async scan(scanId, onProgress) {
        if (this.scanning) {
            return this.emptyResult(scanId, 'XDR scan already in progress');
        }
        this.scanning = true;
        const startTime = Date.now();
        try {
            const threats = [];
            const existingAv = [];
            onProgress?.(5);
            const [processes, connections, services, scripts, avProcs] = await Promise.all([
                this.collectProcesses(),
                this.collectConnections(),
                this.collectServices(),
                this.collectPersistenceScripts(),
                this.detectExistingAv(),
            ]);
            onProgress?.(40);
            existingAv.push(...avProcs);
            // Analyse processes
            const processThreats = this.analyseProcesses(processes);
            threats.push(...processThreats);
            onProgress?.(55);
            // Analyse connections
            const connThreats = this.analyseConnections(connections, processes);
            threats.push(...connThreats);
            onProgress?.(70);
            // Analyse services
            const serviceThreats = this.analyseServices(services);
            threats.push(...serviceThreats);
            onProgress?.(80);
            // Analyse persistence scripts / startup entries
            const scriptThreats = this.analyseScripts(scripts);
            threats.push(...scriptThreats);
            onProgress?.(95);
            const duration = Date.now() - startTime;
            onProgress?.(100);
            return {
                scanId,
                threats,
                summary: {
                    processesScanned: processes.length,
                    connectionsScanned: connections.length,
                    servicesScanned: services.length,
                    scriptsScanned: scripts.length,
                    criticalThreats: threats.filter(t => t.severity === 'critical').length,
                    highThreats: threats.filter(t => t.severity === 'high').length,
                    mediumThreats: threats.filter(t => t.severity === 'medium').length,
                    lowThreats: threats.filter(t => t.severity === 'low').length,
                    scanDuration: duration,
                },
                existingAvDetected: existingAv,
            };
        }
        catch (e) {
            logger_1.logger.error(`XDR scan error: ${e.message}`);
            return this.emptyResult(scanId, e.message);
        }
        finally {
            this.scanning = false;
        }
    }
    emptyResult(scanId, error) {
        return {
            scanId, threats: [], existingAvDetected: [],
            summary: { processesScanned: 0, connectionsScanned: 0, servicesScanned: 0, scriptsScanned: 0, criticalThreats: 0, highThreats: 0, mediumThreats: 0, lowThreats: 0, scanDuration: 0 },
            error,
        };
    }
    // ---- Data collectors ----
    async collectProcesses() {
        try {
            if (this.isWindows) {
                const out = await runShell('wmic process get ProcessId,Name,ExecutablePath,CommandLine /FORMAT:csv 2>nul');
                return this.parseWmicProcesses(out);
            }
            else {
                const out = await runShell('ps aux --no-headers 2>/dev/null || ps aux 2>/dev/null');
                return this.parsePsProcesses(out);
            }
        }
        catch {
            return [];
        }
    }
    parseWmicProcesses(raw) {
        const lines = raw.split('\n').filter(l => l.trim() && !l.startsWith('Node'));
        return lines.slice(1).map(line => {
            const parts = line.split(',');
            return {
                pid: parseInt(parts[2] || '0', 10),
                name: (parts[3] || '').trim(),
                path: (parts[1] || '').trim(),
                cmdline: (parts[0] || '').trim(),
                parent: '',
                user: '',
            };
        }).filter(p => p.name);
    }
    parsePsProcesses(raw) {
        return raw.split('\n').filter(l => l.trim()).map(line => {
            const parts = line.trim().split(/\s+/);
            const pid = parseInt(parts[1] || '0', 10);
            const user = parts[0] || '';
            const cmdParts = parts.slice(10);
            const cmd = cmdParts.join(' ');
            const name = path.basename(cmdParts[0] || '').replace(/^-/, '');
            return { pid, name, path: cmdParts[0] || '', cmdline: cmd, parent: '', user };
        }).filter(p => p.pid > 0 && p.name);
    }
    async collectConnections() {
        try {
            if (this.isWindows) {
                const out = await runShell('netstat -nao 2>nul');
                return this.parseNetstatWindows(out);
            }
            else {
                const out = await runShell('ss -tnup 2>/dev/null || netstat -tnup 2>/dev/null');
                return this.parseNetstatLinux(out);
            }
        }
        catch {
            return [];
        }
    }
    parseNetstatWindows(raw) {
        const conns = [];
        for (const line of raw.split('\n')) {
            const m = line.trim().match(/^(TCP|UDP)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\d+)/);
            if (!m)
                continue;
            const remParts = m[3].split(':');
            const remPort = parseInt(remParts[remParts.length - 1] || '0', 10);
            const remIp = remParts.slice(0, -1).join(':');
            if (remIp === '*' || remIp === '0.0.0.0' || remIp === '::')
                continue;
            conns.push({
                proto: m[1].toLowerCase(),
                localAddr: m[2],
                remoteAddr: m[3],
                remoteIp: remIp,
                remotePort: remPort,
                state: m[4],
                pid: parseInt(m[5], 10),
            });
        }
        return conns;
    }
    parseNetstatLinux(raw) {
        const conns = [];
        for (const line of raw.split('\n')) {
            if (!line.trim() || line.startsWith('Netid') || line.startsWith('State') || line.startsWith('Proto'))
                continue;
            const parts = line.trim().split(/\s+/);
            if (parts.length < 5)
                continue;
            // ss output: State Recv-Q Send-Q Local:Port Peer:Port Process
            const isSs = /^(ESTAB|LISTEN|TIME-WAIT|CLOSE-WAIT|SYN-SENT|SYN-RECV|UNCONN)/.test(parts[0]);
            if (isSs) {
                const state = parts[0];
                const peerAddr = parts[4] || '';
                if (peerAddr === '*' || peerAddr === '0.0.0.0:*')
                    continue;
                const lastColon = peerAddr.lastIndexOf(':');
                const remoteIp = peerAddr.substring(0, lastColon);
                const remotePort = parseInt(peerAddr.substring(lastColon + 1), 10);
                const pidMatch = line.match(/pid=(\d+)/);
                conns.push({ proto: 'tcp', localAddr: parts[3], remoteAddr: peerAddr, remoteIp, remotePort, state, pid: pidMatch ? parseInt(pidMatch[1]) : 0 });
            }
            else {
                // netstat format
                const proto = parts[0];
                const foreign = parts[4] || '';
                if (foreign === '0.0.0.0:*' || foreign === ':::*' || foreign === '*:*')
                    continue;
                const lastColon = foreign.lastIndexOf(':');
                const remoteIp = foreign.substring(0, lastColon);
                const remotePort = parseInt(foreign.substring(lastColon + 1), 10);
                const pidPart = parts[6] || parts[5] || '';
                const pid = parseInt(pidPart.split('/')[0]) || 0;
                conns.push({ proto: proto.toLowerCase().replace(/[46]/, ''), localAddr: parts[3], remoteAddr: foreign, remoteIp, remotePort, state: parts[5] || '', pid });
            }
        }
        return conns.filter(c => c.remotePort > 0 && c.remoteIp && c.remoteIp !== '127.0.0.1' && c.remoteIp !== '::1');
    }
    async collectServices() {
        try {
            if (this.isWindows) {
                const out = await runShell('sc query type= all state= all 2>nul');
                return this.parseScServices(out);
            }
            else if (this.isLinux) {
                const out = await runShell('systemctl list-units --type=service --all --no-pager --no-legend 2>/dev/null');
                return this.parseSystemdServices(out);
            }
            else {
                const out = await runShell('launchctl list 2>/dev/null');
                return this.parseLaunchctlServices(out);
            }
        }
        catch {
            return [];
        }
    }
    parseScServices(raw) {
        const services = [];
        const blocks = raw.split(/SERVICE_NAME:/);
        for (const block of blocks.slice(1)) {
            const nameLine = block.split('\n')[0].trim();
            const stateMatch = block.match(/STATE\s*:\s*\d+\s+(\S+)/);
            const pathMatch = block.match(/BINARY_PATH_NAME\s*:\s*(.+)/);
            services.push({
                name: nameLine,
                status: stateMatch ? stateMatch[1] : 'UNKNOWN',
                path: pathMatch ? pathMatch[1].trim() : '',
                enabled: true,
            });
        }
        return services;
    }
    parseSystemdServices(raw) {
        return raw.split('\n').filter(l => l.trim()).map(line => {
            const parts = line.trim().split(/\s+/);
            return {
                name: (parts[0] || '').replace('.service', ''),
                status: parts[2] || '',
                path: '',
                enabled: parts[1] === 'enabled' || parts[1] === 'static',
            };
        }).filter(s => s.name);
    }
    parseLaunchctlServices(raw) {
        return raw.split('\n').filter(l => l.trim()).map(line => {
            const parts = line.split(/\t/);
            return { name: (parts[2] || '').trim(), status: parts[1] || '', path: '', enabled: true };
        }).filter(s => s.name);
    }
    async collectPersistenceScripts() {
        const scripts = [];
        try {
            if (this.isLinux || this.isMac) {
                // Cron jobs
                const userCron = await runShell('crontab -l 2>/dev/null');
                if (userCron.trim())
                    scripts.push({ type: 'cron', path: 'user-crontab', content: userCron.trim().substring(0, 500) });
                const sysCron = await runShell('cat /etc/cron.d/* /etc/cron.daily/* /etc/cron.hourly/* 2>/dev/null | head -200');
                if (sysCron.trim())
                    scripts.push({ type: 'cron', path: '/etc/cron.*', content: sysCron.trim().substring(0, 500) });
                // Systemd timers
                const timers = await runShell('systemctl list-timers --all --no-pager --no-legend 2>/dev/null');
                if (timers.trim())
                    scripts.push({ type: 'systemd_timer', path: 'systemd-timers', content: timers.trim().substring(0, 500) });
                // rc.local and init scripts
                const rcLocal = await runShell('cat /etc/rc.local 2>/dev/null');
                if (rcLocal.trim() && rcLocal.length > 10)
                    scripts.push({ type: 'init_script', path: '/etc/rc.local', content: rcLocal.substring(0, 500) });
                // Autostart user files
                const autostartDir = `${os.homedir()}/.config/autostart`;
                if (fs.existsSync(autostartDir)) {
                    const files = fs.readdirSync(autostartDir).filter(f => f.endsWith('.desktop')).slice(0, 20);
                    for (const f of files) {
                        try {
                            const content = fs.readFileSync(path.join(autostartDir, f), 'utf8').substring(0, 300);
                            scripts.push({ type: 'autostart', path: path.join(autostartDir, f), content });
                        }
                        catch { /* ignore */ }
                    }
                }
                // Check for unusual files in /tmp being executed
                const tmpExecutables = await runShell('find /tmp /var/tmp -type f -executable -newer /tmp 2>/dev/null | head -20');
                if (tmpExecutables.trim()) {
                    for (const fp of tmpExecutables.trim().split('\n').filter(Boolean)) {
                        scripts.push({ type: 'tmp_executable', path: fp, content: '' });
                    }
                }
            }
            else if (this.isWindows) {
                // Run/RunOnce registry keys
                const runKeys = await runShell('reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" 2>nul');
                if (runKeys.trim())
                    scripts.push({ type: 'registry_run', path: 'HKCU Run', content: runKeys.substring(0, 500) });
                const runKeysMachine = await runShell('reg query "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" 2>nul');
                if (runKeysMachine.trim())
                    scripts.push({ type: 'registry_run', path: 'HKLM Run', content: runKeysMachine.substring(0, 500) });
                // Scheduled tasks
                const tasks = await runShell('schtasks /query /fo LIST /v 2>nul | findstr /i "task name status run as program"');
                if (tasks.trim())
                    scripts.push({ type: 'scheduled_task', path: 'schtasks', content: tasks.substring(0, 1000) });
                // Startup folder
                const startupFolder = await runShell('dir "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup" /b 2>nul');
                if (startupFolder.trim())
                    scripts.push({ type: 'startup_folder', path: 'Startup folder', content: startupFolder.substring(0, 500) });
            }
        }
        catch (e) {
            logger_1.logger.warn(`Script collection error: ${e.message}`);
        }
        return scripts;
    }
    async detectExistingAv() {
        const detected = [];
        try {
            let procList = '';
            if (this.isWindows) {
                procList = await runShell('tasklist /FO CSV /NH 2>nul');
            }
            else {
                procList = await runShell('ps aux 2>/dev/null');
            }
            for (const av of KNOWN_AV_PROCESSES) {
                if (av.pattern.test(procList)) {
                    if (!detected.includes(av.name))
                        detected.push(av.name);
                }
            }
            // Also check installed software on Linux
            if (this.isLinux) {
                const dpkg = await runShell('dpkg -l 2>/dev/null | grep -iE "clamav|sophos|symantec|kaspersky|bitdefender|eset|avg|avast" | head -5');
                if (dpkg.trim())
                    detected.push('Linux AV (dpkg)');
                const rpm = await runShell('rpm -qa 2>/dev/null | grep -iE "clamav|sophos|symantec|kaspersky" | head -5');
                if (rpm.trim())
                    detected.push('Linux AV (rpm)');
            }
        }
        catch { /* non-critical */ }
        return [...new Set(detected)];
    }
    // ---- Analysis functions ----
    analyseProcesses(processes) {
        const threats = [];
        const seenPatterns = new Set();
        for (const proc of processes) {
            const combined = `${proc.name} ${proc.cmdline}`.toLowerCase();
            for (const sp of SUSPICIOUS_PROCESS_PATTERNS) {
                if (sp.pattern.test(combined)) {
                    const key = `${sp.pattern.source}:${proc.name}`;
                    if (seenPatterns.has(key))
                        continue;
                    seenPatterns.add(key);
                    const mlFeatures = this.extractProcessMlFeatures(proc);
                    threats.push({
                        id: generateId(),
                        threatType: 'suspicious_process',
                        category: 'malware',
                        severity: sp.severity,
                        threatScore: sp.score,
                        description: `${sp.reason}: ${proc.name}`,
                        indicators: [sp.reason, `Process: ${proc.name}`, proc.cmdline ? `CMD: ${proc.cmdline.substring(0, 100)}` : ''].filter(Boolean),
                        processName: proc.name,
                        processPid: proc.pid,
                        processPath: proc.path,
                        processCmdline: proc.cmdline,
                        processParent: proc.parent,
                        mlFeatures,
                        rawData: { user: proc.user },
                    });
                }
            }
            // Detect processes running from /tmp, /var/tmp, /dev/shm (Linux) or %TEMP% (Windows)
            if (proc.path) {
                const runFromTemp = /^\/(tmp|var\/tmp|dev\/shm)\//.test(proc.path) ||
                    /^[A-Z]:\\(Users\\[^\\]+\\AppData\\Local\\Temp|Windows\\Temp)\\/i.test(proc.path);
                if (runFromTemp) {
                    threats.push({
                        id: generateId(),
                        threatType: 'suspicious_process',
                        category: 'malware',
                        severity: 'high',
                        threatScore: 80,
                        description: `Process executing from temporary directory: ${proc.name}`,
                        indicators: ['Running from temp directory', `Path: ${proc.path}`],
                        processName: proc.name,
                        processPid: proc.pid,
                        processPath: proc.path,
                        processCmdline: proc.cmdline,
                        mlFeatures: this.extractProcessMlFeatures(proc),
                    });
                }
            }
            // Detect processes with no path (common in malware that injects into memory)
            if (!proc.path && proc.name && !['[kthreadd]', '[kworker', '[ksoftirqd', '[migration', '[rcu_', '[watchdog'].some(k => proc.name.startsWith(k))) {
                const isKernelThread = proc.name.startsWith('[') && proc.name.endsWith(']');
                if (!isKernelThread && proc.pid > 1000) {
                    // Minor indicator, low score
                }
            }
        }
        return threats;
    }
    extractProcessMlFeatures(proc) {
        return {
            hasPath: proc.path ? 1 : 0,
            pathInTemp: proc.path && /tmp|temp/i.test(proc.path) ? 1 : 0,
            cmdlineLength: Math.min(1, (proc.cmdline || '').length / 200),
            hasInlineCode: /\s+-[erce]\s+["']/.test(proc.cmdline || '') ? 1 : 0,
            isRoot: proc.user === 'root' ? 1 : 0,
            pidHigh: proc.pid > 50000 ? 1 : 0,
        };
    }
    analyseConnections(connections, processes) {
        const threats = [];
        const pidToProcess = new Map();
        for (const p of processes)
            pidToProcess.set(p.pid, p);
        for (const conn of connections) {
            if (!conn.remoteIp || conn.remoteIp === '127.0.0.1' || conn.remoteIp === '::1')
                continue;
            const suspPort = SUSPICIOUS_PORTS[conn.remotePort];
            if (suspPort) {
                const proc = pidToProcess.get(conn.pid);
                threats.push({
                    id: generateId(),
                    threatType: 'suspicious_connection',
                    category: 'network',
                    severity: suspPort.severity,
                    threatScore: suspPort.score,
                    description: `Outbound connection to suspicious port ${conn.remotePort}: ${suspPort.reason}`,
                    indicators: [suspPort.reason, `Remote: ${conn.remoteIp}:${conn.remotePort}`, proc ? `Process: ${proc.name}` : ''].filter(Boolean),
                    connectionRemoteIp: conn.remoteIp,
                    connectionRemotePort: conn.remotePort,
                    connectionProto: conn.proto,
                    processName: proc?.name,
                    processPid: conn.pid,
                    processPath: proc?.path,
                    mlFeatures: {
                        portRisk: suspPort.score / 100,
                        isEstablished: conn.state === 'ESTAB' || conn.state === 'ESTABLISHED' ? 1 : 0,
                    },
                    rawData: { state: conn.state, localAddr: conn.localAddr },
                });
            }
            // High outbound port numbers to unusual destinations (potential C2 beaconing)
            const isPrivateIp = /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|fc00:|fe80:)/.test(conn.remoteIp);
            if (!isPrivateIp && conn.remotePort > 1024 && conn.remotePort < 65535 && conn.remotePort !== 8080 && conn.remotePort !== 8443 && conn.remotePort !== 443 && conn.remotePort !== 80) {
                // Non-standard port to public IP — lower score, informational
                const proc = pidToProcess.get(conn.pid);
                if (proc && !this.isKnownGoodProcess(proc.name)) {
                    threats.push({
                        id: generateId(),
                        threatType: 'suspicious_connection',
                        category: 'network',
                        severity: 'low',
                        threatScore: 25,
                        description: `Outbound connection to non-standard port on public IP: ${conn.remoteIp}:${conn.remotePort}`,
                        indicators: [`Non-standard port: ${conn.remotePort}`, `Public IP: ${conn.remoteIp}`, proc ? `Process: ${proc.name}` : ''].filter(Boolean),
                        connectionRemoteIp: conn.remoteIp,
                        connectionRemotePort: conn.remotePort,
                        connectionProto: conn.proto,
                        processName: proc?.name,
                        processPid: conn.pid,
                        mlFeatures: { portRisk: 0.25, isPublicIp: 1, isNonStandardPort: 1 },
                        rawData: { state: conn.state },
                    });
                }
            }
        }
        return threats;
    }
    isKnownGoodProcess(name) {
        const normalized = (name || '').toLowerCase().replace(/\.exe$/, '');
        const known = ['chrome', 'firefox', 'node', 'npm', 'python', 'python3', 'java', 'git', 'curl', 'wget',
            'apt', 'yum', 'dnf', 'ssh', 'sshd', 'systemd', 'nginx', 'apache2', 'httpd', 'postgres', 'mysql',
            'mongod', 'redis', 'docker', 'containerd', 'kubelet', 'kube-proxy', 'teams', 'slack', 'zoom',
            'code', 'vscodium', 'electron', 'opera', 'brave', 'edge', 'safari', 'thunderbird'];
        return known.includes(normalized);
    }
    analyseServices(services) {
        const threats = [];
        for (const svc of services) {
            const nameLower = (svc.name || '').toLowerCase();
            const pathLower = (svc.path || '').toLowerCase();
            // Services running from temp directories
            if (svc.path && /tmp|temp|appdata.*local.*temp/i.test(svc.path)) {
                threats.push({
                    id: generateId(),
                    threatType: 'suspicious_service',
                    category: 'persistence',
                    severity: 'high',
                    threatScore: 82,
                    description: `Service running from temporary directory: ${svc.name}`,
                    indicators: ['Service path in temp directory', `Path: ${svc.path}`],
                    serviceName: svc.name,
                    scriptPath: svc.path,
                    rawData: { status: svc.status },
                });
                continue;
            }
            // Services with suspicious names
            for (const sp of SUSPICIOUS_PROCESS_PATTERNS) {
                if (sp.pattern.test(nameLower) || (svc.path && sp.pattern.test(pathLower))) {
                    threats.push({
                        id: generateId(),
                        threatType: 'suspicious_service',
                        category: 'persistence',
                        severity: sp.severity,
                        threatScore: sp.score,
                        description: `${sp.reason} found as a service: ${svc.name}`,
                        indicators: [sp.reason, `Service: ${svc.name}`],
                        serviceName: svc.name,
                        scriptPath: svc.path,
                        rawData: { status: svc.status },
                    });
                    break;
                }
            }
        }
        return threats;
    }
    analyseScripts(scripts) {
        const threats = [];
        const scriptSuspicionPatterns = [
            { pattern: /base64\s+[-–]d|base64decode|frombase64/i, reason: 'Base64 decode (common obfuscation)', severity: 'medium', score: 55 },
            { pattern: /curl.*\|\s*bash|wget.*\|\s*sh|fetch.*\|\s*sh/i, reason: 'Remote code execution via pipe', severity: 'critical', score: 92 },
            { pattern: /\/dev\/tcp\/|\/dev\/udp\//i, reason: 'Bash TCP/UDP redirect (reverse shell indicator)', severity: 'high', score: 85 },
            { pattern: /nc\s+-[elu]|\bncat\b.*-e/i, reason: 'Netcat reverse shell pattern', severity: 'critical', score: 90 },
            { pattern: /chmod\s+\+x\s+\/tmp|chmod\s+777\s+\/tmp/i, reason: 'Making temp file executable', severity: 'high', score: 70 },
            { pattern: /python.*pty\|python.*socket|python.*subprocess.*shell/i, reason: 'Python reverse shell pattern', severity: 'critical', score: 88 },
            { pattern: /rm\s+-rf\s+\/(?!tmp|home)\w/i, reason: 'Dangerous recursive delete command', severity: 'high', score: 75 },
            { pattern: /curl\s+.*\|\s*(bash|sh|python|perl|ruby)/i, reason: 'Piped remote execution', severity: 'critical', score: 93 },
            { pattern: /xterm\s+-display\s+\d+\.\d+\.\d+\.\d+/i, reason: 'Remote X11 display forwarding (exfil indicator)', severity: 'medium', score: 60 },
            { pattern: /crontab.*-[lr]|echo.*cron|crontab/i, reason: 'Crontab modification in script', severity: 'medium', score: 50 },
        ];
        for (const script of scripts) {
            // Skip informational entries with no content to analyse
            if (!script.content) {
                if (script.type === 'tmp_executable') {
                    threats.push({
                        id: generateId(),
                        threatType: 'suspicious_script',
                        category: 'persistence',
                        severity: 'medium',
                        threatScore: 60,
                        description: `Executable file found in temporary directory: ${script.path}`,
                        indicators: ['Executable in /tmp or /var/tmp', `Path: ${script.path}`],
                        scriptPath: script.path,
                    });
                }
                continue;
            }
            for (const sp of scriptSuspicionPatterns) {
                if (sp.pattern.test(script.content)) {
                    threats.push({
                        id: generateId(),
                        threatType: 'suspicious_script',
                        category: sp.score >= 85 ? 'malware' : 'persistence',
                        severity: sp.severity,
                        threatScore: sp.score,
                        description: `${sp.reason} in ${script.type}: ${script.path}`,
                        indicators: [sp.reason, `Type: ${script.type}`, `Location: ${script.path}`],
                        scriptPath: script.path,
                        scriptContentPreview: script.content.substring(0, 300),
                    });
                    break; // one threat per script entry
                }
            }
        }
        return threats;
    }
    async blockProcess(pid, processName) {
        try {
            if (this.isWindows) {
                await runShell(`taskkill /PID ${pid} /F 2>nul`);
            }
            else {
                await runShell(`kill -9 ${pid} 2>/dev/null`);
            }
            logger_1.logger.info(`XDR: Killed process PID=${pid} (${processName})`);
            return { success: true, message: `Process ${pid} (${processName}) terminated` };
        }
        catch (e) {
            return { success: false, message: e.message };
        }
    }
    async blockConnection(remoteIp, remotePort) {
        try {
            if (this.isLinux) {
                await runShell(`iptables -I OUTPUT -d ${remoteIp} --dport ${remotePort} -j DROP 2>/dev/null || true`);
                await runShell(`iptables -I INPUT -s ${remoteIp} --sport ${remotePort} -j DROP 2>/dev/null || true`);
                logger_1.logger.info(`XDR: Blocked connection to ${remoteIp}:${remotePort} via iptables`);
                return { success: true, message: `Connection to ${remoteIp}:${remotePort} blocked via iptables` };
            }
            else if (this.isWindows) {
                await runShell(`netsh advfirewall firewall add rule name="XDR_BLOCK_${remoteIp}_${remotePort}" dir=out action=block remoteip=${remoteIp} remoteport=${remotePort} 2>nul`);
                return { success: true, message: `Connection to ${remoteIp}:${remotePort} blocked via Windows Firewall` };
            }
            else {
                return { success: false, message: 'Connection blocking not supported on this platform' };
            }
        }
        catch (e) {
            return { success: false, message: e.message };
        }
    }
    async unblockConnection(remoteIp, remotePort) {
        try {
            if (this.isLinux) {
                await runShell(`iptables -D OUTPUT -d ${remoteIp} --dport ${remotePort} -j DROP 2>/dev/null || true`);
                await runShell(`iptables -D INPUT -s ${remoteIp} --sport ${remotePort} -j DROP 2>/dev/null || true`);
                return { success: true, message: `Block on ${remoteIp}:${remotePort} removed` };
            }
            else if (this.isWindows) {
                await runShell(`netsh advfirewall firewall delete rule name="XDR_BLOCK_${remoteIp}_${remotePort}" 2>nul`);
                return { success: true, message: `Block on ${remoteIp}:${remotePort} removed` };
            }
            return { success: false, message: 'Unblock not supported on this platform' };
        }
        catch (e) {
            return { success: false, message: e.message };
        }
    }
}
exports.XdrScanner = XdrScanner;
//# sourceMappingURL=xdr-scanner.js.map