Skip to main content

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:

TEXT
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 @v1 or @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:

YAML
## 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:

YAML
- 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.

YAML
## 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:

Bash
## Check for known vulnerabilities in npm packages
npm audit --audit-level=high
## Auto-fix safe updates
npm audit fix

The 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:

Bash
## Install Syft for SBOM generation
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
## Generate SBOM for a container image
syft your-org/payment-service:v1.2.3 -o cyclonedx-json > payment-service-v1.2.3-sbom.json
## Generate SBOM for a directory
syft dir:./src -o spdx-json > sbom-spdx.json

In your CI pipeline:

YAML
- 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 critical

Grype 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:

YAML
- 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:

YAML
name: Security Pipeline
on:
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:

Bash
## Install Cosign — download the release binary directly for Linux CI
## verify checksums against the published release before installing
## Sign image after scan passes
cosign sign --key cosign.key your-registry/payment-service:v1.2.3
## Verify signature before deployment
cosign verify --key cosign.pub your-registry/payment-service:v1.2.3

Add 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.

Note

References and Further Reading

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