<!-- Canonical: https://lucioduran.com/es/blog/supply-chain-security-sigstore-slsa-sbom
Language: es
Content-SHA256: 397e08dd01dd6e65f61ff4f9673f97d77ebdc38971fceb8af34f1a0457ce082d
-->

Security

# Seguridad de la Cadena de Suministro: Sigstore, SLSA, SBOMs y Asegurando el Pipeline de Build de Punta a Punta

2025-12-318 min lectura

Lucio Durán
Engineering Manager & AI Solutions Architect

También disponible en: [English](<https://lucioduran.com/blog/supply-chain-security-sigstore-slsa-sbom>)

## El Modelo de Amenazas Que Nadie Dibuja

La mayoría de los equipos piensan en ataques a la cadena de suministro como "alguien envenena una dependencia." Ese es un vector, pero la superficie de ataque completa es mucho más amplia:

```
Mapa de Superficie de Ataque:
┌─────────────────────────────────────────────────┐
│ 1. Código Fuente │
│ - Cuenta de developer comprometida │
│ - PR malicioso desde fork (typosquatting CI) │
│ - Commits sin firmar (impersonación) │
│ │
│ 2. Dependencias │
│ - Paquetes typosquatting │
│ - Cuenta de maintainer comprometida │
│ - Confusión de dependencias (privado/público)│
│ - Scripts postinstall maliciose es │
│ │
│ 3. Pipeline de Build │
│ - Archivos de CI workflow modificados │
│ - Entorno de build comprometido │
│ - Pase es de build inyectados │
│ - Secrets exfiltrados durante el build │
│ │
│ 4. Almacenamiento de Artefactos │
│ - Artefactos reemplazados en el registry │
│ - Credenciales del registry comprometidas │
│ - Man-in-the-middle en descarga de artefactos│
│ │
│ 5. Deployment │
│ - Scripts de deploy modificados │
│ - Credenciales de deploy comprometidas │
│ - Inyección de dependencias en runtime │
└─────────────────────────────────────────────────┘
```

SLSA, Sigstore, y SBOMs cada uno aborda diferentes partes de esta superficie. Las siguientes secciones recorren cada uno con detalles de implementación reales.

## Sigstore: Firma Keyless Que Realmente Funciona

El modelo viejo de firma de código requería manejar claves de larga vida, lo cual creaba sus propios problemas de seguridad (almacenamiento, rotación, revocación de claves). [Sigstore](<https://www.sigstore.dev/>) reemplaza esto con **claves efímeras atadas a identidad**, usando los mismos tokens OIDC que tu CI ya tiene.

Así funciona la firma keyless por debajo:

```bash
# 1. Instalar cosign
go install github.com/sigstore/cosign/v2/cmd/cosign@latest

# 2. Firmar una imagen de container (keyless, en CI)
# El flag --yes salta la confirmación interactiva
cosign sign --yes ghcr.io/myorg/myapp:v1.2.3

# Qué pasa detrás de escena:
# a) cosign pide un token OIDC al proveedor de CI
# (GitHub Actions: ACTIONS_ID_TOKEN_REQUEST_URL)
# b) Manda el token OIDC al CA Fulcio de Sigstore
# c) Fulcio verifica el token, emite un certificado de vida corta
# (vinculado a la identidad del workflow, válido ~10 minutos)
# d) cosign genera un par de claves efímero
# e) Firma el digest de la imagen con la clave privada efímera
# f) Registra la firma + certificado en el log de transparencia Rekor
# g) Descarta la clave privada (nunca se almacena en ningún lado)
```

La verificación es donde se pone poderoso:

```bash
# Verificar una imagen firmada, chequeando claims de identidad
cosign verify \
 --certificate-identity "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/tags/v1.2.3" \
 --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
 ghcr.io/myorg/myapp:v1.2.3

# Esto chequea:
# 1. La firma es válida
# 2. El certificado de firma fue emitido por Fulcio
# 3. La identidad del certificado matchea el workflow esperado
# 4. El evento de firma existe en el log de transparencia Rekor
# 5. El certificado era válido al momento de la firma
```

Este es un workflow de GitHub Actions para firmar imágenes de container:

```yaml
name: Build and Sign
on:
 push:
 tags: ['v*']

permissions:
 contents: read
 packages: write
 id-token: write # Requerido para firma keyless

jobs:
 build-sign:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4

 - uses: sigstore/cosign-installer@v3

 - name: Login a GHCR
 uses: docker/login-action@v3
 with:
 registry: ghcr.io
 username: ${{ github.actor }}
 password: ${{ secrets.GITHUB_TOKEN }}

 - name: Build y push
 id: build
 uses: docker/build-push-action@v5
 with:
 push: true
 tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}

 - name: Firmar imagen
 run: |
 cosign sign --yes \
 ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

 - name: Adjuntar SBOM
 run: |
 syft ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }} \
 -o cyclonedx-json > sbom.cdx.json
 cosign attach sbom \
 --sbom sbom.cdx.json \
 ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
```

## SLSA: Provenance Que Significa Algo

SLSA (se pronuncia "salsa") te da un framework para pensar sobre integridad de builds. El concepto clave es **provenance**, un registro verificable de cómo se construyó un artefacto, incluyendo:

- Qué código fuente se usó
- Qué sistema de build lo produjo
- Cuáles fueron los pase es de build
- Cuáles fueron los inputs y outputs

El generador SLSA para GitHub Actions lo hace casi llave en mano:

```yaml
# .github/workflows/slsa-build.yml
name: SLSA Build
on:
 push:
 tags: ['v*']

permissions:
 contents: read
 id-token: write
 actions: read

jobs:
 build:
 runs-on: ubuntu-latest
 outputs:
 digest: ${{ steps.hash.outputs.digest }}
 steps:
 - uses: actions/checkout@v4

 - name: Build
 run: npm ci && npm run build

 - name: Empaquetar
 run: tar -czf dist.tar.gz dist/

 - name: Generar hash
 id: hash
 run: |
 DIGEST=$(sha256sum dist.tar.gz | cut -d' ' -f1)
 echo "digest=sha256:${DIGEST}" >> "$GITHUB_OUTPUT"

 - uses: actions/upload-artifact@v4
 with:
 name: build-artifact
 path: dist.tar.gz

 provenance:
 needs: build
 permissions:
 actions: read
 id-token: write
 contents: write
 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
 with:
 base64-subjects: |
 ${{ needs.build.outputs.digest }}
```

El documento de provenance se ve así (simplificado):

```json
{
 "_type": "https://in-toto.io/Statement/v1",
 "subject": [{
 "name": "dist.tar.gz",
 "digest": { "sha256": "a1b2c3d4..." }
 }],
 "predicateType": "https://slsa.dev/provenance/v1",
 "predicate": {
 "buildDefinition": {
 "buildType": "https://github.com/slsa-framework/slsa-github-generator/generic@v1",
 "externalParameters": {
 "source": {
 "uri": "git+https://github.com/myorg/myapp@refs/tags/v1.2.3",
 "digest": { "sha1": "abc123..." }
 }
 }
 },
 "runDetails": {
 "builder": {
 "id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v1.10.0"
 },
 "metadata": {
 "invocationId": "https://github.com/myorg/myapp/actions/runs/12345678"
 }
 }
 }
}
```

## SBOMs: Sabé Qué Estás Despachando

Un SBOM (Software Bill of Materials) es un inventario de cada componente en tu software. Hay dos formatos dominantes: CycloneDX y SPDX. CycloneDX tiende a ser la opción preferida porque su tooling es más maduro para dependencias de aplicaciones.

```bash
# Generar SBOM desde una imagen de container
syft ghcr.io/myorg/myapp:v1.2.3 -o cyclonedx-json > sbom.cdx.json

# Generar SBOM desde source (proyecto npm)
cdxgen -t node -o sbom.cdx.json .

# Generar SBOM incluyendo paquetes del SO en container
syft ghcr.io/myorg/myapp:v1.2.3 \
 --scope all-layers \
 -o cyclonedx-json > sbom-full.cdx.json
```

La diferencia entre un lockfile y un SBOM es importante. En un proyecto representativo de Node.js:

```
package-lock.json: 847 dependencias
SBOM (solo source): 847 dependencias (igual al lockfile)
SBOM (container): 1.243 dependencias (incluye libc, openssl,
 zlib, libcurl, ca-certificates,
 runtime de node, etc.)
```

Esas 396 dependencias extra son superficie de ataque real. Cuando sale una vulnerabilidad en ` libwebp ` o ` openssl `, tu lockfile no te dice nada, pero tu SBOM de container sí.

## npm Provenance: Un Flag Para Gobernarlos a Todos

Desde npm 9.5, es posible publicar paquetes con provenance criptográfico en un solo paso:

```bash
npm publish --provenance
```

Eso es todo. Detrás de escena, esto:

1. Genera un predicado de provenance SLSA vinculando el paquete al run de GitHub Actions
2. Lo firma con firma keyless de Sigstore
3. Sube el provenance firmado a npm junto con el paquete
4. npm muestra un badge "published with provenance"

Tu ` package.json ` no necesita nada especial. El único requisito es publicar desde un workflow de GitHub Actions con permiso ` id-token: write `.

```yaml
# .github/workflows/publish.yml
name: Publish
on:
 release:
 types: [published]

permissions:
 contents: read
 id-token: write

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
 env:
 NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

Los consumidores pueden verificar provenance:

```bash
npm audit signatures
# Chequea que todos los paquetes instalados tengan firmas Sigstore válidas
```

## Lo Que Realmente Importa: Priorización Pragmática

Basado en experiencia de implementación en múltiples organizaciones, este es un ranking pragmático de qué entrega más valor de seguridad por hora de esfuerzo de ingeniería:

1. **npm provenance** (30 min de setup, valor masivo), Un flag, attestation criptográfica. Hacer esto primero.
2. **Firma de imágenes de container con cosign** (2 horas), Firma keyless en CI, verificación en deploy.
3. **Generación y escaneo de SBOMs** (4 horas), Conocer las dependencias, atrapá vulns en todo el stack.
4. **SLSA Level 2 provenance** (1 día), Provenance autenticado desde un servicio de build hosted.
5. **SLSA Level 3** (1-2 semanas), Entornos de build endurecidos, aislados. Acá es donde se pone caro en tiempo de ingeniería.
6. **Builds reproducibles** (esfuerzo continuo), El santo grial. Prácticamente difícil para la mayoría de los proyectos.

En un caso documentado, después de implementar los items 1-4, el tiempo de detección para un compromiso similar del pipeline bajó de 11 horas a unos 4 minutos, porque el mismatch de identidad de firma se atraparía en el momento del deploy.

La seguridad de la cadena de suministro no se trata de alcanzar algún nivel SLSA perfecto. Se trata de hacer caro atacar un pipeline y barato detectar cuando alguien lo intenta. Comenzar con la fruta al alcance de la mano, medir la brecha de detección, y subir desde ahí.

## Preguntas frecuentes

### ¿Qué es Sigstore y cómo funciona la firma keyless?

Sigstore es un proyecto open-source que provee infraestructura gratuita de firma de código. La firma keyless funciona usando certificados de vida corta vinculados a una identidad OIDC (como la identidad de tu workflow de GitHub Actions) en vez de claves de firma de larga vida. La firma, el certificado y el evento de firma se registran en un log de transparencia (Rekor) para auditabilidad pública.

### ¿Cuáles son los niveles SLSA y qué requieren?

SLSA (Supply-chain Levels for Software Artifacts) define cuatro niveles de integridad de build creciente. Level 1 requiere documentación de provenance del build. Level 2 requiere un servicio de build hosted y provenance autenticado. Level 3 requiere entornos de build endurecidos con provenance no falsificable. Level 4 requiere builds herméticos y reproducibles con revisión de dos partes.

### ¿Por qué necesito un SBOM si ya tengo un lockfile?

Los lockfiles solo capturan dependencias directas y transitivas manejadas por tu package manager. Los SBOMs capturan el grafo completo de dependencias incluyendo librerías nativas, paquetes del SO, herramientas de build, y dependencias de runtime que los lockfiles no ven. También son machine-readable en formatos estándar (CycloneDX, SPDX) que escáneres de vulnerabilidades y herramientas de compliance pueden consumir.

## Tips

1. **Tip:** Comenzar con ` npm publish --provenance ` hoy. Es literalmente un flag y les da a los consumidores un link criptográfico entre el paquete publicado y el run exacto de GitHub Actions que lo construyó. Cero excusa para no hacerlo.
2. **Tip:** SLSA Level 3 requiere un entorno de build aislado que el developer no pueda influenciar. Si un pipeline de CI deja que los desarrolladores modifiquen el script de build vía PR *y* el build corre en el mismo contexto del PR, eso no es Level 3, es cosplay de Level 3.
3. **Tip:** Generar SBOMs en build time, no como algo tardío. Ejecutar ` syft ` o ` cdxgen ` contra la imagen de container atrapa dependencias que el lockfile no ve, librerías nativas, paquetes del SO, todo el runtime.
4. **Tip:** La firma keyless de Sigstore con ` cosign sign --yes ` usa tokens de identidad OIDC del proveedor de CI. Esto significa que la identidad de firma es el workflow de CI, no una key de larga vida en un secret. El log de transparencia Rekor lo hace auditable para siempre.

- supply-chain
- sigstore
- cosign
- slsa
- sbom
- npm-provenance
- seguridad
- ci-cd
- pipeline-build

[Volver al Blog](<https://lucioduran.com/es/blog>)
