Supply Chain en la Nube: cuando tu dependencia de NPM o PyPI apunta a un servidor de atacante

El patrón se repite: un equipo acelera entregas, sube versiones con frecuencia y confía en que “npm install” o “pip install” son operaciones inocuas. En cloud, esa suposición es peligrosa. Una dependencia puede ejecutar código en el pipeline, exfiltrar secretos y, sobre todo, intentar acceder al metadata service de la instancia o del runner para obtener credenciales temporales con las que moverse por la cuenta.

El problema no es solo “instalar un paquete vulnerable”. Es permitir que tu cadena de suministro apunte a un servidor controlado por un atacante: registries alternativos, dependencias con URLs directas, typosquatting, o paquetes que descargan payloads adicionales desde fuera de NPM/PyPI. Si tu CI/CD tiene salida a Internet y credenciales con permisos reales, el impacto deja de ser teórico.

Qué salió mal: la dependencia que convierte el install en un canal de exfiltración

El fallo típico no es un RCE sofisticado. Es una decisión “práctica” en un PR: añadir una dependencia de NPM o PyPI que, directa o indirectamente, incluye un origen no verificado. En Node.js suele verse con entradas en package.json que apuntan a git+https, a un tarball en una URL, o con scripts de lifecycle (por ejemplo, preinstall, install, postinstall) que ejecutan código al instalar.

En Python, el equivalente se manifiesta cuando la instalación dispara lógica en el build/install (según el empaquetado) o cuando el paquete hace llamadas de red en import/instalación. El resultado práctico en empresa es el mismo: el pipeline ejecuta código que no pasó por revisión real, con acceso a red y, con frecuencia, con acceso implícito a credenciales (variables de entorno del CI, tokens, o credenciales cloud temporales del runner).

La consecuencia más dañina en nube aparece cuando ese código intenta hablar con el metadata service (por ejemplo, el endpoint link-local de metadatos) para obtener credenciales temporales asociadas al rol de la instancia/runner. Muchas organizaciones asumen que “eso solo existe en producción”, pero runners autoalojados, agentes de build en VMs o nodos Kubernetes suelen tener exactamente ese tipo de identidad.

Cómo un package.json malicioso busca credenciales en metadata services (y por qué funciona en CI)

Un atacante no necesita que tu aplicación esté en ejecución; le basta con que el código se ejecute durante el install. En Node.js, los scripts de lifecycle permiten ejecutar comandos en el entorno de build. Un paquete malicioso puede intentar acceder al endpoint de metadatos desde el runner y, si lo consigue, obtener credenciales temporales que luego exfiltra a un servidor controlado por el atacante.

Esto funciona porque en muchos pipelines hay tres condiciones simultáneas: salida a Internet sin restricciones, identidad cloud con permisos (a veces excesivos) y ausencia de barreras entre el proceso de build y recursos internos. En una investigación interna, lo que suele sorprender es lo trivial del “paso atacante”: una simple llamada HTTP al endpoint de metadatos seguida de un POST a un dominio externo.

  • Señal técnica: acceso a endpoints link-local durante el install

En logs de red o egress, ver conexiones a direcciones link-local asociadas a metadata (por ejemplo, rangos reservados para ese propósito) durante la fase de dependencias suele indicar que algo en el pipeline está intentando enumerar credenciales. En la práctica, esta señal es más útil que buscar “strings maliciosos” dentro de un paquete, porque el atacante puede ofuscar, pero no puede evitar hablar con metadatos si su objetivo es robar identidad cloud.

  • Impacto real: credenciales temporales con permisos suficientes para pivotar

En empresas, los roles asignados a runners suelen tener permisos para leer artefactos, publicar en registries internos, asumir otros roles o interactuar con servicios de despliegue. Un robo de credenciales temporales aquí no es “solo un token”: puede terminar en acceso a repositorios, manipulación de artefactos firmados o despliegue de versiones alteradas.

Señales tempranas en empresa: cuando la supply chain ya está apuntando fuera

Antes de ver un incidente, suele haber señales en el propio repositorio. Un ejemplo muy común: dependencias declaradas como URLs directas o repositorios Git en lugar de paquetes fijados a un registry corporativo. En Node.js, además, la presencia de scripts en paquetes nuevos o poco usados debería elevar sospecha, especialmente cuando aparecen junto con cambios que “solo actualizan dependencias”.

Otra señal es operacional: builds que, de repente, empiezan a tardar más y muestran descargas adicionales desde dominios no habituales. Esto pasa cuando un paquete incorpora lógica de “download on install” para traer binarios o payloads. En entornos corporativos con proxy, estas descargas a veces quedan registradas como excepciones “temporales” para que el build pase, y esa excepción se queda para siempre.

  • Lo que reviso en PRs de dependencias

Si el pipeline no tiene guardrails fuertes, la revisión humana es la última barrera. En la práctica, conviene revisar si se introducen fuentes no estándar (tarballs/URLs), si se activan scripts de instalación, y si se cambia el lockfile de forma masiva sin justificación. Un lockfile enorme no es sospechoso por sí mismo, pero sí lo es cuando arrastra paquetes “nuevos” que no guardan relación con el cambio funcional.

  • Lo que busco en observabilidad de red del runner

