Gabriel Neuman
Gabriel Neuman
SEGURIDAD · SUPPLY CHAIN

Una dependencia hackeada escondió un troyano detrás de 10 mil espacios. Esto lo caza antes de que corra.

Si tu equipo usa Claude Code o Cursor, cada npx paquete@latest es una apuesta a que el publisher no fue hackeado hoy. pin-guard fija las versiones exactas, escanea el RAT del inyector de whitespace que pegó en 2026 y bloquea paquetes sospechosos antes de que toquen tu máquina. Lo curé porque no tiene una sola dependencia: es puro Python de la librería estándar, y una herramienta de supply-chain no debería tener supply-chain.

Estrellas
8★
Lenguaje
Python
Dependencias
0
Revisado
13 jun 2026
Lo que ofrece

De un vistazo, lo que vas a obtener si lo instalas.

  • ·
    Versiones fijas

    Reescribe rangos con acento circunflejo y arroba-latest a semver exacto en package.json, scripts, configs de MCP y workflows de CI.

  • ·
    Escaneo de indicadores

    Detecta el inyector de whitespace, paquetes comprometidos conocidos en cuatro tipos de lockfile y referencias a hosts y wallets de comando y control.

  • ·
    Gate antes de instalar

    verify.py revisa edad del release, scripts de instalación y lista negra antes de que un paquete nuevo toque tu máquina.

  • ·
    Hook de pre-commit

    Bloquea cualquier commit que cargue un indicador de compromiso, para que el malware no llegue ni a la rama.

  • ·
    Cero dependencias

    Puro Python de la librería estándar. Una herramienta de supply-chain no debería tener supply-chain propia.

Por qué importa

El contexto detrás del repo.

En 2026 una dependencia de npm fue secuestrada y escondió su payload dentro de postcss.config.mjs, detrás de unos 10 mil espacios para que fuera invisible en el editor y en el git diff. Al hacer build corría con todos los privilegios de Node. Debajo de la ofuscación era un loader de RAT de tipo blockchain dead-drop: leía comandos escondidos en transacciones de TRON y BSC y los ejecutaba con node -e. Ejecución remota completa, por un canal que no se puede tumbar. Entró por una dependencia sin fijar.

pin-guard ataca esa clase entera de riesgo con varias capas. Estáticamente fija todas las versiones a semver exacto en package.json, scripts de npm, configs de MCP (.mcp.json, settings de Claude y Cursor) y workflows de GitHub Actions. Escanea en busca de los marcadores del inyector, configs infladas con corridas largas de espacios, versiones comprometidas conocidas en los lockfiles de npm, pnpm, yarn y bun, y referencias a los hosts y wallets de comando y control. Antes de instalar cualquier paquete nuevo, verify.py checa la edad del release, el hueco desde el release anterior, los scripts de instalación y si la versión está en la lista negra.

Lo importante para mí: el scanner es de solo lectura, el pin es dry-run por default, y verify.py trae protección contra argument-injection (probé pasarle un nombre malicioso tipo bandera de npm y lo rechaza). Es la clase de herramienta defensiva que puedes correr sin miedo a que te rompa el repo.

Para qué te sirve

Cuándo lo recomiendo (y cuándo no).

Es para equipos que ya corren agentes de IA (Claude Code, Cursor, Codex) sobre repos de JavaScript o TypeScript y entienden que cada npx paquete@latest es una superficie de ataque. Si tu CI hace npm install en lugar de npm ci, si tus dependencias tienen rangos con acento circunflejo, o si nunca verificaste un paquete antes de instalarlo, esto te da las cinco capas de defensa en una tarde. El scan es seguro de correr ahora mismo: te dice qué tan expuesto estás sin cambiar nada.

No es para quien necesita protección en tiempo de ejecución. pin-guard es la capa estática (versiones fijas, escaneo de indicadores). El repo recomienda una capa de runtime aparte que instala un plugin de terceros; esa parte la dejaría fuera hasta auditarla por separado, porque agregar un marketplace externo sin revisar es justo lo que el resto de la herramienta predica evitar. Tampoco esperes una comunidad grande detrás: es un proyecto nuevo, de un solo mantenedor. El valor está en el código, no en el sello.

Cómo arrancarlo

En 3 pasos, listo para probar.

  1. 01

    Audita cualquier repo (solo lectura)

    python3 scripts/scan.py /ruta/a/tu/repo

    No cambia nada. Reporta cada versión sin fijar con archivo y línea, los huecos de higiene y los hallazgos de indicadores. Sale con código 1 si encuentra algo, 0 si está limpio.

  2. 02

    Verifica un paquete antes de instalarlo

    python3 scripts/verify.py algun-paquete@1.2.3

    Checa edad del release, hueco desde el anterior, scripts de instalación y lista negra. Si el paquete es una versión comprometida conocida, bloquea con código de salida 2 y te dice por qué.

  3. 03

    Instala el guard de pre-commit

    bash scripts/install-hook.sh /ruta/a/tu/repo

    Deja un hook de pre-commit que corre el escaneo de indicadores antes de cada commit y lo bloquea si encuentra malware. La capa que evita que el RAT llegue siquiera a tu rama.

