Blog Logo

Así roban paquetes npm: checklist para proteger el tuyo

Un mantenedor de un paquete con millones de descargas semanales abre un email que parece de soporte de npm, mete sus credenciales y, al día siguiente, su paquete publica una versión que roba tokens a todo el que la instala. No es un guion de película: es el patrón que se repite trimestre sí, trimestre también, con phishings cada vez más finos. Y no hace falta tener un paquete famoso para salir perdiendo: basta con una dependencia que se actualice sola en tu CI.

GitHub lleva meses desplegando cambios en npm y Actions para cortar esas cadenas de ataque, y esta semana ha publicado un repaso de todo lo que ya está en producción. La buena noticia: la mayoría de esas defensas son gratuitas y se activan con configuración, no con infraestructura nueva.

La mala: muchas son opt-in, y si nadie las activa en tu proyecto, no existen. Así que en vez de resumir la noticia, aquí va lo que puedes aplicar hoy, punto por punto, con los enlaces a la documentación oficial de cada medida. Si solo tienes diez minutos, ve directo al punto 1.

Escudo con un check sobre bloques verdes, imagen oficial de GitHub para seguridad de la cadena de suministro

Así roban tu paquete: la cadena de ataque en 30 segundos

El patrón se repite: el atacante compromete la cuenta de un mantenedor o cuela código en un workflow de CI, desde ahí exfiltra credenciales (tokens de npm, secrets del pipeline), y con esas credenciales publica versiones maliciosas que se propagan a cientos de proyectos antes de que nadie reaccione. GitHub lo divide en tres fases: compromiso inicial, exfiltración de credenciales y propagación. Cada medida que sigue rompe una de esas fases.

1. Publica paquetes sin tokens: trusted publishing con OIDC

La medida con mejor ratio esfuerzo/impacto: eliminar el token de npm de larga duración que vive en los secrets de tu CI. Con trusted publishing, tu workflow se autentica contra npm con OIDC: tokens efímeros, firmados, válidos solo para ese workflow concreto. No hay nada que robar.

Funciona con GitHub Actions, GitLab CI/CD y, desde abril, CircleCI. Necesitas npm CLI 11.5.1+ y Node 22.14.0+. Se configura en dos pasos: en npmjs.com (Settings del paquete → Trusted Publisher) declaras organización, repo y nombre del fichero de workflow; y en el workflow añades el permiso OIDC:

permissions:
  id-token: write # imprescindible para OIDC
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: '24'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
      - run: npm publish # sin NODE_AUTH_TOKEN, sin secrets

Dos detalles importantes de la documentación: publicando así desde un repo público se genera provenance automáticamente (prueba criptográfica de dónde y cómo se construyó el paquete), y una vez migrado deberías ir a Settings → Publishing access y marcar “Require two-factor authentication and disallow tokens” para que los tokens clásicos dejen de funcionar en ese paquete. Ah, y solo se permite un trusted publisher por paquete.

2. Staged publishing: que las credenciales de CI no basten para publicar

Si quieres ir un paso más allá, el staged publishing (opt-in, disponible desde mayo) desacopla “tener credenciales” de “publicar”: el paquete queda en staging hasta que un mantenedor lo aprueba con 2FA desde la CLI o npmjs.com. La combinación que recomienda la propia documentación de npm es configurar el trusted publisher solo con permiso de npm stage publish (sin npm publish), de modo que todo lo que sale del CI necesita aprobación humana antes de ser público. Si te roban el pipeline, el atacante se queda a un paso de nada.

3. npm v12: los scripts de instalación se apagan por defecto

La vía favorita de propagación de este malware no es el código del paquete en runtime, sino los scripts preinstall/postinstall: se ejecutan en cuanto haces npm install, antes de que nadie haya leído nada. Con npm v12 cambian tres defaults:

  • allowScripts pasa a off: ninguna dependencia ejecuta scripts de instalación salvo que la apruebes explícitamente. Esto incluye los builds implícitos de node-gyp.
  • --allow-git pasa a none: no se resuelven dependencias desde Git salvo opt-in.
  • --allow-remote pasa a none: lo mismo para tarballs por URL remota.

Para prepararte, actualiza a npm 11.16.0 o superior y revisa los warnings de tu instalación habitual. El flujo es:

# Ver qué paquetes tienen scripts pendientes de aprobación
npm approve-scripts --allow-scripts-pending

# Aprobar los que confías / denegar el resto
npm approve-scripts
npm deny-scripts

La lista resultante se escribe en package.json y se commitea, así la decisión es auditable en code review. Si hoy instalas con --ignore-scripts en CI por costumbre, v12 simplemente convierte esa cautela en el comportamiento por defecto de todo el mundo.

