// Phantom — Environment auto-detection // Lazy singleton: fast checks on first access, cached in __r.ENV import { execFileSync, spawn } from "child_process"; // ── Cache ── let _populated = false; // ── Quick binary check (which, cached) ── const _binCache = {}; function hasBin(name) { if (name in _binCache) return _binCache[name]; try { execFileSync("which", [name], { timeout: 2000, stdio: "pipe" }); _binCache[name] = true; } catch { _binCache[name] = false; } return _binCache[name]; } // ── Security/network tool categories ── const RECON_BINS = ["nmap","masscan","subfinder","amass","whois","dig","nslookup","dnsx","shuffledns"]; const EXPLOIT_BINS = ["sqlmap","metasploit","searchsploit","hydra","john","hashcat","nuclei","ffuf","gobuster"]; const DEV_BINS = ["python3","python","go","rustc","gcc","make","git","curl","wget","jq"]; const NET_BINS = ["ping","traceroute","netstat","ss","iptables","ufw"]; const BROWSER_BINS = ["google-chrome","chromium","firefox","chromium-browser"]; const ALL_BINS = [...RECON_BINS, ...EXPLOIT_BINS, ...DEV_BINS, ...NET_BINS, ...BROWSER_BINS]; // Binary name → apt package name mapping const PKG_MAP = { nmap:"nmap", masscan:"masscan", whois:"whois", dig:"dnsutils", hydra:"hydra", john:"john", hashcat:"hashcat", traceroute:"traceroute", netstat:"net-tools", ss:"iproute2", nikto:"nikto", dirb:"dirb", whatweb:"whatweb", wafw00f:"wafw00f", aircrack:"aircrack-ng", tshark:"tshark", sqlmap:"sqlmap", dnsrecon:"dnsrecon", }; // Estimated installed size in KB (from apt-cache show). hashcat=145MB is excluded. const PKG_SIZE = { nmap:4156, masscan:464, whois:300, dig:1200, hydra:1006, john:403, hashcat:148553, traceroute:216, netstat:1072, ss:3703, nikto:1862, dirb:1594, whatweb:19206, wafw00f:260, aircrack:2491, tshark:398, sqlmap:15000, dnsrecon:200, }; function detectTools() { const tools = {}; try { const script = ALL_BINS.map(t => `command -v ${t} 2>/dev/null && echo BIN:${t}`).join(";"); let out; try { out = execFileSync("sh", ["-c", script], { timeout: 5000, stdio: ["pipe","pipe","pipe"] }).toString(); } catch (e) { // Some commands fail (tools not found) — still read partial stdout out = e.stdout?.toString() || ""; } const found = new Set(out.trim().split("\n").filter(l => l.startsWith("BIN:")).map(l => l.slice(4))); for (const t of ALL_BINS) tools[t] = found.has(t); } catch { for (const t of ALL_BINS) tools[t] = false; } return tools; } // ── Package manager ── function detectPkgManager() { if (hasBin("apt")) return "apt"; if (hasBin("apk")) return "apk"; if (hasBin("yum")) return "yum"; if (hasBin("dnf")) return "dnf"; if (hasBin("pacman")) return "pacman"; if (hasBin("brew")) return "brew"; if (hasBin("pkg")) return "pkg"; return null; } // ── Shell ── function detectShell() { return process.env.SHELL?.split("/").pop() || "sh"; } // ── Network ── function detectNetwork() { const info = { online: false, publicIP: null, dns: null, proxy: null }; try { execFileSync("sh", ["-c", "timeout 3 curl -s --max-time 3 https://1.1.1.1 >/dev/null 2>&1 || timeout 3 ping -c 1 -W 2 8.8.8.8 2>/dev/null"], { timeout: 5000 }); info.online = true; } catch {} try { const out = execFileSync("sh", ["-c", "timeout 3 dig +short myip.opendns.com @resolver1.opendns.com 2>/dev/null || timeout 3 curl -s --max-time 3 ifconfig.me 2>/dev/null || timeout 3 curl -s --max-time 3 icanhazip.com 2>/dev/null"], { timeout: 5000, stdio: ["pipe","pipe","pipe"] }); const ip = out.toString().trim(); if (ip) { info.publicIP = ip; info.online = true; } } catch {} if (process.env.HTTP_PROXY || process.env.https_proxy) info.proxy = process.env.HTTP_PROXY || process.env.https_proxy; if (process.env.HTTPS_PROXY || process.env.https_proxy) info.proxy = info.proxy || process.env.HTTPS_PROXY; try { const out = execFileSync("sh", ["-c", "grep '^nameserver' /etc/resolv.conf 2>/dev/null | head -1 | awk '{print $2}'"], { timeout: 2000 }); const dns = out.toString().trim(); if (dns) info.dns = dns; } catch {} return info; } // ── System resources ── function detectResources() { const r = { cpu: 0, mem: 0, memFree: 0, diskFree: 0, arch: process.arch }; try { r.cpu = parseInt(execFileSync("nproc",[],{timeout:2000}).toString().trim()) || 0; } catch { try { r.cpu = parseInt(execFileSync("sh",["-c","grep -c ^processor /proc/cpuinfo 2>/dev/null || echo 1"],{timeout:2000}).toString().trim()) || 1; } catch { r.cpu = 1; } } try { const out1 = execFileSync("sh",["-c","grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}' || echo 0"],{timeout:2000}); r.mem = parseInt(out1.toString().trim()) || 0; const out2 = execFileSync("sh",["-c","grep MemAvailable /proc/meminfo 2>/dev/null | awk '{print $2}' || grep MemFree /proc/meminfo 2>/dev/null | awk '{print $2}' || echo 0"],{timeout:2000}); r.memFree = parseInt(out2.toString().trim()) || 0; } catch {} try { const df = execFileSync("sh",["-c","df / 2>/dev/null | tail -1 | awk '{print $4}'"],{timeout:2000}); const kb = parseInt(df.toString().trim()); if (!isNaN(kb)) r.diskFree = kb; } catch {} return r; } const CORE_TOOLS = ["curl","wget","git","jq","python3"]; // Auto-install core missing tools (skips if disk < 50MB). // Returns: >0 installed, 0 nothing needed, -1 install failed, -2 disk too low export function autoInstallCore(envTools) { if (!envTools?.pkgMgr || envTools.pkgMgr !== "apt") return 0; if (envTools.resources?.diskFree < 50*1024) return -2; if (!envTools.tools) return 0; const missing = CORE_TOOLS.filter(t => envTools.tools[t] === false); if (!missing.length) return 0; try { execFileSync("apt",["install","-y",...missing],{timeout:120000,stdio:"pipe"}); for (const t of missing) { try { execFileSync("which",[t],{timeout:2000,stdio:"pipe"}); envTools.tools[t] = true; } catch {} } return missing.length; } catch { return -1; } } // Auto-install missing security tools (background, non-blocking). // Picks smallest tools first, skips hashcat (145MB). Runs at most once. let _autoSecurityStarted = false; export function autoInstallSecurity(envTools, logFn) { if (_autoSecurityStarted) return; if (!envTools?.pkgMgr || envTools.pkgMgr !== "apt") return; if (envTools.resources?.diskFree < 200*1024) return; // need 200MB+ free if (!envTools.tools) return; const missingBins = Object.entries(envTools.tools) .filter(([k,v]) => !v && PKG_MAP[k]) .map(([k]) => k); // Skip hashcat (145MB), sort remaining by size (smallest first) const targets = missingBins .filter(t => t !== "hashcat" && t !== "sqlmap") // skip biggest .sort((a, b) => (PKG_SIZE[a] || 999999) - (PKG_SIZE[b] || 999999)) .slice(0, 15); // max 15 packages if (!targets.length) return; _autoSecurityStarted = true; const pkgs = targets.map(t => PKG_MAP[t]); const totalEst = targets.reduce((s, t) => s + (PKG_SIZE[t] || 0), 0); const neededKb = totalEst + 50000; // +50MB margin for deps if (envTools.resources.diskFree < neededKb) { if (logFn) logFn(`[env] Need ~${Math.round(neededKb/1024)}MB for security tools, only ${Math.round(envTools.resources.diskFree/1024)}MB free — skipping auto-install`); _autoSecurityStarted = false; return; } if (logFn) logFn(`[env] Auto-installing ${targets.length} security tools (${Math.round(totalEst/1024)}MB)...`); const proc = spawn("apt", ["install", "-y", ...pkgs], { stdio: ["pipe", "pipe", "pipe"], timeout: 300000 }); let output = ""; proc.stdout.on("data", d => { output += d.toString(); }); proc.stderr.on("data", d => { output += d.toString(); }); proc.on("close", (code) => { if (code === 0 && logFn) { // Refresh tool detection const freshTools = detectTools(); Object.assign(envTools.tools, freshTools); const now = Object.entries(freshTools).filter(([,v]) => v).length; if (logFn) logFn(`[env] ✓ ${targets.length} tools installed (${now}/${Object.keys(freshTools).length} available)`); } else { if (logFn) logFn(`[env] ✗ apt install exited code ${code} — will retry next startup`); _autoSecurityStarted = false; // allow retry } }); proc.on("error", (e) => { if (logFn) logFn(`[env] ✗ apt install error: ${e.message}`); _autoSecurityStarted = false; }); } // ── Public API ── export function populateEnv(existingEnv = {}) { if (_populated) return; _populated = true; const tools = detectTools(); const pkgMgr = detectPkgManager(); const shell = detectShell(); const network = detectNetwork(); const resources = detectResources(); // Auto-install core missing tools const installed = autoInstallCore({ pkgMgr, tools, resources }); if (installed > 0) { const freshTools = detectTools(); Object.assign(tools, freshTools); } const availableCount = Object.values(tools).filter(Boolean).length; const totalChecked = Object.keys(tools).length; Object.assign(existingEnv, { tools, pkgMgr, shell, network, resources, availableTools: availableCount, totalChecked }); } export function getEnvSummary(env) { const lines = []; lines.push(`Platform: ${env.platform || "?"}`); lines.push(`Shell: ${env.shell || "?"}`); lines.push(`Terminal: ${env.terminal || "?"}`); lines.push(`Colors: ${env.hasTrueColor ? "truecolor" : env.has256 ? "256" : "16"}`); lines.push(`Screen: ${env.cols}x${env.rows} (${env.screenSize})`); lines.push(`Package manager: ${env.pkgMgr || "none"}`); lines.push(`Network: ${env.network?.online ? "online" : "offline"}${env.network?.publicIP ? " · IP: " + env.network.publicIP : ""}${env.network?.dns ? " · DNS: " + env.network.dns : ""}`); lines.push(`CPU: ${env.resources?.cpu || "?"} cores · RAM: ${Math.round((env.resources?.mem||0)/1024)}MB (${Math.round((env.resources?.memFree||0)/1024)}MB free) · Disk: ${Math.round((env.resources?.diskFree||0)/1024)}MB free`); const { tools } = env; if (tools) { const avail = Object.entries(tools).filter(([,v]) => v).map(([k]) => k); const missing = Object.entries(tools).filter(([,v]) => !v).map(([k]) => k); lines.push(`Tools: ${avail.length}/${Object.keys(tools).length} available`); lines.push(` Present: ${avail.join(", ")}`); if (missing.length < 60) lines.push(` Missing: ${missing.join(", ")}`); } return lines.join("\n"); }