Secrets in Image Layers
A private package-index token is baked into a public container image and survives the RUN that deletes it. Read it out of the layer history like an attacker would, then rotate the credential and rebuild so the secret never enters a layer.
What Is Secrets in Image Layers?
A secret does not leave an image when the Dockerfile deletes it. Image layers are append-only, so a credential passed into one RUN is recorded in that layer forever, and a later RUN that removes the file only adds a new layer on top. This exercise covers a private package-index token baked into a public image: watch an attacker read it straight out of the layer history and use it, then work the real fix. Rotate the credential first so the leaked value is dead, then rebuild so the secret never enters a layer at all.
What You'll Learn in Secrets in Image Layers
- Yes. Layers are append-only, so the layer that added the token still contains it and the delete only adds a new layer on top.
- Rotate the credential, because the leaked value stays valid until it is revoked no matter what you do to the image.
- It truncates the CREATED BY column, so a long RUN command is cut off before the part carrying the credential.
- A BuildKit secret mount or a multi-stage build, so the credential is available during the build but is never written into a layer that ships.
Secrets in Image Layers — Training Steps
-
Size up the target
Today Bob is targeting Cindralt, a payments company whose settlement service ships as a container image. He starts where anyone can: the company's own container registry. A published image is a convenient thing to attack. It is the exact artifact running in production, and pulling it costs nothing.
-
Anyone can pull it
The repository is published for anonymous pull. That is a deliberate choice for a lot of companies and not a vulnerability on its own, but it does mean the artifact is available to Bob on the same terms as to Cindralt's own build servers.
-
Pull the image
Bob pulls the tag that the registry lists as latest. What lands on his machine is byte for byte what Cindralt runs in production, including every layer the build produced along the way.
-
Read the build history
Every image carries the commands that built it. Bob does not need to run the container or unpack anything: the layer history is metadata, and it comes with the pull. At a glance this build looks careful. Someone even cleaned up after the dependency install.
-
Ask for the full command
The default table is built to fit a terminal, not to tell the truth. Bob asks for the build commands on their own, with nothing trimmed. The install command wrote a credential into a config file, and it is still sitting in the layer that command created.
-
Why deleting did not help
A moment on the mechanism before Bob uses what he found.
-
Check what the token opens
A credential is only worth what it can reach. Bob asks Cindralt's package index who this token belongs to and what it is allowed to do. The answer is worse than a single service. The token publishes to every package Cindralt builds against, and it never expires.
-
What that token is worth
Bob does not need to touch Cindralt's servers again. With publish rights on every package the company builds against, the next version of any internal library can be his, and it will be pulled by every build that runs after it.
-
The index flags a publish
Cindralt's package index logs every authenticated call. Overnight it recorded the ci-publisher token being used from an address that belongs to nobody at the company.
-
Confirm it from the image
Alice checks the claim herself against the published artifact, using exactly what an outsider would have: the public tag and a stock Docker client.