Trivy, OWASP Dependency-Check, and CodeQL Integration
Trivy, OWASP Dependency-Check, and CodeQL Integration
The Failure
The team ran Trivy on the checkout-service container image and found zero vulnerabilities. They celebrated. Then a penetration tester found a critical CVE in a Go dependency that Trivy missed because the binary was statically compiled with CGO_ENABLED=0 and Trivy’s filesystem scanner was not configured for Go binaries. They needed both container scanning and filesystem scanning to get full coverage.
Each scanner has blind spots. Trivy excels at container images and IaC. CodeQL excels at source-level logic bugs. OWASP Dependency-Check catches CVEs that Trivy might miss in certain language ecosystems. Layer them.
The Mechanism
Scanner Coverage Matrix
| Scanner | Go | Java | Node.js | Container | K8s YAML |
|---|---|---|---|---|---|
| Trivy (image) | ✓ | ✓ | ✓ | ✓ | - |
| Trivy (fs) | ✓ | ✓ | ✓ | - | - |
| Trivy (config) | - | - | - | - | ✓ |
| CodeQL | ✓ | ✓ | ✓ | - | - |
| OWASP DC | - | ✓ | ✓ | - | - |
| Gitleaks | secrets in any language |
SARIF Integration
All scanners output SARIF (Static Analysis Results Interchange Format). GitHub’s Security tab aggregates SARIF from all sources into a unified view.
The Implementation
Per-Service Trivy Configuration
# catalog-service/.github/workflows/security.yml
# HARDENED: Trivy filesystem scan for Node.js dependencies
- name: Trivy filesystem scan
uses: aquasecurity/trivy-action@master
with:
scan-type: "fs"
scan-ref: "."
format: "sarif"
output: "trivy-fs.sarif"
severity: "CRITICAL,HIGH"
exit-code: "1"
vuln-type: "library"
# checkout-service/.github/workflows/security.yml
# HARDENED: Trivy for Go binaries
- name: Build Go binary
run: CGO_ENABLED=0 go build -o checkout-service .
- name: Trivy binary scan
uses: aquasecurity/trivy-action@master
with:
scan-type: "rootfs"
scan-ref: "."
format: "sarif"
output: "trivy-rootfs.sarif"
severity: "CRITICAL,HIGH"
exit-code: "1"
OWASP Dependency-Check for Java Services
# payments-service/.github/workflows/security.yml
# HARDENED: OWASP Dependency-Check for Java
- name: OWASP Dependency-Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: "payments-service"
path: "."
format: "SARIF"
args: >-
--failOnCVSS 7
--suppression suppression.xml
--nvdApiKey ${{ secrets.NVD_API_KEY }}
- name: Upload OWASP DC SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: reports/dependency-check-report.sarif
Suppression File for Known Exceptions
<!-- suppression.xml -->
<!-- HARDENED: Tracked suppressions with expiration -->
<suppressions>
<suppress>
<notes>
False positive: test dependency not in runtime classpath.
Tracking: JIRA-1234. Expires: 2025-06-01.
</notes>
<cve>CVE-2024-12345</cve>
</suppress>
</suppressions>
Scheduled Full Database Scan
# .github/workflows/scheduled-scan.yml
# HARDENED: Weekly full scan with updated vulnerability databases
name: Scheduled Security Scan
on:
schedule:
- cron: "0 6 * * 1"
jobs:
full-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy full scan (all severities)
uses: aquasecurity/trivy-action@master
with:
scan-type: "fs"
format: "sarif"
output: "trivy-full.sarif"
severity: "CRITICAL,HIGH,MEDIUM"
exit-code: "0" # Advisory only for scheduled
- name: Upload results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: "trivy-full.sarif"
The Gate
PR scans gate on CRITICAL and HIGH. Scheduled scans report on MEDIUM as advisories. This two-tier approach prevents alert fatigue while catching the most dangerous vulnerabilities at merge time.
The Recovery
Scanner misses a vulnerability: No single scanner catches everything. Layer scanners. Run at least two: one for container images and one for source/dependencies.
NVD API rate limiting breaks OWASP DC: Cache the NVD database. Use --nvdDatafeedUrl to point to a locally hosted mirror or use the NVD API key with --nvdApiKey.
SARIF upload fails with “too many results”: Filter by severity before uploading. The GitHub Security tab has a limit of ~5000 results per SARIF file.