Skip to content

Commit fc967ed

Browse files
JAORMXclaude
andauthored
chore: migrate from Trivy to Grype for vulnerability scanning (#451)
Replace aquasecurity/trivy-action with anchore/scan-action (Grype) v7.3.2 in both build-containers.yml and periodic-security-scan.yml. Update JSON parsing for Grype output format. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8bf9a0d commit fc967ed

2 files changed

Lines changed: 44 additions & 54 deletions

File tree

.github/workflows/build-containers.yml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ jobs:
335335
packages: write
336336
id-token: write # Needed for OIDC token (sigstore)
337337
attestations: write # Needed for attestations
338-
security-events: write # Needed for Trivy SARIF upload
338+
security-events: write # Needed for Grype SARIF upload
339339

340340
steps:
341341
- name: Checkout repository
@@ -444,7 +444,7 @@ jobs:
444444
sbom: true
445445
provenance: true
446446

447-
- name: Build single-platform image for Trivy scan
447+
- name: Build single-platform image for Grype scan
448448
id: build-for-scan
449449
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
450450
with:
@@ -534,20 +534,20 @@ jobs:
534534
# Clean up
535535
rm -f /tmp/security-attestation.json
536536
537-
- name: Run Trivy vulnerability scanner
538-
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
537+
- name: Run Grype vulnerability scanner
538+
id: grype-scan
539+
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
539540
with:
540-
image-ref: local-scan:${{ steps.meta.outputs.server_name }}-${{ steps.meta.outputs.version }}
541-
format: 'sarif'
542-
output: 'trivy-results.sarif'
543-
severity: 'CRITICAL,HIGH,MEDIUM'
541+
image: "local-scan:${{ steps.meta.outputs.server_name }}-${{ steps.meta.outputs.version }}"
542+
severity-cutoff: "medium"
543+
output-format: "sarif"
544544

545-
- name: Upload Trivy results to GitHub Security
545+
- name: Upload Grype results to GitHub Security
546546
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4
547547
if: always()
548548
with:
549-
sarif_file: 'trivy-results.sarif'
550-
category: 'trivy-${{ steps.meta.outputs.server_name }}'
549+
sarif_file: ${{ steps.grype-scan.outputs.sarif }}
550+
category: 'grype-${{ steps.meta.outputs.server_name }}'
551551

552552
- name: Generate image summary
553553
env:
@@ -572,14 +572,14 @@ jobs:
572572
echo "- **Build Provenance**: ✅ Attested" >> $GITHUB_STEP_SUMMARY
573573
echo "- **Security Scan**: ✅ Attested" >> $GITHUB_STEP_SUMMARY
574574
echo "- **Signatures**: ✅ Signed with Sigstore/Cosign" >> $GITHUB_STEP_SUMMARY
575-
echo "- **Trivy Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY
575+
echo "- **Grype Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY
576576
echo "- **Status**: ✅ Built, pushed, signed, and attested" >> $GITHUB_STEP_SUMMARY
577577
echo "- **Tags**:" >> $GITHUB_STEP_SUMMARY
578578
echo " - ${IMAGE_NAME}:${VERSION}" >> $GITHUB_STEP_SUMMARY
579579
echo " - ${IMAGE_NAME}:latest" >> $GITHUB_STEP_SUMMARY
580580
echo "- **Digest**: $DIGEST" >> $GITHUB_STEP_SUMMARY
581581
else
582-
echo "- **Trivy Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY
582+
echo "- **Grype Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY
583583
echo "- **Status**: ✅ Built (not pushed - PR)" >> $GITHUB_STEP_SUMMARY
584584
fi
585585

.github/workflows/periodic-security-scan.yml

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -87,45 +87,39 @@ jobs:
8787
username: ${{ github.actor }}
8888
password: ${{ secrets.GITHUB_TOKEN }}
8989

90-
- name: Run Trivy comprehensive scan
91-
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
90+
- name: Run Grype vulnerability scan (SARIF)
91+
id: grype-scan
92+
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
9293
with:
93-
image-ref: ${{ steps.meta.outputs.image_ref }}
94-
format: 'sarif'
95-
output: 'trivy-results.sarif'
96-
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
97-
scanners: 'vuln,secret,config,license'
98-
timeout: '15m'
94+
image: "${{ steps.meta.outputs.image_ref }}"
95+
severity-cutoff: "low"
96+
output-format: "sarif"
9997

10098
- name: Upload SARIF to GitHub Security
10199
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4
102100
if: always()
103101
with:
104-
sarif_file: 'trivy-results.sarif'
105-
category: 'periodic-trivy-${{ steps.meta.outputs.server_name }}'
102+
sarif_file: ${{ steps.grype-scan.outputs.sarif }}
103+
category: 'periodic-grype-${{ steps.meta.outputs.server_name }}'
106104

107-
- name: Run Trivy for detailed JSON report
108-
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
105+
- name: Run Grype vulnerability scan (JSON)
106+
id: grype-scan-json
107+
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
109108
with:
110-
image-ref: ${{ steps.meta.outputs.image_ref }}
111-
format: 'json'
112-
output: 'trivy-results.json'
113-
severity: 'CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN'
114-
scanners: 'vuln,secret,config,license'
115-
timeout: '15m'
109+
image: "${{ steps.meta.outputs.image_ref }}"
110+
severity-cutoff: "low"
111+
output-format: "json"
116112

117113
- name: Check for critical issues
118114
id: check-critical
119115
run: |
120-
critical=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' trivy-results.json)
121-
high=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' trivy-results.json)
122-
secrets=$(jq '[.Results[]?.Secrets[]?] | length' trivy-results.json)
116+
critical=$(jq '[.matches[]? | select(.vulnerability.severity == "Critical")] | length' ${{ steps.grype-scan-json.outputs.json }})
117+
high=$(jq '[.matches[]? | select(.vulnerability.severity == "High")] | length' ${{ steps.grype-scan-json.outputs.json }})
123118
124119
echo "critical=$critical" >> $GITHUB_OUTPUT
125120
echo "high=$high" >> $GITHUB_OUTPUT
126-
echo "secrets=$secrets" >> $GITHUB_OUTPUT
127121
128-
if [ "$critical" -gt 0 ] || [ "$secrets" -gt 0 ]; then
122+
if [ "$critical" -gt 0 ]; then
129123
echo "should_create_issue=true" >> $GITHUB_OUTPUT
130124
else
131125
echo "should_create_issue=false" >> $GITHUB_OUTPUT
@@ -134,44 +128,40 @@ jobs:
134128
- name: Create issue for critical findings
135129
if: steps.check-critical.outputs.should_create_issue == 'true'
136130
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
131+
env:
132+
GRYPE_JSON_PATH: ${{ steps.grype-scan-json.outputs.json }}
137133
with:
138134
script: |
139135
const fs = require('fs');
140-
const results = JSON.parse(fs.readFileSync('trivy-results.json', 'utf8'));
136+
const results = JSON.parse(fs.readFileSync(process.env.GRYPE_JSON_PATH, 'utf8'));
141137
142138
const critical = ${{ steps.check-critical.outputs.critical }};
143139
const high = ${{ steps.check-critical.outputs.high }};
144-
const secrets = ${{ steps.check-critical.outputs.secrets }};
145140
146141
let body = `## 🚨 Security Scan Alert\n\n`;
147142
body += `A periodic security scan found critical issues in the container image:\n\n`;
148143
body += `- **Image**: \`${{ steps.meta.outputs.image_ref }}\`\n`;
149144
body += `- **Critical vulnerabilities**: ${critical}\n`;
150-
body += `- **High vulnerabilities**: ${high}\n`;
151-
body += `- **Secrets detected**: ${secrets}\n\n`;
145+
body += `- **High vulnerabilities**: ${high}\n\n`;
152146
153147
body += `### Details\n\n`;
154148
body += `See the [Security tab](../../security/code-scanning) for full details.\n\n`;
155149
156150
if (critical > 0) {
157151
body += `#### Critical Vulnerabilities\n\n`;
158-
const criticalVulns = results.Results.flatMap(r =>
159-
(r.Vulnerabilities || []).filter(v => v.Severity === 'CRITICAL').slice(0, 5)
160-
);
152+
const criticalVulns = (results.matches || [])
153+
.filter(m => m.vulnerability.severity === 'Critical')
154+
.slice(0, 5);
161155
162-
for (const vuln of criticalVulns) {
163-
body += `- **${vuln.VulnerabilityID}** in \`${vuln.PkgName}\`: ${vuln.Title || 'No title'}\n`;
156+
for (const match of criticalVulns) {
157+
body += `- **${match.vulnerability.id}** in \`${match.artifact.name}@${match.artifact.version}\`: ${match.vulnerability.description || 'No description'}\n`;
164158
}
165159
166160
if (critical > 5) {
167161
body += `\n_... and ${critical - 5} more. See Security tab for complete list._\n`;
168162
}
169163
}
170164
171-
if (secrets > 0) {
172-
body += `\n⚠️ **${secrets} potential secret(s) detected in the image!**\n`;
173-
}
174-
175165
body += `\n---\n`;
176166
body += `_Automated security scan from [periodic-security-scan workflow](../actions/workflows/periodic-security-scan.yml)_`;
177167
@@ -180,7 +170,7 @@ jobs:
180170
owner: context.repo.owner,
181171
repo: context.repo.repo,
182172
state: 'open',
183-
labels: 'security,trivy',
173+
labels: 'security,grype',
184174
});
185175
186176
const existingIssue = issues.find(issue =>
@@ -203,7 +193,7 @@ jobs:
203193
repo: context.repo.repo,
204194
title: `🚨 Security: Critical issues in ${{ steps.meta.outputs.server_name }} container`,
205195
body: body,
206-
labels: ['security', 'trivy', 'critical']
196+
labels: ['security', 'grype', 'critical']
207197
});
208198
console.log('Created new security issue');
209199
}
@@ -214,8 +204,8 @@ jobs:
214204
with:
215205
name: periodic-scan-${{ steps.meta.outputs.server_name }}
216206
path: |
217-
trivy-results.json
218-
trivy-results.sarif
207+
${{ steps.grype-scan-json.outputs.json }}
208+
${{ steps.grype-scan.outputs.sarif }}
219209
retention-days: 90
220210

221211
summary:
@@ -227,7 +217,7 @@ jobs:
227217
- name: Generate summary
228218
run: |
229219
echo "## Periodic Security Scan Complete" >> $GITHUB_STEP_SUMMARY
230-
echo "- **Scan Type**: Comprehensive (vulnerabilities, secrets, configs, licenses)" >> $GITHUB_STEP_SUMMARY
220+
echo "- **Scan Type**: Vulnerability scan (Grype)" >> $GITHUB_STEP_SUMMARY
231221
echo "- **Severity Levels**: CRITICAL, HIGH, MEDIUM, LOW" >> $GITHUB_STEP_SUMMARY
232222
echo "- **Status**: ${{ needs.scan-images.result }}" >> $GITHUB_STEP_SUMMARY
233223
echo "" >> $GITHUB_STEP_SUMMARY

0 commit comments

Comments
 (0)