Principales conclusiones
- La procedencia de npm demuestra qué repositorio, qué commit y qué flujo de trabajo han generado un paquete, pero no garantiza que el código sea seguro para su ejecución.
- La brecha de certificación es la diferencia entre lo que demuestra la procedencia y lo que realmente necesitas saber antes de instalar algo.
- Un flujo de trabajo secuestrado o una confirmación maliciosa siguen generando una procedencia válida, y la mayoría de los paquetes de npm no incluyen ninguna procedencia.
- La recompilación de paquetes a partir de código fuente verificado en un entorno reforzado aleja la raíz de confianza del proceso de desarrollo del editor.
Si has echado un vistazo a npmjs.com últimamente, probablemente te habrás dado cuenta. Cerca de la parte inferior de muchas páginas de paquetes, ahora hay una sección de procedencia que indica que el paquete se ha compilado y firmado en GitHub Actions, con un enlace directo al commit del que procede. Parece un sello. Da la sensación de que el problema está resuelto.
Muchos equipos consideran que ese sello es la meta y, sinceramente, lo entiendo. Provenance supuso un auténtico paso adelante para el ecosistema. Pero responde a una pregunta mucho más limitada de lo que la mayoría de la gente cree, y el espacio entre la pregunta a la que responde y la que realmente te importa es precisamente donde se han producido muchos de los recientes ataques a npm.
Qué demuestra realmente «npm provenance»
npm introdujo la procedencia en abril de 2023, y la llegada de la publicación de confianza en julio de 2025 hizo que el proceso fuera prácticamente automático. Cuando un mantenedor publica desde GitHub Actions mediante la publicación de confianza, la CLI de npm genera un certificado de procedencia de forma predeterminada, sin necesidad de ningún parámetro. Ese certificado es una declaración SLSA firmada, registrada en el registro público de transparencia de Sigstore, que vincula el archivo tarball a un repositorio de código fuente, a una confirmación específica y al flujo de trabajo que lo compiló. Si te encuentras en el lado del consumidor, el comando «npm audit signatures» comprueba esos certificados en todo tu árbol de instalación.
Es bueno conocer estos datos. Antes de la implementación de «provenance», el enlace al repositorio que aparecía en la página de un paquete era el que el responsable introducía en package.json. No había nada que lo verificara ni que lo vinculara a un commit. Y como npm publish empaqueta todo lo que se encuentre en el directorio de trabajo del editor, un archivo tar podría contener resultados de compilación, archivos sueltos o algo mucho peor que nunca haya existido en Git. Para los paquetes que lo utilizan, la trazabilidad de origen cierra esa brecha. Sabes que el archivo tar procede de una ejecución concreta de CI basada en un commit concreto.
Lo que no sabes es si algo de lo que ocurrió en esa carrera merecía tu confianza.
La falta de certificación
Es útil ponerle un nombre a esto, así que lo llamaré «brecha de certificación». La brecha de certificación es la diferencia entre lo que demuestra una certificación de procedencia (qué repositorio, commit y flujo de trabajo de compilación generaron un paquete) y lo que realmente necesitas saber antes de instalarlo, es decir, si ese artefacto es seguro y se ha compilado fielmente a partir de un código fuente que te darían confianza para ejecutarlo.
La procedencia es una afirmación sobre el origen. La seguridad es una afirmación sobre el contenido. Un certificado puede ser criptográficamente perfecto y, aun así, no decir nada sobre lo que hace el código cuando llega a tu node_modules carpeta. Si un atacante consigue introducir su código en la confirmación o en la compilación, obtiene una certificación válida sin ningún coste.
Cómo un paquete supera el control de procedencia y aun así te quema
La vía más sencilla es un responsable de mantenimiento comprometido. Si alguien se hace con el control de una cuenta de GitHub con acceso de «push» a un proyecto, no necesita tocar npm en absoluto. Realiza un «push» de una confirmación, el flujo de trabajo de lanzamiento se ejecuta exactamente como estaba previsto y la nueva versión se publica con una procedencia que apunta fielmente a la confirmación del atacante. Se superan todas las comprobaciones, ya que no hay nada inusual en la publicación en sí. El problema reside en la confirmación, y la procedencia nunca se diseñó para evaluar confirmaciones.
La segunda vía pasa por el propio proceso de compilación. La publicación de confianza sustituye los tokens de larga duración por otros de corta duración generados por el flujo de trabajo de CI, lo cual supone una gran mejora. Además, esto significa que la identidad del flujo de trabajo se convierte en la credencial. El análisis de Andrew Nesbitt sobre las vulnerabilidades de GitHub Actions en los paquetes de Python reveló casos en los que el atacante acababa en poder de un token válido generado por el flujo de trabajo real del proyecto, la única situación en la que la publicación de confianza no puede hacer nada. El ecosistema es diferente, pero la mecánica se traslada directamente a npm.
La tercera vía es la que la gente suele pasar por alto. Tu compilación de lanzamiento incorpora sus propias dependencias —como herramientas de empaquetado, transpiladores y complementos—, todas ellas resueltas desde cero en el servidor de integración continua. Si alguna de ellas se ve comprometida cuando se ejecuta la tarea de lanzamiento, su carga maliciosa puede acabar en tu dist carpeta y enviarla dentro de un paquete cuya procedencia sea irreprochable. El certificado indica tu commit. No avala los cientos de paquetes que tu proceso de compilación instaló durante la creación de ese archivo tar.
La falta de procedencia es una señal que nadie tiene en cuenta
Lo que viene a continuación me preocupa aún más. La procedencia es opcional, y la mayor parte del registro no se ha adherido a ella. Ni siquiera está disponible para los paquetes compilados a partir de repositorios de código fuente privados. Y npm install instalará sin problemas una versión sin certificado, incluso aunque todas las versiones anteriores de ese paquete lo tuvieran.
Ahora piensa en cómo funcionan las grandes campañas de gusanos. Shai-Hulud y las oleadas que le siguieron se centraron en los tokens de npm y las credenciales de GitHub para poder volver a publicar paquetes legítimos de forma automática. Cualquiera que tenga un token robado puede publicar desde cualquier lugar, lo que normalmente implica que no se ejecuta la integración continua (CI) ni hay certificación de publicación. En teoría, eso es una señal de alarma. Cuando PyPI elaboró su análisis del ataque a Ultralytics, señaló que la segunda oleada de publicaciones maliciosas destacaba porque no tenía actividad correspondiente en el repositorio ni certificaciones de publicación.
Eso funciona si alguien está buscando el «drop». En la mayoría de las configuraciones de npm, nadie lo busca. Un paquete que de repente pierde su procedencia se ve exactamente igual que los miles que nunca la tuvieron, y la instalación se lleva a cabo sin ningún problema.
La reconstrucción a partir del código fuente subsana otra carencia
Si la procedencia te indica dónde se ha compilado algo, la siguiente pregunta obvia es si puedes compilarlo tú mismo y obtener el mismo resultado. Esa es la idea que subyace a una recompilación independiente. En lugar de confiar en el archivo tar que ha subido el editor, tomas el código fuente del que dice proceder, lo compilas de nuevo en un entorno que controlas y consideras que el resultado obtenido es lo que se instala. El proceso de compilación del editor deja de ser la base de la confianza.
Ese es el enfoque que hemos adoptado con SecureChain. Los paquetes se vuelven a compilar a partir de código fuente verificado en entornos reforzados y aislados, sin binarios heredados ni artefactos opacos de los desarrolladores originales. Además, cada uno de ellos se analiza en busca de malware conocido y patrones de código sospechosos, y todo lo que no supere la inspección se bloquea antes de que llegue al portátil de un desarrollador o a un ejecutor de CI. Funciona como un sustituto directo del registro público de npm, por lo que npm, Yarn y pnpm se comportan tal y como espera tu equipo, y puede situarse en la fase previa de Artifactory, Nexus o GitHub Packages si así es como ya gestionas los paquetes.
SecureChain también proporciona información de procedencia SLSA de nivel 3, lo que podría parecer que estoy contradiciendo mi propio argumento. Pero no es así. La procedencia solo tiene sentido en la medida en que lo tenga la compilación a la que hace referencia. La procedencia de la cadena de suministro indica que el sistema de integración continua (CI) del mantenedor ha generado un paquete, y un sistema de integración continua comprometido puede generar paquetes sin ningún problema. La procedencia asociada a una recompilación independiente indica que el artefacto procede de un entorno de compilación reforzado, a partir de código fuente que se comprobó antes de su incorporación. Se trata del mismo formato SLSA en ambos casos, pero avala una compilación completamente diferente.
Lo que una reconstrucción no puede hacer por ti
La recompilación tiene sus límites, y conviene ser claro al respecto. Si el código malicioso se encuentra en el propio código fuente, una recompilación fiel lo reproducirá tal cual. Por eso, la recompilación y la detección de malware deben ir de la mano. Una comprueba si el artefacto coincide con su código fuente, y la otra comprueba si el código fuente es algo que quieres que se ejecute en tu equipo. Ninguna de las dos preguntas por sí sola resuelve el problema.
También hay un problema que afecta a todo el ecosistema. No todos los paquetes de npm se compilan correctamente a partir de lo que hay en su repositorio. Algunos incluyen archivos que nunca se han sometido a control de versiones, otros dependen de configuraciones de compilación que nadie ha documentado jamás y unos pocos ni siquiera tienen código fuente público. Eso supone un problema para cualquiera que intente verificar el registro a gran escala, y es un argumento válido para exigir más a los editores, no menos.
Por dónde empezaría
Lo más barato para empezar es ejecutar «npm audit signatures» en la integración continua (CI), para que al menos puedas ver cuáles de tus dependencias están certificadas y cuáles no. Si publicas paquetes por tu cuenta, cambia a la publicación de confianza para que tus propias versiones incluyan la procedencia. Te cuesta muy poco y hace que el ecosistema sea un poco más fácil de entender.
A continuación, mantén una conversación más profunda con tu equipo sobre dónde recae realmente tu confianza. En este momento, en la mayoría de los proyectos de JavaScript, recae en los procesos de integración continua (CI) y las cuentas de npm de miles de desconocidos, y «provenance» simplemente te indica cuáles son. Trasladar esa confianza a una fuente que tú controlas, una que recompila, revisa y sigue aplicando parches a los paquetes mucho después de que sus mantenedores hayan dejado el proyecto, supone una posición mucho más sólida. Esa última parte es donde entra en juego el «Endless Lifecycle Support» de TuxCare una vez que un paquete llega al final de su vida útil.
La información sobre la procedencia es útil, y me gustaría que se utilizara mucho más en el registro. Simplemente indica de dónde procede un paquete, y lo que garantiza la seguridad de los secretos de tu integración continua es lo que hay dentro de él.
