Guía10 min

SBOM y attestations Docker para un agente IA: qué hay dentro, cómo se construyó

Resumen

BuildKit adjunta provenance mode=min por defecto. El SBOM no va solo: --sbom=true, SPDX in-toto y Syft. Escanea el stage final salvo ARG BUILDKIT_SBOM_SCAN_STAGE. mode=max filtra ARG; secretos van en mounts. Docs Docker attestations, SBOM y GitHub Actions, 7 sep 2026.

DockerGitHub
Tres columnas planas etiquetadas spdx, syft y provenance

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

Un agente IA empaquetado en Docker no es solo un binario Node. Es una imagen con runtime, lockfile, transitives y, a menudo, un stage de build que instala compiladores que no aparecen en el FROM scratch final. Las build attestations de BuildKit responden dos preguntas que un pin de digest no cubre: qué hay dentro (SBOM) y cómo se construyó (provenance). Docs oficiales Docker consultadas el 7 de septiembre de 2026.

BuildKit genera las attestations en el build y las envuelve en el formato in-toto JSON. Quedan pegadas al image index, no a una capa suelta. Por eso el store clásico de imágenes de Docker no las aguanta: o activas el containerd image store, o cambias de driver (docker-container, kubernetes, remote). docker buildx build --push conserva las attestations en el registry. --load al daemon clásico las pierde o falla.

Esta guía es la pieza de cadena de suministro del flujo que ya cubrimos en Docker para el agente, multi-stage y pin de digest. El pin dice cuál imagen; el SBOM dice qué paquetes hay dentro.

Qué genera BuildKit y qué no

Hay dos tipos:

TipoPreguntaDefault
Provenance¿Cómo se construyó? timestamps, frontend, materiales, repo, revisión, plataformamode=min se añade
SBOM¿Qué artefactos contiene (o usó el build)? nombre, versión, licencia, PURLno se añade hasta --sbom=true

Personalizas el comportamiento:

docker buildx build --sbom=true .
docker buildx build --provenance=mode=max .
docker buildx build --provenance=false .

--attest type=sbom equivale a --sbom=true. --attest type=provenance,mode=max,version=v1 equivale a --provenance=mode=max,version=v1. Para apagar el default: BUILDX_NO_DEFAULT_ATTESTATIONS=1.

El schema de provenance es SLSA v0.2 por defecto; version=v1 pide SLSA Provenance v1. El SBOM de BuildKit sigue SPDX y se adjunta como predicado in-toto SPDX. El generador default es el plugin Syft de BuildKit (Anchore). Puedes cambiarlo con --attest type=sbom,generator=<imagen> si implementa el protocolo de scanner de BuildKit.

Provenance: min es seguro, max filtra ARG

mode=min incluye timestamps, frontend, materiales, repositorio y revisión, plataforma y si el build se declara reproducible. No incluye valores de build arguments, identidades de secrets ni metadata rica de capas. Docker lo considera seguro para todos los builds.

mode=max añade la definición LLB (pasos exactos), el Dockerfile completo en base64 y source maps capa↔instrucción. Docker recomienda max cuando puedes. La advertencia dura: mode=max expone los valores de ARG. Si pasas tokens o credenciales como build args, salen en la attestation. Refactoriza a secret mounts; los mounts de secretos nunca entran en provenance.

SBOM: el stage final miente si tu runtime es scratch

Por defecto BuildKit solo escanea el stage final. Un Dockerfile típico de agente —stage build con pnpm install y compiladores, stage runner FROM scratch o node:22-alpine con COPY --from=build— produce un SBOM del runtime, no de las tools de build. Una CVE en el compilador o en un paquete de apk add del stage de build no aparece.

Los ARG especiales BUILDKIT_SBOM_SCAN_CONTEXT y BUILDKIT_SBOM_SCAN_STAGE amplían el alcance. Son valores especiales: no hay substitución de variables ni env; solo un ARG explícito en el Dockerfile. Pasar --build-arg en la CLI sin el ARG en el Dockerfile no hace nada.

# syntax=docker/dockerfile:1
FROM node:22-alpine AS build
ARG BUILDKIT_SBOM_SCAN_STAGE=true
WORKDIR /src
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=build /src/dist ./dist
USER node
CMD ["node", "dist/index.js"]

