Skip to content

Trivy Vulnerability + License Scan (PR + Scheduled) #51

Trivy Vulnerability + License Scan (PR + Scheduled)

Trivy Vulnerability + License Scan (PR + Scheduled) #51

Workflow file for this run

name: Trivy Vulnerability + License Scan (PR + Scheduled)
# Trigger conditions: PR/push on main/3.0 branches, daily scheduled scan (00:00 UTC)
on:
workflow_dispatch:
pull_request:
branches: [ main, 3.0 ]
types: [ opened, synchronize, reopened ]
push:
branches: [ main, 3.0 ]
schedule:
- cron: '0 0 * * *' # Daily scan at 00:00 UTC (8:00 Beijing Time)
jobs:
trivy-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # Required for uploading SARIF to GitHub Security
steps:
# Step 1: Check out repository code
- name: Checkout code
uses: actions/checkout@v4
# Step 2: Generate SBOM using Trivy
- name: Generate SBOM
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'spdx-json'
output: 'sbom-spdx.json'
skip-dirs: 'target'
# Step 3: Filter SBOM - Remove test-only dependencies
- name: Filter SBOM (Exclude Test Dependencies)
run: |
# Filter out test-only dependencies from SBOM
# Match by package name since externalRefs might not exist
jq '
del(.packages[] |
select(
.name == "org.lz4:lz4-java" or
.name == "com.ibm.icu:icu4j" or
.name == "net.java.dev.jna:jna" or
.name == "org.javassist:javassist" or
.name == "junit:junit" or
.name == "org.junit.platform:junit-platform-commons" or
.name == "org.opentest4j:opentest4j" or
.name == "org.apiguardian:apiguardian-api"
)
)
' sbom-spdx.json > sbom-filtered.json
# Show statistics
TOTAL=$(jq '.packages | length' sbom-spdx.json)
FILTERED=$(jq '.packages | length' sbom-filtered.json)
REMOVED=$((TOTAL - FILTERED))
echo "📊 SBOM Statistics:"
echo " Total packages: $TOTAL"
echo " Removed (test-only): $REMOVED"
echo " Remaining (production): $FILTERED"
echo ""
echo "✅ Filtered SBOM created: sbom-filtered.json"
# Validate filtered SBOM
if [ ! -s sbom-filtered.json ]; then
echo "❌ Error: sbom-filtered.json is empty!"
exit 1
fi
# Show sample packages
echo ""
echo "📦 Sample production packages:"
jq -r '.packages[0:3] | .[].name' sbom-filtered.json
# Step 3.5: Output SBOM content for debugging
- name: Output SBOM Content (Debug)
run: |
echo "===== Original SBOM (first 100 lines) ====="
head -100 sbom-spdx.json
echo ""
echo "===== Filtered SBOM (first 100 lines) ====="
head -100 sbom-filtered.json
echo ""
echo "===== Filtered SBOM package list ====="
jq -r '.packages[] | .name' sbom-filtered.json | sort
# Step 4: Trivy Vulnerability Scan on filtered SBOM (JSON format)
- name: Trivy Vulnerability Scan (SBOM)
id: vuln_scan
continue-on-error: true
uses: aquasecurity/trivy-action@master
with:
scan-type: 'sbom'
scan-ref: 'sbom-filtered.json'
format: 'json'
output: 'trivy-vuln.json'
severity: 'CRITICAL,HIGH,MEDIUM'
# Step 4.5: Debug - Output Trivy JSON structure
- name: Debug Trivy JSON
run: |
echo "===== Trivy JSON structure (first 200 lines) ====="
head -200 trivy-vuln.json
echo ""
echo "===== Check Results array ====="
jq '.Results | length' trivy-vuln.json
echo ""
echo "===== Sample result ====="
jq '.Results[0]' trivy-vuln.json 2>/dev/null || echo "No results"
# Step 4.6: Convert Trivy JSON to SARIF
- name: Convert JSON to SARIF
run: |
# First, check if there are any vulnerabilities
VULN_COUNT=$(jq '.Results | length' trivy-vuln.json)
echo "Found $VULN_COUNT vulnerabilities"
if [ "$VULN_COUNT" -eq 0 ]; then
# Create empty SARIF if no vulnerabilities
jq '{
version: "2.1.0",
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
runs: [
{
tool: {
driver: {
name: "Trivy",
version: "0.68.1",
informationUri: "https://github.com/aquasecurity/trivy"
}
},
results: []
}
]
}' > trivy-vuln.sarif
else
# For dependency vulnerabilities, use pom.xml as the location
# This way GitHub Security can find the file
jq '{
version: "2.1.0",
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
runs: [
{
tool: {
driver: {
name: "Trivy",
informationUri: "https://github.com/aquasecurity/trivy",
rules: [
.Results[] | {
id: (.VulnerabilityID // "CVE-UNKNOWN"),
name: (.VulnerabilityID // "CVE-UNKNOWN"),
shortDescription: {
text: (.Title // .Description // "Unknown vulnerability")
},
fullDescription: {
text: (.Description // .Title // "No description available")
}
} | if (.PrimaryURL // null) != null then . + {helpUri: .PrimaryURL} else . end
] | unique_by(.id)
}
},
results: [
.Results[] | {
ruleId: (.VulnerabilityID // "CVE-UNKNOWN"),
level: (if .Severity == "CRITICAL" then "error" elif .Severity == "HIGH" then "error" else "warning" end),
message: {
text: "\(.PkgName // "unknown")@\(.InstalledVersion // "unknown") has \(.Severity // "UNKNOWN") severity vulnerability: \(.Title // .Description // "No description")"
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: "pom.xml",
description: {
text: "Dependency: \(.PkgName // "unknown")@\(.InstalledVersion // "unknown")"
}
},
region: {
startLine: 1,
endLine: 1
}
}
}
],
properties: {
severity: .Severity,
package: (.PkgName // "unknown"),
version: (.InstalledVersion // "unknown"),
fixedVersion: (.FixedVersion // null),
primaryURL: (.PrimaryURL // null),
vulnerabilityID: (.VulnerabilityID // null)
}
}
]
}
]
}' trivy-vuln.json > trivy-vuln.sarif
fi
# Validate SARIF
echo "SARIF preview:"
head -100 trivy-vuln.sarif
# Step 5: Trivy License Scan on filtered SBOM
- name: Trivy License Scan (SBOM)
continue-on-error: true
uses: aquasecurity/trivy-action@master
with:
scan-type: 'sbom'
scan-ref: 'sbom-filtered.json'
format: 'table'
severity: 'CRITICAL,HIGH,UNKNOWN'
scanners: 'license'
# Step 6: Upload SARIF to GitHub Security
- name: Upload Results to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: 'trivy-vuln.sarif'
# Step 7: Upload SBOM artifacts
- name: Upload SBOM Artifacts
uses: actions/upload-artifact@v4
with:
name: sbom-files
path: |
sbom-spdx.json
sbom-filtered.json
retention-days: 30