Provenance desde CI: procedencia firmada y SLSA
La procedencia firmada como respuesta criptográfica a los ataques a la cadena de suministro: cómo npm publish --provenance ata un tarball a su commit y su workflow con Sigstore keyless, OIDC, Fulcio y el log de transparencia Rekor sin claves de larga vida, qué garantizan los niveles del marco SLSA, cómo el trusted publishing sin tokens cierra la otra puerta del atacante, y cómo verificar firmas y atestaciones del lado del consumidor con npm audit signatures.
Cuando instalas un paquete, ¿cómo sabes que el tarball del registro se construyó de verdad a partir del código fuente que ves en el repositorio, y no lo inyectó un atacante que robó la cuenta del mantenedor? Durante décadas la respuesta fue “confía en la persona”. La procedencia firmada cambia la pregunta por otra, verificable: “confía en la prueba criptográfica de que este artefacto salió de este commit, en este workflow”. Es el eslabón que blinda la cadena de suministro, y en 2026 dejó de ser una buena práctica opcional para volverse el estándar del oficio de publicar.
- Situar los ataques a la cadena de suministro y por qué confiar en cuentas es estructuralmente insuficiente.
- Entender cómo
npm publish --provenancefirma una atestación con Sigstore keyless, OIDC, Fulcio y Rekor. - Relacionar la procedencia con los niveles del marco SLSA y con el trusted publishing sin tokens.
- Verificar firmas y atestaciones del lado del consumidor con
npm audit signaturescomo puerta de CI.
El problema: confiar en personas no escala
Los ataques más rentables ya no explotan tu código, sino tus dependencias. Comprometer un paquete popular es infectar de golpe cada proyecto que lo instala, un efecto multiplicador que ningún ataque directo alcanza. Y como históricamente el tarball se subía “a mano” desde un portátil, nada ataba lo publicado a un código fuente auditable: el registro solo sabía que alguien con credenciales válidas subió unos bytes. El patrón se repite con variantes:
- Cuenta comprometida: roban el token o las credenciales del mantenedor y publican en su nombre.
- Máquina comprometida: el portátil del mantenedor inyecta código en la build local antes de subir.
- Dependencia infiltrada: una versión maliciosa de un paquete popular se cuela en miles de árboles a la vez.
- Confusión de dependencias: se publica en el registro público un paquete con el nombre de uno interno, esperando que un gestor mal configurado lo prefiera.
La procedencia ataca la raíz. En lugar de confiar en quién publica, exige una prueba verificable de cómo se construyó el artefacto: de qué commit exacto salió, en qué repositorio, con qué workflow. Si esa prueba es criptográficamente sólida y pública, un atacante que suba un tarball desde su portátil no puede fabricarla, porque no tiene ni el commit firmado ni la identidad del workflow legítimo. La confianza se desplaza de la persona al proceso: de “creo en el mantenedor” a “puedo verificar el origen”.
Cómo funciona: Sigstore keyless
El mecanismo es npm publish --provenance, ejecutado desde CI. Al publicar, npm genera una atestación de procedencia: un documento que ata el tarball a su commit de origen y a las instrucciones de build. Lo firma con Sigstore mediante una técnica llamada keyless: no hay clave privada que guardar ni rotar, y ahí reside buena parte de su seguridad. En su lugar, el runner presenta su identidad OIDC a Fulcio, que le emite un certificado efímero; con él se firma la atestación, y el acto queda anotado en Rekor, un log de transparencia público e inmutable. Sin secretos de larga vida que robar, se elimina una clase entera de ataques.
La atestación en sí no es texto libre: es un documento in-toto firmado que ata el digest exacto del tarball —su subject— a un predicado de procedencia con el repositorio, el commit, el workflow y sus parámetros de invocación. Como el digest forma parte de lo firmado, cambiar un solo byte del artefacto invalida la firma: la atestación no vale para “un paquete parecido”, vale para ese tarball y ningún otro.
OIDC
La identidad efímera del workflow de CI. Sustituye al token robable: el registro confía en quién ejecuta, no en un secreto guardado.
Fulcio
La autoridad de Sigstore que emite un certificado de vida cortísima para firmar sin custodiar claves privadas.
Rekor
El log de transparencia público donde queda anotada cada firma, para que cualquiera pueda auditarla después.
flowchart LR CI[CI con identidad OIDC] --> F[Fulcio emite certificado efimero] F --> S[firma la atestacion de procedencia] S --> R[Rekor log de transparencia] S --> N[registro npm] N --> B[insignia verificado en npmjs] style CI fill:#89b4fa,color:#11111b style S fill:#cba6f7,color:#11111b style B fill:#a6e3a1,color:#11111b
La firma keyless impone requisitos concretos, y cada uno tiene su razón:
- CI compatible: GitHub Actions o GitLab CI, capaces de emitir tokens OIDC.
- Permiso
id-token: write: sin él no hay identidad que presentar a Fulcio y la firma no ocurre. - Paquete y repositorio públicos: la procedencia solo vale si cualquiera puede contrastar el artefacto contra un código visible.
# .github/workflows/publish.yml
permissions:
id-token: write # habilita OIDC y la firma keyless de Sigstore
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
- run: npm ci
- run: npm publish --provenance --access public
El error más común al estrenar procedencia es un workflow que publica pero rechaza firmar, porque olvidaste declarar id-token: write en los permissions. Sin ese permiso, el runner no puede presentar su identidad OIDC a Fulcio y la firma no ocurre. El segundo tropiezo habitual es intentar procedencia sobre un repositorio privado: hoy la atestación exige un origen público, porque su valor entero reside en que cualquiera pueda contrastar el artefacto contra el código fuente.
SLSA, la insignia y el trusted publishing
Toda esta maquinaria encaja en un marco con nombre: SLSA, niveles de cadena de suministro para artefactos de software, que define una escalera de garantías de build ascendentes:
- Nivel 1: existe una procedencia que documenta el origen, aunque la genere el propio proceso.
- Nivel 2: la procedencia la firma un servicio de build alojado, no el publicador; deja de ser falsificable a mano.
- Nivel 3: el build corre aislado y endurecido, con garantías más fuertes contra manipulación.
La atestación de npm, generada por un CI alojado y firmada con Sigstore, cubre los niveles intermedios de esa escalera. Cuando la firma llega al registro, npmjs muestra la insignia de verificado, que enlaza a evidencia contrastable:
- El commit exacto del que salió el tarball, no una rama que pueda reescribirse después.
- El repositorio de origen y el workflow concreto que ejecutó la build.
- La entrada en Rekor que prueba, en un log inmutable, que la firma es auténtica.
# consulta los metadatos de distribucion de una version publicada
npm view @acme/ui@2.1.0 dist
El patrón de referencia en 2026 suma una pieza más: el trusted publishing. En vez de guardar un token de publicación de larga vida como secreto en CI —otro objeto robable—, el registro confía directamente en la identidad OIDC del workflow para autorizar la publicación. Configuras el repositorio como publicador de confianza en npmjs, y ya no hay token que filtrar.
La procedencia responde a “de dónde salió este tarball”; el trusted publishing responde a “quién tuvo permiso de subirlo”. Son mecanismos distintos —uno firma el artefacto, el otro sustituye el token de autenticación— pero apuntan a la misma clase de ataque. Adoptados juntos desde CI, no queda ni un secreto de larga vida que robar ni un artefacto sin origen contrastable. Es la razón por la que el ecosistema los promueve como pareja, no por separado, y por la que en 2026 los registros empujan agresivamente hacia esta combinación como norma.
Verificar del lado del consumidor
La procedencia no sirve de nada si nadie la comprueba. Del lado de quien instala, npm audit signatures recorre el árbol instalado y verifica dos cosas distintas: que cada tarball lleve la firma del registro que garantiza que no se alteró en tránsito, y que las dependencias con procedencia traigan una atestación válida que las ate a su origen. Un fallo aquí es una señal roja.
# verifica firmas del registro y atestaciones de procedencia
npm audit signatures
El informe distingue tres situaciones que conviene saber leer:
- Firma y procedencia válidas: la dependencia viene, comprobadamente, del origen que declara.
- Firma válida sin procedencia: íntegra en tránsito, pero sin prueba de build; aún lo común en buena parte del ecosistema.
- Firma inválida o ausente: la alarma que debe detener el despliegue e iniciar una investigación inmediata.
Integrar esta comprobación en CI convierte la confianza en una puerta automática: si una dependencia pierde su firma o su procedencia no valida, el pipeline se detiene antes de desplegar.
# puerta de CI: falla el pipeline si una firma o una procedencia no valida
- run: npm audit signatures
La verificación deja de ser un gesto puntual y pasa a ser una condición estructural del despliegue, comprobada en cada build sin depender de que alguien se acuerde de mirarla.
El salto conceptual de este nivel es de los más profundos del track: la seguridad de la cadena de suministro deja de ser una cuestión de reputación para volverse una cuestión de prueba. Antes, instalar un paquete era un acto de fe en que la cuenta del mantenedor no estuviera comprometida, una fe que los ataques reales han traicionado una y otra vez. La procedencia sustituye esa fe por una cadena verificable: la identidad OIDC del workflow, atestiguada por Fulcio con un certificado efímero, firmando una atestación que ata el tarball a un commit público, registrada para siempre en el log de transparencia de Rekor. Cada eslabón elimina un secreto robable o introduce una prueba contrastable. SLSA le pone nombre y niveles a esa escalera de garantías, y el trusted publishing remata la obra quitando de en medio el último objeto peligroso, el token de larga vida. Del otro lado, npm audit signatures cierra el círculo permitiendo que el consumidor no confíe, sino verifique. El resultado no es “más seguridad” en abstracto, sino un cambio de modelo mental: ya no preguntas en quién confías, sino qué puedes verificar. Cuando adoptas procedencia y publicación sin tokens desde CI, tu paquete deja de pedirle fe al mundo y empieza a ofrecerle evidencia. Ese es el estándar que el ecosistema ha decidido volver la norma, y saber operarlo es hoy parte inseparable del oficio de publicar.
- Explica con tus palabras qué clase de ataque a la cadena de suministro neutraliza la procedencia y cuál no.
- Escribe un workflow de CI con el permiso
id-token: writeque publique con--provenance --access public. - Localiza en un paquete real de npmjs la insignia de verificado y sigue el enlace al commit, al workflow y a la entrada de Rekor.
- Corre
npm audit signaturesen un proyecto y lee cuántas dependencias traen firma y cuántas además procedencia. - Investiga cómo configurar el trusted publishing sin tokens y razona qué puerta del atacante cierra que la procedencia sola no cierra.