Containers are everywhere today. Whether supporting a small side-project or driving the world's largest platforms, containers deliver developers a quick and reproducible application packaging mechanism. The central element of this ecosystem is Docker images — the blueprints that containers run on.
When you pull an image from Docker Hub or your company’s private registry, you assume that the image is secure and untouched. We trust that the layers inside haven’t been changed since the developer built them.
That being said, the problem is that Docker images have multiple layers on top of one another, and the security checks may not often go as deep as you would expect. Research like gh0stEdit has shown that attackers can tamper with these layers in sneaky ways. In some cases, the changes don’t even get noticed by Docker’s own integrity checks.
Why does this matter?
Because an image that looks legitimate could be quietly running backdoored binaries, hidden scripts, or altered configuration files. Imagine pulling what looks like a trusted “alpine” or “nginx” image, only to find out it’s been silently changed to leak secrets or open a backdoor.
In this article, we’ll break down how Docker images are structured, how attacks like gh0stEdit take advantage of that, and what you can do to protect your build and runtime environments. By the end, you’ll see why image integrity is more than just a buzzword — it’s something every team relying on containers should care about.
Docker Image Basics
To understand how an attack like gh0stEdit operates, it is useful to first take a look at what a Docker image actually is.
A Docker image is like a layered cake. Whenever you add a new command in the Dockerfile - such as "FROM ubuntu", "RUN apt-get install", or "COPY app.py /app/" - another layer gets added. When all layers stack up, you get a complete image.
When you spin up the image, Docker leverages what is known as a union filesystem (such as OverlayFS) so that these many layers all come to appear as a single filesystem to the container. The app itself sees it as a regular, ordinary Linux environment, but underneath are loads of layers bolted together.
A few key things to know:
- Base image → The bottom layer (e.g., ubuntu, alpine).
- Intermediate layers → Each command in your Dockerfile creates another layer.
- Final layer → The very top, often where your app code and configs live.
- Manifest → a small JSON file. It tells Docker which layers to use and how they fit together.
When you pull an image from Docker Hub, Docker grabs those layers. It then checks their digests (cryptographic fingerprints) to be sure they match the one in the registry. If the check passes, Docker unpacks the layers and starts the container.
What is gh0stEdit?
Now that we know Docker images are built from layers, let’s look at how attackers can sneak into them.
gh0stEdit is the name given to a clever attack described in recent research. The idea is simple but dangerous: an attacker directly edits one of the lower layers of an image, adds something malicious, and then reassembles the image.
Here’s the tricky part — Docker’s normal checks don’t always notice this. Why? Because Docker focuses on the manifest (the recipe that lists the layers and their digests), not on every single file inside each layer once it’s been unpacked. If the attacker is clever, the manifest can still look normal. But inside, the container carries hidden code you don’t want.
It is like purchasing a layered cake - packaged in a box. The label tells you the correct flavor: chocolate, vanilla, and strawberry. The bakery signed it, so you know it is authentic. On the surface, everything is good. However, somebody sneaks in and slices the cake sideways and puts chili powder between the strawberry and the vanilla layer. They button it all up again, and the boxis still in good shape, the stamp still there. When you bite in… surprise.
That’s essentially what gh0stEdit does: it hides something harmful inside a Docker image layer without breaking the label (manifest) on the outside.
The impact? A developer or CI pipeline might happily pull what looks like a trusted alpine or nginx image, but when it runs, the container quietly executes a malicious binary or script that wasn’t there before.
Demo: Modifying a Docker Image Layer (PoC)
To see how fragile Docker image integrity can be, let’s do a simple proof of concept. We’ll take a trusted base image (alpine), mess with one of its layers directly, and then run a container that shows our changes.
Note: This is just for demonstration — don’t try this on production hosts!
Step 1: Pull a clean base image
docker pull alpine:latest
We start by pulling the alpine image. Nothing fancy here — just the official lightweight Linux distribution that everyone uses.
Step 2: Run a container in the background
docker run -d --name test alpine sleep 1000
Next, we launch a container from that image. The sleep 1000 just keeps it running in the background so we can poke around.
Step 3: Find where Docker keeps the layer
LAYER=$(docker inspect test | jq -r '.[0].GraphDriver.Data.MergedDir')
echo $LAYER
Every container is built from layers stored under Docker’s overlay2 directory. The command above grabs the path to the merged filesystem that this container is actually using.
Step 4: Inject something malicious
echo 'echo hacked' > $LAYER/usr/local/bin/fake
chmod +x $LAYER/usr/local/bin/fake
Here’s where the fun starts: we drop a fake script called fake directly into the container’s filesystem. This bypasses the Dockerfile, build process, and registry checks.
Step 5: Test it out
docker exec test fake
# Output: hacked
Boom. The container runs our injected binary as if it were part of the original image. No warning, no error — Docker thinks everything is fine.
Why this matters
- The registry still shows alpine:latest as clean.
- Docker’s manifest digest hasn’t changed.
- But our container now runs extra code we never built or signed.
This simple demo is the same core idea behind gh0stEdit: tamper with image layers in ways that the usual checks don’t catch. At scale, this means a poisoned image could spread through CI/CD pipelines or production clusters without anyone noticing until it’s too late.
Why Docker’s Built-in Checks Miss This
You might wonder: Shouldn’t Docker notice if an image has been changed?
The problem is that Docker only validates the big pieces:
- The manifest → a JSON recipe listing layers and their digests.
- The layer tarballs → compressed files that hold each layer.
As long as those tarballs match their digest, Docker assumes the image is clean. But once a layer is unpacked into the filesystem, Docker doesn’t re-check the individual files inside it.
This creates a blind spot:
- An attacker can alter files in the unpacked filesystem.
- The manifest digest still looks valid.
- Docker happily runs the container, unaware of the tampering.
Note: Docker does provide Content Trust for signing and verifying images, but that focuses on manifests and digests — not on detecting post-unpack changes inside layers.
Thus, despite the integrity checks Docker may perform, it does not guard against post facto attacks integrated on a developer machine, CI/CD cache, or even a compromised registry.
Security Implications
Once an image layer is tampered with, it can spread silently through your systems.
- CI/CD pipelines → If a cached base image is poisoned, every new build inherits the backdoor.
- Public registries → Trusted images like alpine or nginx could carry hidden code if re-uploaded.
- Insider threats → An admin with filesystem access can alter layers without breaking manifests.
The danger is invisibility: everything looks normal — the image name, the digest, even the signature. But your containers may be running code you never intended. In regulated industries, hidden image tampering is both a security and a compliance risk. Standards like NIST SP 800-204C: Strategies for Microservices Security and NIST SSDF highlight the importance of securing the container supply chain.
Defenses & Best Practices
How do you defend against attacks like gh0stEdit? Here are some practical steps:
Reproducible builds
- Always rebuild images from a trusted Dockerfile and base image. Avoid passing around pre-built images or manually edited layers. This way, you can always prove how an image was made.
Image signing and verification
- Use tools like Notary v2 or cosign to sign images at build time. At deploy time, verify the signature before running the container. This ensures that only images you trust make it to production.
Registry scanning
- Run vulnerability and content scans with tools such as trivy, grype, or your cloud provider’s artifact scanning service. These tools can catch suspicious files or unexpected binaries added to an image.
Runtime admission controls
- Enforce policies with systems like Binary Authorization (GCP), OPA Gatekeeper, or Kyverno. These admission controllers prevent unsigned or unscanned images from running inside your cluster.
Continuous auditing and monitoring
- Regularly audit your registries and running workloads. Look for image drift (when what’s running no longer matches what was built), and set up alerts for unusual binaries or processes inside containers.
Conclusion
Docker images are the foundation of modern software delivery. But as the gh0stEdit attack shows, image integrity isn’t guaranteed once layers are unpacked — attackers can exploit that blind spot to slip in code invisible to normal checks.
The good news: teams can fight back. Rebuild images from source, sign and verify them. Scan registries and enforce runtime policies. Such measures not only enhance security, but also build trust with auditors, customers, and developers.
The big takeaway is: trust, but verify. Don’t assume an image is safe just because it comes from a familiar registry or passes a digest check. Treat image integrity as a first-class concern, the same way you treat secrets or TLS certificates.
If you bake these checks into your CI/CD process, you won’t just be protecting containers — you’ll be protecting the systems, customers, and business that rely on them.