4. GitHub Actions: cierra los pwn requests

En el lado del CI, los cambios de junio atacan el patrón de “pwn request”: un workflow con pull_request_target que hace checkout del código del fork y lo ejecuta con permisos elevados. Tres cosas a revisar:

  1. actions/checkout ya no descarga código de forks por defecto en los triggers habitualmente explotados; tienes que hacer opt-out explícito para volver al comportamiento inseguro. Si tu workflow dependía de eso, te toca revisarlo: es casi seguro que estabas expuesto.
  2. Políticas de ejecución de workflows: a nivel de repo, organización o empresa puedes limitar quién dispara workflows y qué tipos de trigger están permitidos. Si ningún workflow tuyo necesita pull_request_target, desactívalo de golpe.
  3. Caché de Actions en solo lectura para triggers no confiables: un workflow poco privilegiado ya no puede envenenar la caché que comparte con tu workflow de release.

Y lo de siempre, que sigue siendo gratis: declara permissions: mínimos en cada workflow (el GITHUB_TOKEN nace con demasiados permisos si no dices nada), fija las actions por SHA de commit y no por tag, y revisa quién puede modificar .github/workflows/. Todo esto está en la guía oficial de hardening de Actions.

5. Dependabot: el cooldown de tres días ya viene de serie

Desde el 14 de julio, Dependabot espera al menos tres días desde que una versión se publica antes de abrir la PR de actualización. La lógica es que estos ataques dependen de la velocidad: si una versión maliciosa se detecta y retira en 48 horas, un cooldown de 3 días te salva sin que hagas nada. Las actualizaciones de seguridad siguen abriéndose al momento.

Si quieres otro valor (más días, o distinto por tipo de bump), se configura con la opción cooldown en dependabot.yml:

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    cooldown:
      default-days: 5
      semver-major-days: 10

6. Para cuando algo sale mal

Dos herramientas de respuesta que conviene conocer antes de necesitarlas: la API de revocación de credenciales ahora cubre también tokens de OAuth y de GitHub Apps (nació en 2025 solo para PATs), y los administradores de empresa tienen revocación self-service de todas las credenciales de un usuario. Además, npm pone las cuentas de alto impacto en modo solo-lectura durante 72 horas si cambian el email o usan un código de recuperación de 2FA, justo la ventana que usan los phishings para publicar antes de que el mantenedor reaccione.

Y si gestionas una organización, echa un ojo al firewall de red para Actions (technical preview): registra todo el tráfico saliente de tus workflows, que es exactamente donde se ve una exfiltración.

La checklist, resumida

  • Migrar la publicación a trusted publishing (OIDC) y prohibir tokens en el paquete.
  • Activar staged publishing si el paquete es crítico.
  • Actualizar a npm 11.16+, revisar warnings y generar la allowlist de scripts con npm approve-scripts.
  • Revisar workflows con pull_request_target; aplicar políticas de ejecución y permissions: mínimos.
  • Configurar cooldown en dependabot.yml si el default de 3 días no te encaja.
  • Guardar los enlaces de revocación de credenciales en tu runbook de incidentes.

Nada de esto cuesta dinero ni exige herramientas nuevas: es configuración que ya está disponible en tu cuenta. La diferencia entre salir en las noticias como víctima o ni enterarte de que hubo un intento es, literalmente, un rato de configuración. El próximo ataque a la cadena de suministro no va a esperar a que lo programes en el sprint, así que yo empezaría por el punto 1 esta misma semana. Si quieres reforzar otras capas de seguridad, también te interesa cómo bloquear bots de IA en Cloudflare sin perder SEO y proteger aplicaciones web gratis con SafeLine WAF.

Preguntas frecuentes

¿Trusted publishing funciona con runners self-hosted?

No. Por ahora solo con runners hospedados por GitHub, runners compartidos de GitLab.com y CircleCI cloud, según la documentación de npm. El soporte para self-hosted está previsto pero sin fecha.

¿npm v12 romperá mis builds?

Si tus dependencias usan scripts de instalación (esbuild, sharp, bcrypt y otros paquetes con binarios nativos), sí: tendrás que aprobarlos con npm approve-scripts. Por eso conviene actualizar a 11.16+ ya, leer los warnings y commitear la allowlist antes de dar el salto a la v12.

¿El cooldown de Dependabot retrasa los parches de seguridad?

No. El cooldown de tres días aplica solo a actualizaciones de versión. Las actualizaciones de seguridad se abren inmediatamente, igual que antes.


¿Qué te ha parecido?

Déjame tu opinión, pregunta o sugerencia. Los comentarios se sincronizan con GitHub Discussions .

Volver al blog