El incidente que le dio origen

En 2026 una dependencia de npm fue secuestrada por una toma de cuenta del mantenedor. El atacante metió el payload dentro de un archivo de configuración legítimo (postcss.config.mjs), empujado a la derecha detrás de cerca de 10 mil espacios en la línea del export default. En el editor se veía un archivo de config normal. En el git diff no aparecía nada raro. Pero al correr el build, ese código se ejecutaba con todos los privilegios de Node del usuario.

Una vez deofuscado, el payload era un loader de RAT con un mecanismo de comando y control poco común: un blockchain dead-drop. En lugar de hablar con un servidor que se pueda tumbar, leía la última transacción de una wallet de TRON, la convertía en un hash de transacción de BSC, descargaba el input de esa transacción, lo decodificaba a un comando y lo ejecutaba con node -e. Ejecución remota completa, a demanda, por un canal descentralizado que no se puede dar de baja.

El punto de entrada fue una dependencia sin fijar. Eso es lo que pin-guard elimina.

Qué atrapa el escaneo

  • Specs sin fijar (acento circunflejo, virgulilla, asterisco, latest, versiones faltantes) en package.json, scripts de npm, configs de MCP (.mcp.json, .claude/settings.json, .cursor/mcp.json) y GitHub Actions.
  • Marcadores del inyector en código JavaScript y TypeScript.
  • Archivos de config inflados con una corrida de 200+ caracteres de espacios antes del código. El tamaño solo es una advertencia; la corrida de espacios es la firma real de la inyección.
  • Versiones comprometidas conocidas en lockfiles de npm, pnpm, yarn y bun.
  • Referencias a hosts o wallets de comando y control en el código fuente (la firma del loader del RAT).
  • Droppers tipo setup.cjs y entradas .bat en .gitignore.
  • Lockfile faltante, npm install en lugar de npm ci, y .npmrc sin save-exact.

Defensa en capas

Capa Herramienta Qué atrapa
Pre-vuelo Regla en CLAUDE.md Que el agente escanee antes de tocar el repo
Pre-commit install-hook.sh git bloquea commits infectados
Pre-install verify.py el paquete malo antes de que aterrice
Estática scan.py marcadores, configs, lockfiles, refs de C2
Pinning pin.py quita el riesgo de versiones sin fijar

Lo que probé antes de curarlo

No publico nada que no haya corrido. Cloné el repo, leí los cinco scripts línea por línea y los probé:

  • scan.py sobre un repo real de Next.js: cero falsos positivos de malware, solo reportó las dependencias con rango sin fijar (que es su trabajo).
  • verify.py axios@1.12.3 (una versión comprometida conocida): bloqueó correctamente con código de salida 2.
  • verify.py con un nombre de paquete malicioso disfrazado de bandera de npm: lo rechazó antes de consultar el registro. La protección contra argument-injection funciona.

El código es honesto: el escaneo es de solo lectura, el pin es dry-run por default y no tiene una sola dependencia externa.

Limitaciones honestas

  • Es un proyecto nuevo (junio de 2026), de un solo mantenedor y con pocas estrellas. El valor está en lo simple y auditable del código, no en una comunidad grande detrás.
  • La capa de runtime que recomienda en su documentación instala un plugin de terceros desde un marketplace externo. Esa parte la dejaría fuera hasta auditarla por separado: agregar supply-chain externa sin revisar contradice el resto de la herramienta.
  • Es defensa de supply-chain, no un antivirus. Si tu máquina ya está comprometida, pin-guard no la limpia; te dice que no sigas y que rotes credenciales primero.

Mi recomendación

Si tu equipo adoptó Claude Code o Cursor y esos agentes corren npx contra el registro de npm, tienes esta superficie de ataque abierta hoy. Correr scan.py no cuesta nada y te dice en minutos qué tan expuesto estás. Fijar versiones y meter el hook de pre-commit es trabajo de una tarde que cierra la puerta por la que entró el incidente de 2026. El downside es nulo (Python puro, sin dependencias, el escaneo no modifica nada) y el upside es no ser la próxima nota de un RAT escondido detrás de 10 mil espacios.

De Gabriel Neuman para tu equipo

¿Sabes qué se instala cuando tu equipo corre un agente de IA?

Si tu empresa adoptó Claude Code o Cursor sin un plan de seguridad para las dependencias que esos agentes jalan, te ayudo a montar el stack que cierra esa puerta. Una llamada para revisar tu superficie de exposición.