Why the problem matters
Modern CI/CD pipelines are a buffet of binaries pulled from package registries, container registries, or vendor sites. One bad apple can slip through, get signed, and end up in production. The 2025 “Supply‑Chain Attack” wave showed that attackers are hitting build tooling hard, not just the app code. If a malicious binary hides inside a Docker image or a shared library, it can survive across environments, dodge runtime defenses, and give attackers a foothold for lateral movement.
This post isn’t about theory; it’s a concrete, repeatable workflow to detect, isolate, and purge third‑party binaries before they reach your deployment target.
1. Establish a Baseline for “Good” Artifacts
You can’t spot anomalies without a reference set.
| Artifact type | Baseline source | Typical verification |
|---|---|---|
| OS packages | Official distro repos (apt, yum) |
GPG signatures, SHA‑256 checksums |
| Language packages | Official registries (npm, pypi, crates.io) |
PGP signatures, lockfile hashes |
| Container images | Official image repositories (docker.io/library, ghcr.io) |
Notary signatures, SBOMs |
| Vendor binaries | Vendor‑provided download pages | Vendor‑signed certificates, checksum files |
Practical steps
-
Lock down the registry
- Use a private mirror (Nexus, Artifactory) that verifies signatures and stores the artifact hash in a database.
- Tell your CI to pull only from the mirror.
-
Maintain a hash database
# Store SHA‑256 of every accepted artifact echo "$(sha256sum /path/to/artifact) $(basename /path/to/artifact)" >> /etc/artifact-hashes.txt -
Automate SBOM generation
# Using syft to generate an SPDX SBOM syft packages /path/to/image > image.sbom.spdx -
Version pinning
- For language packages, use lockfiles (
package-lock.json,Cargo.lock). - For binaries, use explicit version tags in your CI scripts.
- For language packages, use lockfiles (
2. Detecting Anomalies During Build
Even with a baseline, attackers can slip in through zero‑day supply‑chain exploits. The following checks should run before any artifact is used downstream.
2.1 Hash and Signature Verification
# Verify a downloaded binary against the stored hash
expected_hash=$(grep "$(basename $artifact)" /etc/artifact-hashes.txt | awk '{print $1}')
actual_hash=$(sha256sum $artifact | awk '{print $1}')
if [[ $expected_hash != $actual_hash ]]; then
echo "Hash mismatch! Aborting build."
exit 1
fi
If the artifact comes from a language registry, also verify the PGP signature:
gpg --verify $artifact.sig $artifact
2.2 Static Binary Analysis
Use a lightweight scanner to catch obvious malicious patterns:
| Tool | What it looks for |
|---|---|
binwalk |
Embedded files, shellcode |
strings + grep |
Hard‑coded URLs, suspicious commands |
rabin2 |
PE/ELF metadata, import tables |
strings $artifact | grep -E 'http[s]?://|powershell|cmd.exe' && echo "Suspicious strings found"
2.3 Runtime Sandbox Validation
The most reliable test is to run the binary in a sandbox and observe its behavior.
docker run --rm -it --cap-drop ALL --security-opt no-new-privileges \
-v $(pwd)/artifact:/artifact:ro \
alpine:latest /artifact
- Observations to log: outbound connections, file writes, privilege escalation attempts.
- Use
straceorauditdinside the container to capture system calls.
If the binary tries to reach an external IP or writes to /etc/passwd, flag it.
2.4 SBOM Consistency Check
Compare the generated SBOM against the expected component list.
diff -u expected.sbom.spdx image.sbom.spdx
Any new component that isn’t in the baseline should trigger a manual review.
3. Handling a Suspicious Artifact
When a check fails, decide whether to quarantine, investigate, or abort.
| Decision | Action |
|---|---|
| Quarantine | Store the artifact in a separate “suspect” bucket, tag it, and notify the security team. |
| Investigate | Run deeper analysis (dynamic analysis, malware sandbox). |
| Abort | Fail the build and prevent downstream stages. |
3.1 Quarantine Workflow
# Move to suspect bucket
mkdir -p /var/suspect
mv $artifact /var/suspect/
# Tag in artifact DB
echo "$(sha256sum $artifact | awk '{print $1}') $(basename $artifact) SUSPECT" >> /etc/artifact-hashes.txt
Notify via Slack or email:
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"Artifact $(basename $artifact) flagged as SUSPECT"}' \
$SLACK_WEBHOOK
3.2 Investigation Pipeline
-
Static Re‑analysis
- Run
radare2orGhidrato inspect control flow. - Look for obfuscated strings or anti‑debug techniques.
- Run
-
Dynamic Sandbox
- Use a commercial sandbox (FireEye, Cuckoo) or an open‑source one (Cuckoo, FireEye).
- Capture network traffic with
tcpdumporwireshark.
-
Malware Hash Lookup
- Query VirusTotal or Hybrid Analysis.
- Example:
curl -s -G "https://www.virustotal.com/api/v3/files/$hash" -H "x-apikey: $VT_API_KEY"
-
Forensic Artifact
- If the binary is a container image, export it and run
docker historyto see layers added after the base image.
- If the binary is a container image, export it and run
4. Removing the Artifact from the Pipeline
Once you confirm maliciousness, purge all traces.
4.1 Delete from Mirrors and Registries
# Example: delete from Nexus
curl -u admin:admin123 -X DELETE "https://nexus.example.com/service/rest/v1/components?repository=repo-name&name=$(basename $artifact)&version=$(version_of $artifact)"
4.2 Rebuild Dependent Artifacts
If the binary was part of a larger build (e.g., a shared library in a Docker image), rebuild the image without it.
# Rebuild Docker image
docker build --no-cache -t myapp:latest .
4.3 Update CI Configuration
- Remove any hard‑coded URLs or version references to the suspect binary.
- Add a new check that rejects any artifact with the suspect hash.
# .gitlab-ci.yml snippet
before_script:
- if grep -q "$(basename $artifact) SUSPECT" /etc/artifact-hashes.txt; then echo "Suspect artifact detected"; exit 1; fi
4.4 Patch the Source
If the binary came from a vendor, contact them. If it was a dependency you control, update to a known good version or fork and patch.
5. Trade‑offs and Practical Caveats
| Trade‑off | What it affects | Mitigation |
|---|---|---|
| Performance | Hash checks and sandbox runs add latency | Cache verified hashes; run sandbox only on first occurrence |
| False positives | Aggressive string checks flag legitimate binaries | Tune regex patterns; whitelist known safe strings |
| Operational overhead | Manual investigation can stall releases | Automate alerts; use a triage queue with clear SLAs |
| Storage | Quarantined artifacts consume space | Set a retention policy (e.g., 30 days) and auto‑purge |
| Complexity | Multiple tools increase maintenance | Containerize the security stack; use a single orchestrator |
Common pitfalls
- Skipping signature verification: some registries disable GPG by default. Enforce signature checks in CI.
- Relying solely on hash: attackers can re‑sign a binary with a stolen key. Combine hash, signature, and behavioral checks.
- Ignoring container base images: a malicious base image can propagate silently. Always pin base images and verify them.
6. Real‑World Example: A 2026 Supply‑Chain Breach
In early 2026, a popular open‑source monitoring agent was compromised. The attacker replaced the binary with a stealthy backdoor that opened a reverse shell to a C2 server. The agent was distributed via a public GitHub release, signed with a stolen GPG key. The vendor’s Docker image pipeline did not verify the key’s trust chain, so the malicious binary was baked into the image and pushed to Docker Hub.
What went wrong?
- Missing trust anchor – The GPG key was not cross‑checked against a trusted key server.
- No sandbox validation – The binary was accepted after a hash check that matched the compromised release.
- No SBOM comparison – The SBOM didn’t flag the new component because the vendor’s SBOM was automatically generated from the same release.
What could have been done?
- Verify the GPG key against a key server and maintain a local trust store.
- Run the binary in a sandbox and monitor for outbound traffic.
- Compare the generated SBOM against a curated list of approved components.
7. Integrating into Your Existing Workflow
Below is a minimal CI pipeline snippet that incorporates the checks discussed. Adjust paths and tool versions to match your environment.
stages:
- fetch
- verify
- build
fetch:
stage: fetch
script:
- curl -L -o /tmp/artifact https://mirror.example.com/artifact-v1.2.3
- mv /tmp/artifact $CI_PROJECT_DIR/artifact
verify:
stage: verify
script:
- expected_hash=$(grep "$(basename $CI_PROJECT_DIR/artifact)" /etc/artifact-hashes.txt | awk '{print $1}')
- actual_hash=$(sha256sum $CI_PROJECT_DIR/artifact | awk '{print $1}')
- if [[ $expected_hash != $actual_hash ]]; then echo "Hash mismatch!"; exit 1; fi
- gpg --verify $CI_PROJECT_DIR/artifact.sig $CI_PROJECT_DIR/artifact || exit 1
- strings $CI_PROJECT_DIR/artifact | grep -E 'http[s]?://|powershell|cmd.exe' && echo "Suspicious strings" && exit 1
- docker run --rm -it --cap-drop ALL --security-opt no-new-privileges alpine:latest /artifact || exit 1
- syft packages $CI_PROJECT_DIR/artifact > artifact.sbom.spdx
- diff -u expected.sbom.spdx artifact.sbom.spdx && echo "SBOM mismatch" && exit 1
build:
stage: build
script:
- docker build -t myapp:latest .
8. Post‑Incident Review
After any incident, perform a “root‑cause” analysis:
- Map the attack surface – which stages allowed the malicious binary to slip through?
- Audit the trust chain – were keys or certificates compromised?
- Update the policy – add missing checks or tighten existing ones.
- Document lessons – store findings in a knowledge base for future reference.
9. Resources for Further Reading
- OWASP Supply Chain Security Project – https://owasp.org/www-project-supply-chain-security/
- NIST SP 800‑161 – Supply Chain Risk Management Practices for Federal Information Systems and Organizations – https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-161.pdf
- MITRE ATT&CK – T1195 (Supply Chain Compromise) – https://attack.mitre.org/techniques/T1195/
See also
- When Your CI Pipeline Ignores Artifact Signatures: How to Enforce GPG Checks Before Deploying to Production
- How to Spot and Block Unauthorized Package Signing in Your CI/CD Pipeline
- Tuning SIEM Correlation Rules to Cut False Positives on Azure AD Sign‑Ins
- Pruning false positives when hunting for lateral movement inside Kubernetes pods.
- Locking Down Service Accounts with Azure AD Conditional Access and Managed Identities