Skip to content

Commit ae33040

Browse files
Merge pull request #18 from grafana/trivy-diff
Add Trivy diff
2 parents 1c0d089 + 9e8bbdd commit ae33040

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed

trivy/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
### Trivy Diff
2+
3+
Use this as a reusable workflow like:
4+
5+
```
6+
name: Trivy-diff
7+
8+
on:
9+
pull_request:
10+
types: [synchronize, opened, reopened]
11+
paths:
12+
# Python
13+
- '**/Pipfile.lock'
14+
- '**/poetry.lock'
15+
- '**/requirements.txt'
16+
# PHP
17+
- '**/composer.lock'
18+
# Node.js
19+
- '**/package-lock.json'
20+
- '**/yarn.lock'
21+
- '**/package.json'
22+
# Go
23+
- '**/go.sum'
24+
jobs:
25+
trivy-scan:
26+
runs-on: ubuntu-latest
27+
28+
steps:
29+
# Use the Trivy Diff reusable workflow
30+
- uses: grafana/security-github-actions/trivy/@trivy-diff
31+
with:
32+
github-token: ${{ secrets.GITHUB_TOKEN }}
33+
severities: "CRITICAL,HIGH"
34+
```
35+
36+
Then this workflow will only be ran on PRs and only if a manifest file has been modified.

trivy/action.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: Trivy diff
2+
description: Compare Trivy scan results between two branches
3+
inputs:
4+
github-token:
5+
description: "GitHub token for posting the comment"
6+
required: true
7+
severities:
8+
description: "Comma-separated list of severity levels to consider (e.g., CRITICAL,HIGH,MEDIUM,LOW)"
9+
required: false
10+
default: "CRITICAL,HIGH"
11+
12+
runs:
13+
using: "composite"
14+
steps:
15+
- name: "Checkout Repository"
16+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
17+
with:
18+
fetch-depth: 0
19+
20+
- name: "Checkout Target Branch"
21+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
22+
with:
23+
ref: ${{ github.base_ref }}
24+
25+
- name: "Scan Target Branch"
26+
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # 0.29.0
27+
with:
28+
scan-type: "fs"
29+
scanners: vuln
30+
timeout: 30s
31+
ignore-unfixed: true
32+
version: v0.58.0
33+
hide-progress: true
34+
output: base_trivy_report.json
35+
format: json
36+
scan-ref: .
37+
severity: ${{ inputs.severities }}
38+
39+
- name: Checkout current commit
40+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
41+
with:
42+
ref: ${{ github.sha }}
43+
clean: false
44+
45+
- name: "Scan Current Branch"
46+
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # 0.29.0
47+
with:
48+
scan-type: "fs"
49+
scanners: vuln
50+
timeout: 30s
51+
ignore-unfixed: true
52+
version: v0.58.0
53+
hide-progress: true
54+
output: main_trivy_report.json
55+
format: json
56+
scan-ref: .
57+
severity: ${{ inputs.severities }}
58+
skip-setup-trivy: true
59+
env:
60+
TRIVY_SKIP_DB_UPDATE: true
61+
TRIVY_SKIP_JAVA_DB_UPDATE: true
62+
63+
- name: "Run Trivy Diff"
64+
id: trivy-diff
65+
run: |
66+
node $GITHUB_ACTION_PATH/trivy-diff.js base_trivy_report.json main_trivy_report.json > output.txt
67+
shell: bash
68+
69+
- name: "Comment the Trivy diff"
70+
env:
71+
GITHUB_TOKEN: ${{ inputs.github-token }}
72+
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
73+
run: |
74+
output=$(cat output.txt)
75+
if [ "$output" == "No new vulnerabilities found." ]; then
76+
echo "No new vulnerabilities found."
77+
exit 0
78+
else
79+
comment=$(echo -e "### New vulnerabilities introduced in branch $BRANCH_NAME compared to ${{ github.base_ref }}\n\n" ; jq -r '.[] | "* \(.VulnerabilityID), Severity: \(.Severity), Package: \(.PkgName), Installed: \(.InstalledVersion), Fixed: \(.FixedVersion // "N/A")"' output.txt)
80+
gh pr comment ${{ github.event.pull_request.number }} --body "$comment"
81+
fi
82+
shell: bash

trivy/trivy-diff.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const fs = require('fs');
2+
3+
// Vulnerability data structure
4+
class Vulnerability {
5+
constructor(VulnerabilityID, PkgName, InstalledVersion, FixedVersion = "", Severity) {
6+
this.VulnerabilityID = VulnerabilityID;
7+
this.PkgName = PkgName;
8+
this.InstalledVersion = InstalledVersion;
9+
this.FixedVersion = FixedVersion;
10+
this.Severity = Severity;
11+
}
12+
}
13+
14+
// Parse Trivy JSON output
15+
function parseTrivyOutput(file) {
16+
try {
17+
const data = fs.readFileSync(file, 'utf8');
18+
return JSON.parse(data);
19+
} catch (err) {
20+
console.error(`Error reading file ${file}: ${err.message}`);
21+
process.exit(1);
22+
}
23+
}
24+
25+
// Extract vulnerabilities into a map
26+
function extractVulnerabilities(result) {
27+
const vulns = {};
28+
result.Results.forEach(res => {
29+
if (res.Vulnerabilities && Array.isArray(res.Vulnerabilities)) {
30+
res.Vulnerabilities.forEach(vuln => {
31+
const vulnKey = `${vuln.VulnerabilityID}-${vuln.PkgName}`;
32+
vulns[vulnKey] = new Vulnerability(vuln.VulnerabilityID, vuln.PkgName, vuln.InstalledVersion, vuln.FixedVersion, vuln.Severity);
33+
});
34+
}
35+
});
36+
return vulns;
37+
}
38+
39+
// Compare vulnerabilities and return new vulnerabilities in the test file
40+
function compareVulnerabilities(testVulns, mainVulns) {
41+
const newVulns = [];
42+
for (const key in testVulns) {
43+
if (!mainVulns[key]) {
44+
newVulns.push(testVulns[key]);
45+
}
46+
}
47+
return newVulns;
48+
}
49+
50+
// Main function
51+
function main() {
52+
if (process.argv.length < 4) {
53+
console.log("Usage: node trivy-diff.js <file_1> <file_2>");
54+
process.exit(1);
55+
}
56+
57+
const file1 = process.argv[2];
58+
const file2 = process.argv[3];
59+
60+
// Parse the Trivy scan results
61+
const mainResult = parseTrivyOutput(file1);
62+
const testResult = parseTrivyOutput(file2);
63+
64+
// Extract vulnerabilities
65+
const mainVulns = extractVulnerabilities(mainResult);
66+
const testVulns = extractVulnerabilities(testResult);
67+
68+
// Compare vulnerabilities
69+
const newVulns = compareVulnerabilities(testVulns, mainVulns);
70+
71+
// Output new vulnerabilities as JSON
72+
if (newVulns.length === 0) {
73+
console.log("No new vulnerabilities found.");
74+
} else {
75+
console.log(JSON.stringify(newVulns, null, 2));
76+
}
77+
}
78+
79+
// Run the main function
80+
main();

0 commit comments

Comments
 (0)