Docker Hub KICS and Trivy Compromise: CI/CD Supply-Chain Response Playbook
On April 23, 2026, Docker published details on two Docker Hub supply-chain compromises that hit common security scanners: Trivy first, then Checkmarx KICS. The KICS incident was especially uncomfortable for CI/CD teams because the compromised image still behaved like a scanner while quietly collecting scan output. If your pipelines pulled mutable scanner tags during the April 2026 exposure window, treat this as a CI incident, not just a Docker cleanup task.

Docker’s write-up says the KICS push happened on April 22, 2026 at about 12:35 UTC, using valid publisher credentials, and that Docker infrastructure itself was not breached. Five existing tags were overwritten and two new tags were created. The practical lesson is blunt: if a pipeline pulls checkmarx/kics:latest, aquasec/trivy:latest, or any other mutable scanner image without digest pinning, the scanner becomes part of your attack surface.
This playbook focuses on what platform and DevSecOps teams should do if those images touched their CI runners, IaC repositories, Kubernetes manifests, Dockerfiles, or cloud credentials. If you are still designing the broader scanning program, read the existing Bits Lovers guide on container security with Trivy, ECR scanning, and runtime protection first. This post is the response plan for when the scanner itself becomes suspicious.
What Happened and Why CI/CD Teams Should Care
According to Docker, the KICS compromise used legitimate Checkmarx publisher credentials to push malicious images into checkmarx/kics. The attack did not require a Docker Hub infrastructure breach. That detail matters because many teams still model registry compromise as a platform breach. In this case, the weak point was publisher identity and tag trust.
The malicious KICS image reportedly preserved normal scanning behavior while adding exfiltration of scan output to attacker-controlled infrastructure. That is exactly the wrong place for a malicious binary to sit. KICS scans Terraform, CloudFormation, Kubernetes YAML, Dockerfiles, and related infrastructure files. Those scan results often include resource names, account IDs, endpoint names, policy snippets, environment variables, internal hostnames, and sometimes secrets that were supposed to be caught before merge.
The scanner is usually trusted by design:
- It runs early in the pipeline.
- It receives broad repository read access.
- It may mount the workspace with Terraform state, rendered manifests, or generated plans.
- It often runs before cleanup steps delete temporary credentials.
- It may execute on shared self-hosted runners with Docker layer caches.
That combination turns a scanner image compromise into a supply-chain incident with CI, secrets, and artifact integrity implications. It also connects directly to the pipeline hardening practices in GitLab CI/CD + Terraform: A Production IaC Pipeline in 2026 and SBOM + Container Signing on GitLab CI.
Known KICS Indicators from Docker
Use this table as the first triage pass. Do not rely only on tag names because compromised tags can later be restored to clean digests. Look for digests in runner pull logs, proxy cache logs, registry mirror metadata, Docker daemon event logs, and CI job output.
| Date or value | What to check | Why it matters |
|---|---|---|
| April 22, 2026 12:35 UTC | Pulls of checkmarx/kics shortly after this time |
Docker says malicious KICS images were pushed at about this time. |
| April 23, 2026 | Docker advisory publication date | Use this as the deadline for incident review, not as the start of exposure. |
latest, v2.1.20, v2.1.20-debian, alpine, debian |
Existing KICS tags that were overwritten | Mutable tags are not evidence of safety after restoration. |
v2.1.21, v2.1.21-debian |
Newly created KICS tags | Treat these as suspicious if seen in CI history. |
audit.checkmarx[.]cx |
Network egress indicator | Docker identified this as attacker-controlled infrastructure. |
KICS-Telemetry/2.0 |
HTTP User-Agent indicator | Strong signal that the malicious path ran. |
Docker also published malicious digest values in its incident post. If you have image pull telemetry, compare directly against the Docker advisory rather than copying partial digests into chat or tickets. The source is here: Docker: Trivy, KICS, and the shape of supply chain attacks so far in 2026.
First Hour Response Checklist
Start with containment. A scanner image compromise is not the time to debate whether a secret “probably” appeared in output. Assume the pipeline workspace was visible to the scanner and reduce blast radius.
- Freeze pipelines that pull affected scanner images by tag.
- Disable scheduled security scans that use
checkmarx/kicsor any scanner image without digest pinning. - Export CI job history for the exposure window before logs expire.
- Search logs for
checkmarx/kics,aquasec/trivy, affected tags, and suspicious digests. - Search proxy, firewall, NAT gateway, and DNS logs for
audit.checkmarx.cxand theKICS-Telemetry/2.0User-Agent. - Snapshot runner hosts or VM disks if you need forensic evidence.
- Purge Docker layer caches on self-hosted runners.
- Purge pull-through cache, registry mirror, and build cache entries for affected digests.
- Rotate credentials that were available to jobs running affected images.
- Replace scanner tags with pinned digests and require code review for future digest changes.
The important detail is cache cleanup. Pulling the restored tag does not delete a malicious layer already sitting on a self-hosted runner or registry mirror. If your runners are ephemeral and rebuilt from a clean image for every job, your cache problem is smaller. If your runners are long-lived VMs with shared Docker state, assume the compromised layer can be reused until removed.
Credential Rotation Priority
Not every secret has the same urgency. Rotate based on exposure, privilege, and persistence. A short-lived OIDC token that expired in 15 minutes is different from an AWS access key stored in a CI variable for three years.
| Credential or data type | Rotate now? | Priority | Notes |
|---|---|---|---|
| Static AWS access keys in CI variables | Yes | P0 | Treat as compromised if present in job environment or config files. |
| GitHub, GitLab, or Bitbucket PATs | Yes | P0 | Especially tokens with repo write, package publish, or workflow permissions. |
| Docker Hub publisher tokens | Yes | P0 | Compromise pattern centered on publisher credentials. |
| Terraform Cloud, Atlantis, or Spacelift tokens | Yes | P0 | IaC scanners commonly see Terraform files and backend metadata. |
| Kubernetes kubeconfigs | Yes | P0 | Rotate if mounted into jobs or stored in repository variables. |
| Cloud provider OIDC trust roles | Review trust policy | P1 | Rotate is not always meaningful, but scope and audience conditions may need tightening. |
| Webhook signing secrets | Yes | P1 | Rotate if included in repository secrets or rendered config. |
| Database passwords in IaC examples | Yes | P1 | Especially if “example” values are reused in lower environments. |
| Account IDs, cluster names, VPC IDs | No | P2 | Not secrets, but useful for attacker targeting and phishing. |
| SBOM or scan reports | No | P2 | Review for embedded secrets before sharing externally. |
For AWS, the right response is usually “disable, create replacement, deploy, then delete old” rather than “delete immediately and break production.” If you already route findings into AWS Security Hub or CloudWatch, the pipeline in AWS Security Hub and CloudWatch Findings is a good pattern for tracking rotation completion as security operations work instead of Slack archaeology.
Find Where the Scanner Ran
The most common mistake is only searching the main application pipelines. Scanners often run in auxiliary jobs that people forget about: nightly compliance scans, pull request templates, IaC validation repos, container base-image refresh jobs, and platform-team bootstrap projects.
Search the repository first:
rg -n "checkmarx/kics|aquasec/trivy|trivy image|trivy config|kics scan|docker run.*kics|docker run.*trivy" .
Then search CI history. For GitLab, start with project pipelines and runner logs:
gitlab_project_id="12345"
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/${gitlab_project_id}/jobs?per_page=100" \
| jq -r '.[] | [.id, .name, .status, .created_at] | @tsv'
For local runner hosts, inspect Docker history:
docker image ls --digests | grep -E 'checkmarx/kics|aquasec/trivy'
docker system df
docker events --since '2026-04-22T00:00:00Z' --until '2026-04-24T00:00:00Z'
If your runners are Kubernetes pods, query image usage through pod history if available, admission logs, audit logs, or your cluster logging backend:
kubectl get pods -A \
-o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.image}{" "}{end}{"\n"}{end}' \
| grep -E 'checkmarx/kics|aquasec/trivy'
Digest Pinning Examples
Tags are convenient for humans. Digests are safer for automation. After this incident, “we use official images” is not enough. The attacker used the official publishing flow. Pin the exact image digest in CI, then move digest changes through the same review path as dependency upgrades.
Bad:
kics:
image: checkmarx/kics:latest
script:
- kics scan -p .
Better:
kics:
image: checkmarx/kics@sha256:REPLACE_WITH_VERIFIED_GOOD_DIGEST
script:
- kics scan -p . --report-formats json --output-path reports/kics
For Trivy:
trivy:
image: aquasec/trivy@sha256:REPLACE_WITH_VERIFIED_GOOD_DIGEST
script:
- trivy config --exit-code 1 --severity HIGH,CRITICAL .
- trivy fs --scanners vuln,secret,misconfig --exit-code 1 .
If your CI system does not support digest-only images cleanly, pull by digest explicitly:
docker pull checkmarx/kics@sha256:REPLACE_WITH_VERIFIED_GOOD_DIGEST
docker run --rm \
-v "$PWD:/repo:ro" \
checkmarx/kics@sha256:REPLACE_WITH_VERIFIED_GOOD_DIGEST \
scan -p /repo
Do not invent the digest manually from a blog post comment or old terminal output. Use the vendor’s restored repository, release notes, signatures if available, and your own approval process. The point is not just “use a digest.” The point is “make image identity reviewable.”
Registry and Runner Cleanup
You need cleanup at every layer where the compromised image may have been cached:
- Developer laptops that ran security scans locally.
- Self-hosted CI runners with persistent Docker state.
- Kubernetes nodes running CI jobs.
- Docker BuildKit caches.
- Pull-through caches and registry mirrors.
- Air-gapped promotion registries.
- Golden runner images that pre-cache tools.
For standalone runner hosts:
docker image ls --digests | grep checkmarx/kics
docker image rm checkmarx/kics@sha256:SUSPICIOUS_DIGEST
docker builder prune --filter until=48h
docker system prune --volumes
For containerd based nodes:
sudo crictl images | grep checkmarx/kics
sudo crictl rmi IMAGE_ID
Be careful with broad prune commands on shared runners. They can disrupt active jobs. Drain the runner first, stop the runner service, clean the image store, then bring it back. That is slower than a one-line docker system prune -af, but it avoids turning a security incident into a delivery outage.
Pipeline Hardening After the Fire Drill
Once the immediate response is done, fix the system design that made the incident dangerous.
The minimum baseline:
- Run scanners in read-only workspaces when possible.
- Do not mount cloud credentials into scanners that do not need them.
- Prefer OIDC with short sessions over static access keys.
- Use digest pinning for CI tool images.
- Mirror approved scanner images into a controlled internal registry.
- Require signed commits or protected merge paths for CI template changes.
- Treat scanner output as sensitive because it can contain secret material.
- Restrict egress from CI runners to known endpoints.
- Alert on new outbound domains from runner networks.
- Keep runners ephemeral unless there is a strong reason not to.
The egress point is often skipped. A malicious scanner does not need inbound access. It needs the ability to send data out. For production-grade runner networks, “deny all internet, allow required registries and APIs” is much better than “everything can talk to everything on port 443.”
Gotchas That Will Waste Your Day
Restored tags do not prove old jobs were safe
If checkmarx/kics:latest points to a clean digest today, that says nothing about what it pointed to when your job ran. You need historical pull telemetry, job logs, runner cache metadata, or proxy logs.
Scan output can contain secrets even if source code does not
IaC tools render values. A Terraform plan can include provider endpoints, resource IDs, environment names, ARNs, and sometimes generated credentials. Rotate based on what the scanner could read, not only what the repository stores.
Ephemeral cloud credentials are not automatically safe
Short-lived credentials may have expired, but they can still be used during the job window. Check CloudTrail for suspicious calls from the same role sessions used by affected jobs.
Digest pinning without an update process becomes shelfware
Pinned digests stop silent tag replacement. They do not keep tools patched. Add a scheduled dependency update workflow that opens a reviewed pull request with the new digest, release notes, and signature verification.
Shared runners create shared blast radius
If project A pulled the malicious layer onto a shared runner, project B may later reuse that host. This is why runner isolation matters for regulated workloads and why long-lived “utility runners” become risky over time.
Final Recovery Criteria
Do not close the incident because “the vendor fixed the tag.” Close it when your environment is clean and your future exposure is lower.
Use this as the exit checklist:
- Affected pipeline runs are identified by date, project, runner, image tag, and digest.
- Egress logs were checked for Docker’s published indicators.
- High-risk credentials were rotated or proven out of scope.
- Runner caches and registry mirrors were purged.
- CI scanner images are pinned by digest.
- CI jobs use read-only mounts where possible.
- Static CI credentials were replaced with OIDC or narrowed in scope.
- Security scan outputs are treated as sensitive artifacts.
- A follow-up issue exists for scanner image signing and verification.
- Teams have a documented process for emergency CI tool revocation.
The uncomfortable lesson from the KICS and Trivy compromises is that security tools are still software supply-chain dependencies. They need the same controls as build images, deployment CLIs, and production base images. If your scanner can read the whole repository and reach the internet, it deserves more scrutiny than a random app dependency, not less.
Comments