The pattern I’ve seen most often in supply chain incidents (or near-incidents) with containers isn’t sophisticated: a team inherits an “official” base image from a public registry, the pipeline consumes it with :latest and, without realizing it, that base changes. Sometimes it changes due to a legitimate update; other times, because someone compromised the maintainer’s account or the upstream publishing process. The practical result is the same: your CI/CD downloads something different from what you reviewed, and runs it with your permissions, your network, and your data.
When we talk about ECR (AWS), ACR (Azure) or GCR/Artifact Registry (Google Cloud), the typical mistake is assuming that “if it’s already in the company registry, it’s safe”. It isn’t if there are no controls at push time (scanning, signing, immutability, deployment policies). A private registry without guardrails is simply an internal mirror of the same risks, except now with faster distribution to production.
What went wrong: mutable tags and inheriting “official” images
The foundational failure almost always starts with an operational decision: using python:latest, alpine:latest or similar “for convenience”. In a corporate environment, that convenience becomes variability: today’s build isn’t tomorrow’s build, even if the Dockerfile hasn’t changed. When anomalous behavior appears (CPU at 100%, strange outbound connections, unknown processes), the team loses hours because reproducibility broke.
Inheriting “official images” doesn’t remove the risk; it shifts it. If an attacker compromises the maintainer’s account or the upstream publishing pipeline and injects a cryptominer or a reverse shell, your pipeline will treat it as a legitimate update. And since base images are used cross-cuttingly (API, jobs, workers), the blast radius isn’t one app: it’s half the platform.
- Using
:latestin CI/CD
This allows the tag to point to a different digest without anyone noticing. In practice, the change comes in as “a rebuild” and ends up deployed by the same automated flow that gives you speed. If the runtime has egress to the Internet, credentials in variables, or access to queues/buckets, the attacker inherits that access.
- “Official” doesn’t mean “immutable” nor “fit for your threat model”
Even if the image is popular, your risk is in the publishing link and in how you consume it. If you don’t pin by digest (sha256:...), there’s no guarantee that the deployed artifact is the same as the approved one. In internal audits, this is often a friction point: you can’t prove which exact binary you ran.
Attack surface in ECR, ACR and GCR: the registry as an incident accelerator
In corporate cloud, the private registry is usually integrated with IAM (roles, service principals, workload identity) and with the build/deploy pipeline. That’s good for operations, but it amplifies incidents: if a malicious image reaches the registry, it becomes internal “trusted distribution”. Instead of each team doing pull from the Internet (noisy and sometimes blocked), everyone consumes from ECR/ACR/GCR, fast and without suspicion.
The frequent misunderstanding is thinking security is “at the perimeter” of the registry (private, with RBAC). The reality is that supply chain risk is about artifact integrity. You can have perfect authentication and still be propagating a backdoored image if nobody scanned it on entry or if nobody validates signature/digest at deployment.
- Upstream compromise + internal replication
A typical scenario: the platform team creates a corporate base image (for example, corp/python-base) that FROM python:latest. They publish it to ECR/ACR/GCR and dozens of repos inherit it. If upstream gets contaminated, the next rebuild of the corporate base drags the problem along and makes it “standard” across the organization.
- Runtime permissions as a multiplier
The image doesn’t need to “exploit” anything if it already runs with useful permissions. In companies it’s common to see workloads with access to secrets, queues, buckets, or internal APIs. A cryptominer consumes budget; a reverse shell consumes confidentiality and can pivot to lateral systems. The registry doesn’t cause the exploit, but it does accelerate deployment of the payload.
Early signals and interpretation errors that make you lose days
When a contaminated image gets in, it’s rarely detected by a “container registry alert”. It’s detected by operational symptoms: increased consumption, strange connections, or unexpected processes. The problem is that if the team doesn’t have digest-level traceability and doesn’t log which exact image was deployed, the diagnosis turns into a discussion of hypotheses.
Another common mistake is assuming that passing the image through the corporate registry “sanitizes” it. In many environments, the registry is just storage. If scanning isn’t enforced at push time and if deployment doesn’t apply (deny) policies based on scan/signature results, the control is cosmetic.
- Drift between build and runtime
If the pipeline builds with a mutable tag, two runs of the same job can produce different images. In incident response this is critical: you can’t reproduce the binary, you can’t correlate hashes, and rollback becomes uncertain (which “version” do you roll back to if everything was :latest?).
- “It’s in ECR/ACR/GCR, therefore it’s approved”
In practice, that assumption leads to relaxed reviews: scrutiny at deployment is reduced and there’s trust that “platform controls it”. If platform didn’t configure immutability, doesn’t scan on push, and doesn’t require signing, the registry ends up being a repository of opaque artifacts, not a security control.
How to do it in practice: immutability, push-time scanning, and digest verification
The first operational decision is to cut mutability where it hurts most: rewritable tags and consumption without a digest. In corporate registries, block overwrites (tag immutability) and require that deployment reference a digest when the risk justifies it (critical bases and runtimes). This isn’t “purism”: it’s reproducibility and responsiveness.
The second axis is that the artifact is evaluated on entry, not when it’s already running. Automatic scanning on push doesn’t guarantee “zero risk”, but it does prevent introducing known vulnerabilities by default and gives you a consistent control point. Scanning must be part of the publishing flow into the registry; if it remains a manual task “when there’s time”, it won’t happen.
- Example (AWS ECR): policy to prevent deleting or overwriting tags
In AWS, combine ECR controls with IAM to reduce operational mutability. For example, deny ecr:BatchDeleteImage and ecr:DeleteRepository to CI/CD identities except controlled exceptions. At the repository level, enable tag immutability (repository setting) to prevent a push from overwriting an existing tag. Validate the outcome by trying to republish the same tag and verifying that the registry rejects it.
- Example (Azure ACR): enable quarantine/scanning in the import flow
In ACR, an effective corporate pattern is to import from public into a controlled repository (instead of direct pulls from the Internet) and apply scanning/validation before promoting to “release”. While the exact mechanism depends on your tools (Defender for Cloud, CI integrations), the practical validation is always the same: the tag that production consumes is only created/promoted if the scan passes and it’s recorded as an approved artifact.
- Example (GCR/Artifact Registry): digest-based consumption in deployment manifests
In GCP, the most tangible change is to stop deploying image: repo/app:tag when the risk is high and move to image: repo/app@sha256:.... You validate it by reviewing the applied manifest (for example, what actually ended up in the cluster) and comparing it with the registry digest. This eliminates the surprise that “the tag moved” between environments.
Image signatures and deployment policies: Cosign/Notation so the control is enforceable
Scanning helps, but it doesn’t solve the integrity problem: an image can be free of critical CVEs and still include a payload. Image signing (Cosign or Notation) targets the key point: proving who produced the artifact and preventing deployment of something unauthorized. In companies, the difference between “we have signatures” and “signatures are useful” is whether there is a policy that blocks unsigned deployments.
Operationally, this requires two pieces: signing in the pipeline (ideally with workload identity, not with static keys in secrets) and verifying at the admission/deploy step. In Kubernetes it usually materializes as policy-as-code (for example, Gatekeeper/Kyverno) that requires a valid signature for images coming from ECR/ACR/GCR. In serverless/managed platforms, it lands as validations in the release pipeline (only promote if the signature is present and matches the expected identity).
- Minimum corporate policy: “not signed, not deployed”
It sounds harsh, but it’s the only way to avoid bypasses due to urgency. In practice, you start with an “audit” mode (only reports) to measure impact, and you move to “enforce” by domains: first internal base images, then publicly exposed workloads. What matters is that the exception is explicit, temporary, and recorded, not a “we turned off the policy because it breaks the deploy”.
- Practical verification: signature ↔ digest traceability
A real verification doesn’t stop at “the image has a signed tag”. It must tie the signature to the digest. The useful control is: for a specific digest in ECR/ACR/GCR, there is a valid signature issued by the expected pipeline identity. That way, even if someone tries to move a tag, the deployment won’t accept an unsigned digest.
Recommendations for corporate environments
If your organization consumes public base images, the main risk isn’t “using open source”: it’s consuming mutable artifacts without pinning, and allowing the pipeline to propagate unreviewed changes. The first guardrail that truly reduces incidents is to stop depending on :latest and start operating with digests for critical bases, because it restores reproducibility and speeds up response to anomalies.
A corporate registry (ECR/ACR/GCR) provides control only if it’s used as a control point: tag immutability to prevent overwrites, automatic scanning on push/import, and a promotion flow where production consumes only approved artifacts. When this doesn’t exist, the private registry becomes an accelerator for propagating unsafe artifacts.
To close the supply chain loop, sign images with Cosign/Notation in the pipeline and turn verification into an enforceable policy that blocks unsigned deployments. In environments under pressure to deliver, the policy must be designed to survive urgency: audit first to measure, enforce in phases, and minimal exceptions with expiry and traceability.
Interested in Cloud Security?
Technical analysis, hands-on labs and real-world cloud security insights.