Los secretos borrados en una capa posterior de Docker siguen vivos en el historial
Borraste el .env de tu imagen de Docker. Sigue ahí adentro.
Una imagen de Docker no es un solo sistema de archivos: es una pila de capas de solo lectura, una por instrucción de construcción. Cada RUN, COPY y ADD agrega una capa nueva sobre la anterior. Cuando una capa posterior "borra" un archivo, solo escribe una marca de ocultamiento (whiteout) que lo esconde en la vista combinada. Los bytes de la capa anterior nunca se tocan. El secreto sigue ahí, a un comando de distancia de cualquiera que tenga la imagen.
Por qué "borrar" no elimina
Las capas son inmutables y direccionadas por contenido. Este Dockerfile parece seguro: copia un archivo de credenciales, lo usa y luego lo elimina:
COPY .env /app/.env
RUN some-tool --config /app/.env && rm /app/.env
El rm corre en la misma capa, así que la imagen combinada no muestra ningún .env. Pero si el secreto alguna vez se escribió en su propia capa (un COPY .env, un valor de ARG/ENV, o un archivo creado antes y borrado después), esa capa todavía lo contiene. Un whiteout en una capa más nueva lo esconde; no lo elimina.
ENV y el ARG de construcción son peores: sus valores se guardan en los metadatos de la imagen, legibles directamente desde docker history o docker inspect, sin necesidad de descomprimir nada.
Extraerlo toma un solo comando
No necesitas herramientas especiales. Guarda la imagen en un tarball y mira dentro:
docker save myimage:latest -o image.tar
tar -xf image.tar # cada blob es el sistema de archivos de una capa
docker export (de un contenedor en ejecución) muestra la vista combinada, así que los archivos borrados parecen haber desaparecido. docker save exporta cada capa por separado, así que los archivos ocultos por whiteout reaparecen. Herramientas como dive muestran lo mismo de forma interactiva. Considera comprometida cualquier imagen que alguna vez haya tocado un secreto.
La solución: nunca grabes secretos en una capa
| Antipatrón | Por qué se filtra | Usa en su lugar |
|---|---|---|
COPY .env y luego rm | la capa anterior conserva el archivo | RUN --mount=type=secret |
ARG TOKEN=... / ENV TOKEN=... | queda en los metadatos de la imagen | build secret mount |
descargar clave, borrar en un RUN posterior | la capa anterior conserva la clave | build secret o inyección en runtime |
Los build secrets de BuildKit montan el valor dentro del contenedor de construcción solo durante esa única instrucción; nunca queda en una capa ni en los metadatos:
docker build --secret id=aws,src=$HOME/.aws/credentials .
RUN --mount=type=secret,id=aws \
AWS_SHARED_CREDENTIALS_FILE=/run/secrets/aws \
aws s3 cp ...
Para secretos que tu aplicación necesita en tiempo de ejecución (no de construcción), no los pongas en la imagen: inyéctalos en docker run mediante un gestor de secretos o un secret del orquestador, para que la imagen quede limpia.
Movidas avanzadas
- Monta un secreto como variable de entorno, acotado a un paso:
RUN --mount=type=secret,id=aws-key-id,env=AWS_ACCESS_KEY_ID ...te da la ergonomía deENVsin persistir el valor. - Audita antes de publicar:
docker history --no-trunc <image>revela cada instrucción y cualquier secreto grabado enENV/ARG. Córrelo en CI como control. - ¿Ya publicaste un secreto grabado? Reconstruir no ayuda: la imagen vieja ya está afuera. Rota la credencial. Un secreto filtrado solo se arregla revocándolo.
- Aplanar capas (squash) no es una solución. Aplanar oculta el historial, pero el secreto sigue en el sistema de archivos aplanado si alguna vez se escribió en disco. Solo los secret mounts de construcción lo mantienen fuera del disco por completo.
Recursos
- Build secrets — documentación de Docker (
RUN --mount=type=secret) - Referencia de
docker history - Referencia de
docker save - Cómo los atacantes usan
docker historypara recuperar credenciales borradas — AquilaX
¿Desarrollas sistemas de IA o en la nube? Yeda AI audita y refuerza pipelines de LLM y contenedores en producción. Hablemos · Lee el blog