#!/usr/bin/env python3
import os
import json
import subprocess

# Indicadores de Compromiso (IoCs): Archivos dropper usados por el gusano
DROPPER_FILES = ["setup.mjs", "math_init.js", "Math_Symbol.js"]

# Paquetes a auditar y sus versiones estrictamente maliciosas confirmadas
KNOWN_VULNERABLE_PACKAGES = {
    "keyv": ["6.0.0"],
    "cacheable": ["2.5.1"],
    "@cacheable/memory": ["2.2.1"],
    "@cacheable/net": ["2.1.1"],
    "@cacheable/node-cache": ["3.1.2"],
    "@cacheable/utils": ["2.5.1"],
    "cache-manager": ["7.2.10"],
    "cacheable-request": ["13.0.20"]
}

TARGET_PACKAGES = [
    "keyv", "cacheable", "cacheable-request", "cache-manager",
    "flat-cache", "file-entry-cache"
]

def check_dropper_files(package_dir):
    """Inspecciona el directorio de un paquete en busca de archivos dropper."""
    found_droppers = []
    for dropper in DROPPER_FILES:
        path = os.path.join(package_dir, dropper)
        if os.path.exists(path):
            found_droppers.append(path)
    return found_droppers

def run_npm_list_audit(project_dir):
    """Ejecuta una inspección de dependencias equivalente a 'npm list'."""
    print(f"\n[+] Ejecutando auditoría del árbol de dependencias en: {project_dir}")
    pkgs_str = " ".join(TARGET_PACKAGES)
    try:
        result = subprocess.run(
            f"npm list {pkgs_str}",
            shell=True,
            cwd=project_dir,
            capture_output=True,
            text=True
        )
        output = result.stdout.strip() or result.stderr.strip()
        print("--- Árbol de dependencias resuelto ---")
        print(output)
        print("--------------------------------------")
    except Exception as e:
        print(f"[!] No se pudo ejecutar npm list: {e}")

def scan_directory(root_dir):
    print(f"[*] Iniciando escaneo avanzado contra 'Shai-Hulud' desde: {os.path.abspath(root_dir)}\n")
    warnings_found = 0
    projects_with_npm = set()

    for dirpath, dirnames, filenames in os.walk(root_dir):
        if ".git" in dirnames:
            dirnames.remove(".git")

        # 1. Comprobación directa de archivos dropper en la ruta actual
        for file in filenames:
            if file in DROPPER_FILES:
                file_path = os.path.join(dirpath, file)
                print(f"[CRÍTICO] Archivo dropper malicioso detectado directamente en: {file_path}")
                warnings_found += 1

        # 2. Análisis de package.json
        if "package.json" in filenames:
            pkg_path = os.path.join(dirpath, "package.json")
            
            # Registrar si es la raíz de un proyecto de Node.js
            if "node_modules" in dirnames or os.path.exists(os.path.join(dirpath, "package-lock.json")):
                projects_with_npm.add(dirpath)

            try:
                with open(pkg_path, "r", encoding="utf-8") as f:
                    data = json.load(f)

                # Verificar scripts preinstall sospechosos
                scripts = data.get("scripts", {})
                preinstall = scripts.get("preinstall", "")
                if any(dropper in preinstall for dropper in DROPPER_FILES):
                    print(f"[CRÍTICO] Hook preinstall infectado en: {pkg_path}")
                    print(f"          Comando: \"preinstall\": \"{preinstall}\"")
                    warnings_found += 1

                # Inspección de instalación local si este package.json pertenece a un nodo instalado
                pkg_name = data.get("name", "")
                if pkg_name in TARGET_PACKAGES or any(pkg_name.startswith(p) for p in ["@cacheable/"]):
                    # Verificar si este paquete en node_modules contiene un dropper físicamente
                    droppers = check_dropper_files(dirpath)
                    if droppers:
                        print(f"[CRÍTICO] ¡ALERTA CONFIRMADA! Dropper hallado dentro del paquete '{pkg_name}':")
                        for d in droppers:
                            print(f"          -> {d}")
                        warnings_found += 1
                    else:
                        version = data.get("version", "desconocida")
                        # Verificar versión contra la lista de infecciones confirmadas
                        if pkg_name in KNOWN_VULNERABLE_PACKAGES and version in KNOWN_VULNERABLE_PACKAGES[pkg_name]:
                            print(f"[ALERTA] Versión infectada conocida hallada: {pkg_name}@{version} en {dirpath}")
                            warnings_found += 1
                        else:
                            print(f"[OK] Paquete de riesgo presente pero LIMPIO (Sin droppers, versión segura): {pkg_name}@{version}")

            except (json.JSONDecodeError, PermissionError):
                continue

    # 3. Ejecutar la comprobación automática del árbol en los proyectos encontrados
    for proj in projects_with_npm:
        run_npm_list_audit(proj)

    print("\n" + "="*60)
    if warnings_found == 0:
        print("[OK] Escaneo finalizado: No se detectaron archivos dropper ni versiones maliciosas.")
    else:
        print(f"[RESULTADO] Se encontraron {warnings_found} amenazas reales/críticas. Revisa los detalles arriba.")
    print("="*60)

if __name__ == "__main__":
    scan_directory(os.getcwd())
