Git, CI/CD & GitOpsXCVI · Incident: Malicious DependencyIncidentResponse
Identify impacted artifacts — what used the bad version
What you'll learn
- Build the artifact inventory from the build provenance and the lockfile history, not from the artifact registry metadata alone
- Trace each artifact produced in the compromise window back to the lockfile commit that resolved the malicious version
- Apply the decision rule: any artifact built from a lockfile that resolved the malicious version is suspect
- Distinguish artifact invalidation (mark as compromised) from artifact deletion (remove from registry) and pick the right one
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
A malicious dependency in the lockfile produces suspect artifacts. Every container image, build artifact, and deployable built from a lockfile that resolved to the malicious version is suspect: a backdoored binary, a credential exfiltrator, a deployable whose install hook ran the attacker’s payload. The inventory is built from the build provenance and used to scope the rebuild.
The artifact classes
A build that resolved the malicious version produces four classes of artifact:
flowchart LR
A["lockfile with malicious version"] --> B["container images"]
A --> C["build artifacts"]
A --> D["release tarballs"]
A --> E["signed attestations"]
B --> F["registry scope"]
C --> G["artifact store scope"]
D --> H["deployable scope"]
E --> I["trust scope"]
- Container images. Pushed to the registry during
docker buildanddocker push. The image digest is the artefact consumers reference. - Build artifacts. Uploaded to the workflow artifact
store during
actions/upload-artifactsteps. - Release tarballs. Packaged by the release workflow and uploaded to the release page.
- Signed attestations. Cosign signatures, in-toto attestations, SLSA provenance. The signature was made by a build that resolved the malicious version.
Building the artifact inventory
The inventory is built from two sources and reconciled:
gh api /repos/OWNER/REPO/actions/runs \
> workflow-runs-$(date -u +%FT%TZ).json
gh api /orgs/ORG/packages/container/IMAGE/versions \
> registry-versions-$(date -u +%FT%TZ).json
git log --all --oneline -- package-lock.json \
--since "$MALICIOUS_PUBLISH_TIMESTAMP"
The reconciliation cross-references every workflow run that resolved the malicious version against a registry version. A mismatch is the highest-priority finding.
The decision rule
Every artifact built from a lockfile that resolved the malicious version in the compromise window is suspect. The window starts at the publish timestamp and ends at the moment the lockfile pin is committed. The rule is unconditional: a build that ran before the publish is not in the window. The lockfile diff against the publish timestamp is the mechanical gate:
trivy fs --security-checks vuln .
Invalidation versus deletion
For each artifact, the team chooses between invalidation and deletion:
- Invalidation marks the artifact as compromised. Consumers see a warning when they pull. The bytes remain for forensic analysis. The default for container images that may still be running.
- Deletion removes the bytes. Consumers fail when they pull. The default for build artifacts already consumed and for confirmed-backdoored images.
| Artifact class | Default action |
|---|---|
| Container images still running | Invalidate |
| Container images confirmed backdoored | Delete |
| Build artifacts | Archive (offline) |
| Release tarballs | Invalidate |
| Signed attestations | Revoke |
Production discipline
- Inventory before action. The artifact inventory drives the rebuild scope; missing an artifact means missing a backdoor.
- Lockfile as the gate. The publish timestamp and lockfile diff are the mechanical gate.
- Reconcile the three sources. The workflow run history, the registry metadata, and the lockfile history must agree.
- Invalidate by default. Destructive actions are reserved for confirmed-backdoored artifacts.
Cross-course references
- Git, CI/CD & GitOps — Part LXVII-06 (Package Pinning and Lockfiles) covers the lockfile shape the inventory reads.
- Git, CI/CD & GitOps — Part LXXI-04 (Dependency Compromise Attack) covers the four shapes of dependency-compromise attack.
- Container Security for Production Sysadmins — Part V (Image Scanning) covers the registry-side validation patterns the invalidation action requires.
Quiz
Knowledge check · 4 questions
Q1. The artifact registry contains a container image digest that was tagged with a known-good version tag but was actually built from a lockfile that resolved the malicious version. What is the right interpretation?
Q2. A container image built from a lockfile that resolved the malicious version before the malicious version was published to the registry is not safe to leave in the registry without invalidation.
Q3. Name the three sources used to build the artifact inventory and state what a mismatch between them indicates.
Q4. A team is responding to a malicious-dependency alert. The build provenance shows 47 workflow runs since the malicious version's publish timestamp. The registry shows 52 container image versions in the same window. The lockfile history shows 49 lockfile changes. Triage the mismatches.
The 47 workflow runs produced container images that were pushed to the registry. The lockfile history shows 49 lockfile changes, two of which reverted a malicious version pin and re-resolved against the live registry. The registry shows 52 image versions — three more than the workflow runs and three more than the lockfile changes.
Passing score: 75%. Answers are checked in this browser.