En entornos maduros, el runner (o su VPC/subred) tiene logs de egress. Ahí busco dominios recién vistos, conexiones a endpoints link-local durante fases de build, y patrones de POST/PUT a hosts externos justo después de instalar dependencias. No es infalible, pero reduce el tiempo de detección de días a minutos.

Cómo hacerlo en la práctica: bloquear fuentes no verificadas en CI/CD sin romper el delivery

La medida más efectiva es tratar el consumo de dependencias como un canal controlado, no como “Internet abierto”. En empresa, esto significa: un registry interno (o proxy) como única fuente permitida, políticas que impidan resoluciones directas hacia NPM/PyPI públicos salvo casos aprobados, y pipelines que fallen si detectan dependencias fuera de policy.

Para Node.js, el control más inmediato es configurar el registry corporativo y prohibir instalaciones desde URLs arbitrarias. Para Python, además de index/extra-index, hay que ser estricto con qué índices se permiten. El objetivo no es solo “cachear” dependencias; es impedir que el manifiesto apunte a un servidor del atacante.

  • Configurar NPM para usar solo tu registry (y validar que se cumple)

Acción concreta: define un .npmrc en el repositorio o en la imagen base del runner con registry= apuntando al registry corporativo (o a un proxy controlado). Luego, en el pipeline, añade una verificación que falle si el registry efectivo no es el esperado (por ejemplo, ejecutando npm config get registry y comparándolo). Esto evita que un job o script sobrescriba el registry “a conveniencia”.

  • Configurar pip para un único índice permitido (y romper builds que usen otro)

Acción concreta: fija en pip.conf (o variables equivalentes) el index-url del índice corporativo y evita extra-index-url salvo excepciones documentadas. En el pipeline, valida la configuración efectiva con pip config debug y haz que el job falle si detecta índices no aprobados. En entornos con muchos equipos, esta validación automatizada reduce el “shadow IT” de dependencias.

Si tu organización usa runners autoalojados, complementa lo anterior con control de egress: el runner debería poder hablar con el registry corporativo y poco más durante la fase de dependencias. Esto no es teoría; es lo que evita que, aunque se cuele un paquete con código malicioso, pueda exfiltrar o descargar payloads desde infra del atacante.

Políticas e identidad del runner: impedir el acceso a metadatos y reducir el valor de lo robado

Aunque bloquees fuentes, asume que algún día algo pasará. El segundo pilar es que el entorno de CI/CD no sea un “salto fácil” a credenciales cloud. En cloud, el objetivo del atacante suele ser el metadata service porque entrega credenciales temporales asociadas a la identidad de la instancia/runner. Si esa identidad existe y tiene permisos, el atacante la quiere.

Dos medidas prácticas: reducir o eliminar el acceso a metadatos desde jobs y hacer que la identidad del runner tenga permisos mínimos y trazables. En AWS, por ejemplo, muchas organizaciones avanzan a IMDSv2 y restringen el acceso al endpoint de metadatos; y, además, migran a identidades efímeras específicas del job (cuando el modelo de CI lo soporta), en lugar de roles amplios atados a una VM “multi-tenant”.

Validación operativa: además de “configurar”, hay que probar. Un test controlado en el runner que intente acceder al endpoint de metadatos debe fallar o requerir token, y los permisos asociados al rol del runner deben revisarse como si fueran permisos de producción. En auditorías internas, es frecuente encontrar runners con permisos heredados “por comodidad” para destrabar despliegues urgentes.

  • Qué reviso en AWS para asegurar que el runner no es un objetivo fácil

Reviso la configuración de metadatos en las instancias/plantillas (requerir tokens, límites de hop, etc.) y confirmo que no hay rutas inesperadas que permitan acceso desde contenedores o procesos no confiables. Luego, inspecciono el rol asociado: políticas adjuntas, permisos para asumir otros roles y acceso a secretos. Si el runner puede leer secretos de forma amplia, el atacante no necesita ni metadatos: basta con ejecutar aws y enumerar.

  • Qué evidencia dejo para poder demostrar cumplimiento

No alcanza con “decir que está bloqueado”. Dejo evidencia reproducible: configuración versionada de plantillas/infra (IaC), logs de egress que muestren que durante builds solo se habla con el registry permitido, y un job de verificación (control) que falle si el registry efectivo cambia o si hay conectividad a destinos no aprobados. Esto reduce discusiones cuando un equipo pide “abrir Internet un momento”.

Recomendaciones para entornos corporativos

Si tu dependencia de NPM o PyPI puede apuntar a un servidor de atacante, el problema no es la biblioteca concreta: es que el pipeline tiene libertad para ejecutar y comunicarse fuera de control. Un package.json o configuración de dependencias puede convertir el install en un canal para buscar credenciales en metadata services y exfiltrarlas con muy poco ruido.

Las medidas que más suelen funcionar en empresa combinan dos cosas: control de fuentes (registry corporativo como única vía, validación en pipeline, egress restringido) y reducción del impacto (runner con identidad mínima, sin acceso fácil a metadatos, con permisos revisados como activos críticos). Cuando ambas están bien aplicadas, la supply chain deja de ser una puerta abierta y pasa a ser un proceso gobernado.


¿Te interesa la seguridad en Cloud?

Comparto análisis técnicos, laboratorios prácticos y experiencias reales sobre Cloud Security.

Política de privacidad