Shift-Left Security: SBOM & Supply Chain Scans
Supply chain attacks are rising, and some scanners meant to catch them have been compromised. Here's how to add SBOM and dependency scanning safely.
In 2020, a compromised update to SolarWinds' Orion software reached roughly 18,000 customers, including multiple US government agencies. The attackers didn't exploit a zero-day vulnerability. They compromised the build pipeline and injected malicious code that was then signed, packaged, and distributed by the vendor itself.
The package looked legitimate because it was built by the legitimate build system. This is a supply chain attack, and software supply chain attacks have grown sharply in recent years. I'd recommend verifying the exact percentage growth figure against a primary source like a recent Sonatype or OWASP State of the Software Supply Chain report rather than relying on any single statistic quoted secondhand, since these numbers vary by methodology and year. The defense is knowing exactly what is in your software — and that starts with an SBOM.
Six years after SolarWinds, in March 2026, the exact same class of attack hit the tools built to prevent it: Aqua Security's Trivy, one of the most widely used open-source container scanners, was itself compromised and used to steal CI/CD secrets from the pipelines that trusted it. More on that below — it changes how you should think about your scanning stack, not just your application dependencies.
What an SBOM Is and Why It Matters
An SBOM (Software Bill of Materials) is a machine-readable inventory of every component in your software: every library, every transitive dependency, every version number, every license.
Without an SBOM, when Log4Shell dropped in December 2021, teams had no systematic way to answer the question "do we have Log4j in our codebase?" They ran grep across repos, checked package.json files manually, and asked developers. It took days to know for certain.
With an SBOM generated at build time, the answer takes thirty seconds: query the SBOM for Log4j, get a list of every service version, fix them in order of exposure.
This is part of why the US government requires SBOMs for software sold to federal agencies, and why enterprise customers increasingly require them before software procurement.
The Four Layers of Supply Chain Security
A complete shift-left security setup has four distinct layers:
Code commit | v [Layer 1: SAST - static code analysis]Build | v [Layer 2: Dependency scanning]Package | v [Layer 3: SBOM generation + container scanning]Deploy | v [Layer 4: Runtime policy enforcement]Each layer catches a different class of problem. None of them alone is sufficient — and as the Trivy incident showed, the tools running these layers are themselves part of your attack surface.
The Trivy Compromise: What Happened and Why It Matters Here
Before walking through pipeline integration, it's worth understanding what happened, because it directly affects how you should configure any scanning tool — not just Trivy.
On March 19, 2026, a threat actor group tracked as TeamPCP used credentials retained from an earlier, incompletely-remediated incident to compromise Aqua Security's release infrastructure. Between roughly March 19 and March 23, 2026, they force-pushed 76 of 77 version tags in the aquasecurity/trivy-action GitHub Action and all 7 tags in aquasecurity/setup-trivy to point at malicious commits, and separately published a compromised Trivy binary (v0.69.4) and poisoned Docker Hub images. The injected code ran before the real scanner executed, so pipelines appeared to complete normally while a credential stealer harvested AWS/GCP/Azure keys, Kubernetes tokens, and other pipeline secrets underneath.
I'm confident on the broad timeline and mechanism based on multiple corroborating vendor writeups (Aqua Security, Docker, Microsoft, CrowdStrike, Wiz), but you should verify the exact affected version list and whether your own pipeline was in the exposure window directly against Aqua's security advisory rather than from this summary.
Aqua Security and Docker removed the malicious artifacts from all distribution channels by around March 23, 2026. The practical lesson isn't "stop using Trivy" — it's that any tool with pipeline-level access, security scanners very much included, needs the same supply-chain hardening you'd apply to a production dependency:
- Pin GitHub Actions to full, immutable commit SHAs, never mutable version tags like
@v1or@latest - Rotate any credentials that were live in a pipeline during a known exposure window for any tool you use
- Treat "it's a security tool" as a reason for more scrutiny of its supply chain, not less
Layer 1: SAST in the CI Pipeline
Static Application Security Testing (SAST) analyzes code for security vulnerabilities without running it. Add it to your CI pipeline as a step that runs on every pull request:
## GitHub Actions SAST with Semgrep — pin to a commit SHA, not @v1- name: Run Semgrep SAST uses: semgrep/semgrep-action@<pinned-sha> with: config: >- p/owasp-top-ten p/nodejs p/secrets env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}For secrets scanning — preventing API keys, database passwords, and tokens from being committed — add GitLeaks:
- name: Detect Secrets with GitLeaks uses: gitleaks/gitleaks-action@<pinned-sha> env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Block PRs from merging if either of these steps finds a HIGH or CRITICAL severity finding. Be cautious with auto-blocking on MEDIUM — the false positive rate is high enough to frustrate developers.
Layer 2: Dependency Scanning
Every npm install or pip install brings in dozens of transitive dependencies you didn't explicitly choose. Any of them can have known vulnerabilities.
## Trivy dependency scan in CI — pin to a commit SHA after the 2026 incident- name: Scan Dependencies with Trivy uses: aquasecurity/trivy-action@<pinned-sha> with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' exit-code: '1'For Node.js projects, also run:
## Check for known vulnerabilities in npm packagesnpm audit --audit-level=high ## Auto-fix safe updatesnpm audit fixThe philosophy: fail CI on CRITICAL and HIGH severity CVEs with a fix available. Warn on HIGH with no fix available — don't block, since a CVE with no patch just means you need to watch it. Let MEDIUM and LOW through with a report.
Layer 3: SBOM Generation
Generate the SBOM at build time, when you know exactly what went into the artifact. The two dominant SBOM formats are SPDX and CycloneDX. CycloneDX tends to be preferred for security-focused workflows with stronger vulnerability and VEX tooling support, while SPDX is often the default for license-compliance-focused workflows; most compliance frameworks accept either.
Syft (from Anchore) remains the most widely used standalone SBOM generator as of mid-2026, covering a broad range of package ecosystems with richer component metadata than most all-in-one scanners produce as a side effect:
## Install Syft for SBOM generationcurl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin ## Generate SBOM for a container imagesyft your-org/payment-service:v1.2.3 -o cyclonedx-json > payment-service-v1.2.3-sbom.json ## Generate SBOM for a directorysyft dir:./src -o spdx-json > sbom-spdx.jsonIn your CI pipeline:
- name: Generate SBOM run: | syft ${{ env.IMAGE_NAME }}:${{ github.sha }} -o cyclonedx-json > sbom.json- name: Upload SBOM as Artifact uses: actions/upload-artifact@<pinned-sha> with: name: sbom-${{ github.sha }} path: sbom.json - name: Scan SBOM for Vulnerabilities run: | grype sbom:./sbom.json --fail-on criticalGrype takes the SBOM as input and checks every component against vulnerability databases. This is faster than scanning the image directly and produces more precise results, since it's matching against a known component list rather than re-deriving one.
Layer 4: Container Image Scanning
Before the image is pushed to your registry, scan it for OS-level vulnerabilities. Trivy remains a strong, widely adopted choice for this — the March 2026 incident was a supply-chain compromise of Trivy's release pipeline, not a flaw in what the scanner itself checks for, and Aqua's remediation removed the malicious artifacts. The main change to your practice should be pinning it (and every third-party action) to a commit SHA rather than blindly trusting it forever:
- name: Build Container Image run: docker build -t payment-service:${{ github.sha }} . - name: Scan Container Image uses: aquasecurity/trivy-action@<pinned-sha> with: image-ref: 'payment-service:${{ github.sha }}' format: 'table' severity: 'CRITICAL,HIGH' exit-code: '1' - name: Push to Registry if: success() run: docker push payment-service:${{ github.sha }}The key pattern: scan before push. If the scan fails, the image never reaches your registry, and it can never be deployed.
Complete Pipeline Integration
Here is the complete GitHub Actions workflow for a production microservice at a company like Razorpay, with every third-party action pinned to a commit SHA rather than a mutable tag:
name: Security Pipelineon: push: branches: [main] pull_request: branches: [main] jobs: security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@<pinned-sha> - name: Secret Detection uses: gitleaks/gitleaks-action@<pinned-sha> - name: SAST Scan uses: semgrep/semgrep-action@<pinned-sha> with: config: p/owasp-top-ten - name: Dependency Audit run: npm audit --audit-level=high - name: Build Image run: docker build -t $IMAGE:${{ github.sha }} . - name: Generate SBOM run: syft $IMAGE:${{ github.sha }} -o cyclonedx-json > sbom.json - name: Vulnerability Scan run: grype sbom:./sbom.json --fail-on critical - name: Upload SBOM uses: actions/upload-artifact@<pinned-sha> with: name: sbom path: sbom.json - name: Push Image if: github.ref == 'refs/heads/main' && success() run: docker push $IMAGE:${{ github.sha }}Signing Images with Cosign
Once you are generating and scanning SBOMs, the next step is signing your images so consumers can verify they haven't been tampered with:
## Install Cosign — download the release binary directly for Linux CI## verify checksums against the published release before installing ## Sign image after scan passescosign sign --key cosign.key your-registry/payment-service:v1.2.3 ## Verify signature before deploymentcosign verify --key cosign.pub your-registry/payment-service:v1.2.3Add verification to your Kubernetes admission webhook (using Kyverno or OPA Gatekeeper) so unsigned images are rejected at deploy time, not just at build time.
Production Implementation Guidelines
Start with secret scanning and dependency auditing — these have the highest signal-to-noise ratio and the fewest false positives. Get engineers comfortable with security failures in CI before adding SBOM generation and image signing.
Store SBOMs in a dedicated artifact store with a consistent naming convention, such as sbom/{service}/{version}.json in a dedicated S3 bucket, or feed them into an aggregation platform like OWASP Dependency-Track. When a new CVE drops, you need to query your SBOMs to find which services are affected — this is only possible if SBOMs are centrally stored and named consistently.
Set a policy: any CRITICAL CVE with a fix available must be patched within 72 hours. Any CRITICAL CVE with no fix available gets a documented exception with a remediation plan. Make this policy visible in your security dashboard, not buried in CI logs.
Pin every third-party GitHub Action — scanners included — to a full commit SHA, and periodically audit your workflow files for any action still referencing a mutable tag. The 2026 Trivy incident specifically exploited teams that trusted @v1-style tags.
Trade-offs and Alternatives
| Tool | What It Does | Note |
|---|---|---|
| Syft | SBOM generation | Broadest ecosystem coverage, richest metadata |
| Grype | SBOM vulnerability scan | Pairs naturally with Syft |
| Trivy | Image + fs scanning, also generates SBOMs | Pin to a commit SHA after the March 2026 incident |
| Semgrep | SAST | Open-source rule sets available |
| Dependency-Track | SBOM aggregation at scale | Ingests SBOMs from Syft or Trivy, not a generator itself |
The open-source stack (Syft + Grype + Semgrep, with or without Trivy) covers all four layers at no license cost. Managed platforms like Snyk add centralized dashboards and developer-friendly remediation advice on top — worth it for teams that want managed tooling rather than assembling and hardening the pipeline themselves.
NoteReferences and Further Reading
- Syft Documentation - SBOM generation tool
- Grype Documentation - Vulnerability scanner for SBOMs
- CISA SBOM Resources - US government SBOM guidance
- Cosign - Container image signing
- Trivy Security Advisory GHSA-69fq-xp46-6x23 - Official incident details for the March 2026 compromise
Frequently Asked Questions
Does the March 2026 Trivy compromise mean teams should stop using Trivy?
No — the practical lesson from Aqua Security's own remediation is to pin any third-party GitHub Action, Trivy included, to a full commit SHA rather than a mutable tag like @v1, and to treat security tools with the same supply-chain scrutiny as any other dependency, not to abandon a scanner that itself did nothing wrong in what it checks for.
What's the difference between SPDX and CycloneDX SBOM formats?
CycloneDX tends to be preferred for security-focused workflows with stronger vulnerability and VEX tooling support, while SPDX is often the default for license-compliance-focused workflows — most compliance frameworks accept either format.
Why generate an SBOM at build time instead of scanning a running container later?
Because at build time you know exactly what went into the artifact, and the resulting SBOM can be queried in seconds when a new CVE like Log4Shell drops — without one, teams have historically resorted to manually grepping repositories, which took days to get a confident answer.
Should a CI pipeline block merges on every dependency vulnerability finding?
No — the recommended policy is to fail CI on CRITICAL and HIGH severity CVEs that have an available fix, warn (don't block) on HIGH findings with no fix available, and let MEDIUM and LOW severity findings through with a report, since auto-blocking on lower severities has a high enough false-positive rate to frustrate developers.
What does signing container images with Cosign actually protect against?
It lets consumers cryptographically verify that an image hasn't been tampered with since it was built and scanned — pairing this with a Kubernetes admission webhook (via Kyverno or OPA Gatekeeper) means unsigned images can be rejected at deploy time, not just flagged at build time.
Discussion0