Valores de BUILDKIT_SBOM_SCAN_STAGE: true (este stage), false (no), o lista base,bin (nombres de stages). Solo se escanean stages que sí se construyen: un stage que no es dependencia del target no se buildea ni se escanea. BUILDKIT_SBOM_SCAN_CONTEXT=true (global, antes del primer FROM) incluye el contexto de build.

Valida antes de pushear. El exporter local escribe JSON en disco:

docker buildx build --sbom=true --output type=local,dest=out .
ls -1 ./out | grep sbom
# sbom.spdx.json
# sbom-build.spdx.json   # si escaneaste el stage build

Flujo de stages: build con scanner encendido, runner final y SBOM SPDX adjunto

Inspeccionar sin adivinar

Tras --push, lee el SBOM del índice:

docker buildx imagetools inspect org/agente:1.2.3 \
  --format "{{ json .SBOM.SPDX }}"

Multi-plataforma: --format '{{ json (index .SBOM "linux/amd64").SPDX }}'. Lista de paquetes:

docker buildx imagetools inspect org/agente:1.2.3 \
  --format "{{ range .SBOM.SPDX.packages }}{{ .name }}@{{ .versionInfo }}{{ println }}{{ end }}"

Un SBOM no sustituye el pin @sha256: el digest fija el blob; el SPDX te dice si undici o musl están en esa capa. Tampoco sustituye usuario no-root: el inventario no cambia el uid 0.

GitHub Actions: público = max, privado = min, load = cero

docker/build-push-action v4+ añade provenance solo:

  • repo públicomode=max automático
  • repo privadomode=min automático
  • load: true o exporter dockercero attestations (el store local del runner no las soporta)

En un repo público, mode=max automático publica valores de ARG. Misma regla: secretos en mounts, no en build-args. Fuerza max en privado y el SBOM (que nunca va solo) con push: true:

- uses: docker/setup-buildx-action@v4
- uses: docker/build-push-action@v7
  with:
    push: true
    sbom: true
    provenance: mode=max
    tags: ${{ steps.meta.outputs.tags }}

Sin push: true no hay attestations que sobrevivan. Encaja con el pipeline de CI/CD del agente: el job de imagen publica digest + SPDX, no un tar al artifact del runner.

Verificación: inspect del índice, lista SPDX y fallo si falta el predicado

Checklist antes del push

  1. Driver que soporta índices (docker-container o containerd image store). Si ves Attestation is not supported for the docker driver, cambia eso, no apagues attestations.
  2. --sbom=true (o sbom: true en Actions). Provenance min ya viene; el SBOM no.
  3. ARG BUILDKIT_SBOM_SCAN_STAGE=true en el stage de build si el runner es scratch/alpine mínimo.
  4. Cero secretos en ARG. mode=max y repos públicos los imprimen.
  5. Valida con exporter local (sbom.spdx.json) antes de --push.
  6. Tras el push: imagetools inspect --format "{{ json .SBOM.SPDX }}". Vacío = no se adjuntó.
  7. Pin el digest publicado; el SBOM describe ese digest, no el tag :latest.

FAQ

¿El SBOM reemplaza un scanner de CVE en el registry? No. El SBOM es el inventario. El scanner (Scout, Grype, el del registry) consume ese inventario. Sin SBOM, el scanner solo ve el filesystem final.

¿Puedo generar el SBOM después, con syft sobre la imagen ya pusheada? Sí, pero pierdes las dependencias de build que no quedaron en el runner. Indexar durante el build es el punto de BuildKit.

¿--provenance=false es más seguro? Solo si no quieres metadata. No oculta secretos mal pasados como ARG en un build previo ya publicado. Rota esos secretos.

¿Multi-arch? Cada plataforma tiene su SBOM bajo el índice. Inspecciona con index .SBOM "linux/amd64", no asumas que amd64 = arm64.

Siguiente paso

Si tu agente aún vive en un Dockerfile plano, empieza por multi-stage y no-root. Luego adjunta SBOM + provenance max (sin ARG secretos) y publica el digest. El curso libre cubre el loop mínimo en /curso/instalar-agente; el hub de seguridad, coste y operación agrupa el resto de gates de producción.