feat: initial commit of cve-differential action

This commit is contained in:
Philipp committed 2026-09-13 00:12:26 +02:00
commit bf996103f5
7 files changed
+651

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
# OS / Editor files
.DS_Store
*.swp
*~
.idea/
.vscode/
# Scanned artifacts / temp reports
sboms/
cve-reports/
*.spdx.json
cve-*.json
cve-delta.md
cve-summary.md
node_modules/
+81
View File
@@ -0,0 +1,81 @@
# CVE Differential (`cve-differential`)
A self-contained Gitea and GitHub Actions composite action that extracts multi-platform SBOM attestations from container images, runs vulnerability scans with [Trivy](https://github.com/aquasecurity/trivy), and produces a set-based CVE delta report between a head image and a target baseline image.
---
## ✨ Features
- **Multi-Platform Support**: Automatically detects and extracts SPDX SBOM attestations for `linux/amd64` and `linux/arm64`.
- **Fast Attestation Extraction**: Uses `docker buildx imagetools inspect` to extract SBOMs directly from registry manifest indices in seconds without downloading layer blobs.
- **Graceful Fallbacks**: If SBOM attestations or baseline images are missing, automatically falls back to generating SBOMs directly with Trivy.
- **Zero-Dependency Diff Engine**: Built with native Node.js (`node:fs`, `node:path`) — no Python or external packages required on runner environments.
- **Set-Based Differential Analysis**: Categorizes vulnerabilities into:
- 🔴 **Introduced Vulnerabilities** (newly added in this branch / commit)
- 🟢 **Fixed Vulnerabilities** (present in target baseline, resolved in this branch)
- ⚪ **Unchanged Vulnerabilities** (pre-existing in target baseline)
- **Secure by Default**: Verified Trivy binary downloads against static SHA256 checksum lockfiles and single-step unified caching (binary + vulnerability database).
---
## 📥 Inputs
| Input | Description | Required | Default |
| :--- | :--- | :---: | :--- |
| `head-image` | Head container image reference to scan (e.g. `registry.example.com/repo:git-sha`) | **Yes** | — |
| `target-image` | Target baseline container image reference to compare against (e.g. `registry.example.com/repo:latest`) | **Yes** | — |
| `target-branch` | Target branch name for report headings | No | `main` |
| `trivy-version` | Trivy version to install and verify | No | `0.74.0` |
| `output-delta` | Output path for the CVE delta Markdown report | No | `cve-delta.md` |
| `output-summary` | Output path for the full vulnerability Markdown summary | No | `cve-summary.md` |
---
## 📤 Outputs
| Output | Description |
| :--- | :--- |
| `delta-report` | Path to the generated CVE delta Markdown report |
| `summary-report` | Path to the full vulnerability Markdown summary report |
---
## 🚀 Usage Example
```yaml
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build and Push Head Container Image
uses: docker/build-push-action@v5
with:
context: .
push: true
sbom: true
platforms: linux/amd64,linux/arm64
tags: registry.example.com/my-app:git-${{ github.sha }}
- name: Compare Vulnerabilities (Head vs Target)
uses: Philipp/cve-differential@main
with:
head-image: registry.example.com/my-app:git-${{ github.sha }}
target-image: registry.example.com/my-app:latest
target-branch: ${{ github.base_ref || 'main' }}
- name: Publish CVE Delta to Job Summary
if: always()
run: |
if [ -f cve-delta.md ]; then
cat cve-delta.md >> "$GITHUB_STEP_SUMMARY"
fi
```
---
## 📄 License
MIT
+186
View File
@@ -0,0 +1,186 @@
name: Trivy Multi-Arch CVE Delta Comparison
description: Downloads and verifies Trivy, extracts multi-platform SBOMs, and computes a consolidated set-based CVE delta report
inputs:
head-image:
description: Head container image reference (e.g. host/server/gitea:git-12345678)
required: true
target-image:
description: Target baseline container image reference (e.g. host/server/gitea:latest)
required: true
target-branch:
description: Target branch name for delta heading (e.g. main)
required: false
default: main
trivy-version:
description: Trivy binary version to install and verify
required: false
default: 0.74.0 # trivy-version
output-delta:
description: Output markdown file for CVE delta report
required: false
default: cve-delta.md
output-summary:
description: Output markdown file for full vulnerability summary
required: false
default: cve-summary.md
outputs:
delta-report:
description: Path to CVE delta markdown report
value: ${{ inputs.output-delta }}
summary-report:
description: Path to full vulnerability markdown report
value: ${{ inputs.output-summary }}
runs:
using: composite
steps:
- name: Prepare Cache Keys
id: vars
shell: bash
run: echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
- name: Cache Trivy (Binary & Vulnerability DB)
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cache/trivy-bin
~/.cache/trivy
key: trivy-${{ inputs.trivy-version }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.vars.outputs.date }}
restore-keys: |
trivy-${{ inputs.trivy-version }}-${{ runner.os }}-${{ runner.arch }}-
- name: Install and Verify Trivy
shell: bash
run: |
mkdir -p "$HOME/.cache/trivy-bin" "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
ARCH=$(uname -m)
case "$ARCH" in
x86_64) T_ARCH="64bit" ;;
aarch64|arm64) T_ARCH="ARM64" ;;
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac
tarball="trivy_${{ inputs.trivy-version }}_Linux-${T_ARCH}.tar.gz"
checksums_file="${{ github.action_path }}/checksums.txt"
if [ -f "$HOME/.cache/trivy-bin/${tarball}" ] && (cd "$HOME/.cache/trivy-bin" && grep " ${tarball}$" "$checksums_file" | sha256sum -c -); then
echo "Cached Trivy archive verified against lockfile."
else
echo "Archive missing or checksum mismatch; downloading..."
curl -sfLO "https://github.com/aquasecurity/trivy/releases/download/v${{ inputs.trivy-version }}/${tarball}" --output-dir "$HOME/.cache/trivy-bin"
(cd "$HOME/.cache/trivy-bin" && grep " ${tarball}$" "$checksums_file" | sha256sum -c -)
fi
tar -xzf "$HOME/.cache/trivy-bin/${tarball}" -C "$HOME/.local/bin" trivy
chmod +x "$HOME/.local/bin/trivy"
- name: Extract Multi-Platform SBOMs
shell: bash
run: |
export PATH="$HOME/.local/bin:$PATH"
mkdir -p sboms/head sboms/target
cleanup_file() {
local f="$1"
if [ ! -s "$f" ] || [ "$(cat "$f" 2>/dev/null)" = "null" ]; then
rm -f "$f"
fi
}
# Extract platform SBOMs for head image
echo "Extracting platform SBOMs for head image: ${{ inputs.head-image }}..."
docker buildx imagetools inspect "${{ inputs.head-image }}" \
--format '{{ json (index .SBOM "linux/amd64").SPDX }}' > sboms/head/sbom-amd64.spdx.json 2>/dev/null || true
cleanup_file sboms/head/sbom-amd64.spdx.json
docker buildx imagetools inspect "${{ inputs.head-image }}" \
--format '{{ json (index .SBOM "linux/arm64").SPDX }}' > sboms/head/sbom-arm64.spdx.json 2>/dev/null || true
cleanup_file sboms/head/sbom-arm64.spdx.json
# If single-arch, extract root .SPDX
if [ ! -f sboms/head/sbom-amd64.spdx.json ] && [ ! -f sboms/head/sbom-arm64.spdx.json ]; then
docker buildx imagetools inspect "${{ inputs.head-image }}" \
--format '{{ json .SBOM.SPDX }}' > sboms/head/sbom-amd64.spdx.json 2>/dev/null || true
cleanup_file sboms/head/sbom-amd64.spdx.json
fi
# Fallback to generating head SBOM with Trivy if attestation missing
if [ ! -f sboms/head/sbom-amd64.spdx.json ] && [ ! -f sboms/head/sbom-arm64.spdx.json ]; then
echo "Head image lacks SBOM attestation; generating with Trivy..."
trivy image --format spdx-json --output sboms/head/sbom-amd64.spdx.json "${{ inputs.head-image }}" || true
cleanup_file sboms/head/sbom-amd64.spdx.json
fi
# Extract platform SBOMs for target image
echo "Extracting platform SBOMs for target image: ${{ inputs.target-image }}..."
docker buildx imagetools inspect "${{ inputs.target-image }}" \
--format '{{ json (index .SBOM "linux/amd64").SPDX }}' > sboms/target/sbom-amd64.spdx.json 2>/dev/null || true
cleanup_file sboms/target/sbom-amd64.spdx.json
docker buildx imagetools inspect "${{ inputs.target-image }}" \
--format '{{ json (index .SBOM "linux/arm64").SPDX }}' > sboms/target/sbom-arm64.spdx.json 2>/dev/null || true
cleanup_file sboms/target/sbom-arm64.spdx.json
if [ ! -f sboms/target/sbom-amd64.spdx.json ] && [ ! -f sboms/target/sbom-arm64.spdx.json ]; then
docker buildx imagetools inspect "${{ inputs.target-image }}" \
--format '{{ json .SBOM.SPDX }}' > sboms/target/sbom-amd64.spdx.json 2>/dev/null || true
cleanup_file sboms/target/sbom-amd64.spdx.json
fi
# Fallback to generating target SBOM with Trivy if attestation missing
if [ ! -f sboms/target/sbom-amd64.spdx.json ] && [ ! -f sboms/target/sbom-arm64.spdx.json ]; then
echo "Target image lacks SBOM attestation; attempting to generate with Trivy..."
trivy image --format spdx-json --output sboms/target/sbom-amd64.spdx.json "${{ inputs.target-image }}" || true
cleanup_file sboms/target/sbom-amd64.spdx.json
fi
# Expose extracted head SBOMs to workspace root for workflow artifact uploads
[ -f sboms/head/sbom-amd64.spdx.json ] && cp sboms/head/sbom-amd64.spdx.json sbom-amd64.spdx.json || true
[ -f sboms/head/sbom-arm64.spdx.json ] && cp sboms/head/sbom-arm64.spdx.json sbom-arm64.spdx.json || true
- name: Scan Multi-Platform SBOMs and Generate Delta
shell: bash
run: |
export PATH="$HOME/.local/bin:$PATH"
mkdir -p cve-reports/head cve-reports/target
# Scan each available head SBOM
for f in sboms/head/sbom-*.spdx.json; do
[ -f "$f" ] || continue
arch=$(basename "$f" .spdx.json | sed 's/sbom-//')
echo "Scanning head SBOM for architecture: $arch"
trivy sbom --format json --output "cve-reports/head/cve-${arch}.json" "$f" || true
done
# Scan each available target SBOM
for f in sboms/target/sbom-*.spdx.json; do
[ -f "$f" ] || continue
arch=$(basename "$f" .spdx.json | sed 's/sbom-//')
echo "Scanning target SBOM for architecture: $arch"
trivy sbom --format json --output "cve-reports/target/cve-${arch}.json" "$f" || true
done
# Run multi-arch set-based diff
node "${{ github.action_path }}/cve-diff.js" \
"cve-reports/target" \
"cve-reports/head" \
"${{ inputs.target-branch }}" > "${{ inputs.output-delta }}" || true
# Generate full markdown summary report for primary platform
primary_sbom=""
if [ -f sboms/head/sbom-amd64.spdx.json ]; then
primary_sbom="sboms/head/sbom-amd64.spdx.json"
elif [ -f sboms/head/sbom-arm64.spdx.json ]; then
primary_sbom="sboms/head/sbom-arm64.spdx.json"
fi
if [ -n "$primary_sbom" ]; then
trivy sbom \
--format template \
--template "@${{ github.action_path }}/markdown.tpl" \
--output "${{ inputs.output-summary }}" \
"$primary_sbom" || true
fi
+22
View File
@@ -0,0 +1,22 @@
b8e8834a90b0f28c172498a96d1b91876eb2f09867f02aa314df9a862aa86227 trivy_0.74.0_FreeBSD-64bit.tar.gz
cea47f6624cab9123a6bebc2f6586f3f82b8cd3165fc0ecd21677ba8a4ca0f5c trivy_0.74.0_Linux-32bit.deb
bec3a7aed02d60b6bb2996bb63897f604464c2db0b2b29dfc3cc7ce9959c8aaf trivy_0.74.0_Linux-32bit.rpm
048af0c9597a8e275f60bb9e0543748dbc76e22bdd52456b84ea0fa0cd2a078a trivy_0.74.0_Linux-32bit.tar.gz
cf1e32ec8d4d8823e023096a28cadb14f5b5123ce03f201fb633c5b76aa712dd trivy_0.74.0_Linux-64bit.deb
2331b82ec0ce5529a09fad8ccbfe03987ec9277ff4fbd6e96c6cb0aff8103477 trivy_0.74.0_Linux-64bit.rpm
2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a trivy_0.74.0_Linux-64bit.tar.gz
5665eaa443bfb117c5c4c373254f0ba9e2888985e6233588ce98bb0960f558c3 trivy_0.74.0_Linux-ARM.deb
bdd73ea298ec538ffae3351614a3adefc8c48599fce7943e6376fcfa040c0e85 trivy_0.74.0_Linux-ARM.rpm
99adbe319fa685cb4bff04dd0077e31cb94828338cab5db7f78fe6dcecca8ec1 trivy_0.74.0_Linux-ARM.tar.gz
14fae2604eab589fd544224ef56b8b8a44266872c43b259bd36cfde81428acc8 trivy_0.74.0_Linux-ARM64.deb
bb753f2dacd22417ac7709fdd910a0b017f4bb43330eac8b51c2a90403ae2fd3 trivy_0.74.0_Linux-ARM64.rpm
b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5 trivy_0.74.0_Linux-ARM64.tar.gz
16a1e36b6d405951b36f1e4b31bb7655f8551f1581934fcb2e6e5bdf8c46f6c9 trivy_0.74.0_Linux-PPC64LE.deb
cc7183af95dbab1ae1400fca17b35ca8f9591676a1042144994698cad9cac5a6 trivy_0.74.0_Linux-PPC64LE.rpm
c5aabfe8789d0d43f578ef2d01174884c8ff0f2106c627a220a43f7bf9054959 trivy_0.74.0_Linux-PPC64LE.tar.gz
f8fe56cee9a502e82f05e45465943e3bc6e75de05530ffc7bddf93e65b0de5bb trivy_0.74.0_Linux-s390x.deb
4fb2d5bd1ab5e61917e73cd2936daeae1c5ab9af024321a222437d824bac5579 trivy_0.74.0_Linux-s390x.rpm
516cfe38eaede79c178088265657aad12be6c155271ac68ff728a141961ef197 trivy_0.74.0_Linux-s390x.tar.gz
472816f6888dda689d075c30254d4210b4d1035acf365aa72332f584c2f60485 trivy_0.74.0_macOS-64bit.tar.gz
1caada5e0e2091909357c7525d3aa76f4b660b13821bc143b190c7483e31cc11 trivy_0.74.0_macOS-ARM64.tar.gz
94c40e0696e4b907a74b7b2e1438d5d72ebaca83115817407f568a002d520842 trivy_0.74.0_windows-64bit.zip
Executable
+241
View File
@@ -0,0 +1,241 @@
#!/usr/bin/env node
/**
* Compare two Trivy vulnerability JSON reports (supporting multi-arch) and produce a set-based Markdown diff.
*/
const fs = require('node:fs');
const path = require('node:path');
const SEVERITY_ORDER = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'UNKNOWN'];
const SEVERITY_BADGES = {
CRITICAL: '🔴 CRITICAL',
HIGH: '🟠 HIGH',
MEDIUM: '🟡 MEDIUM',
LOW: '🔵 LOW',
UNKNOWN: '⚪ UNKNOWN',
};
/**
* Loads one or more JSON reports (path, comma-separated list, or directory) and merges findings with arch tracking.
* @param {string} arg
* @returns {Map<string, object>}
*/
function loadReportsForArg(arg) {
const cves = new Map();
const filePaths = [];
for (const part of arg.split(',').map((s) => s.trim())) {
if (!part || !fs.existsSync(part)) continue;
const stat = fs.statSync(part);
if (stat.isDirectory()) {
const entries = fs.readdirSync(part).sort();
for (const entry of entries) {
if (entry.endsWith('.json')) {
filePaths.push(path.join(part, entry));
}
}
} else if (stat.isFile()) {
filePaths.push(part);
}
}
for (const filePath of filePaths) {
let stat;
try {
stat = fs.statSync(filePath);
} catch {
continue;
}
if (stat.size === 0) continue;
const baseName = path.basename(filePath).toLowerCase();
let arch = 'all';
if (baseName.includes('amd64')) {
arch = 'amd64';
} else if (baseName.includes('arm64') || baseName.includes('aarch64')) {
arch = 'arm64';
}
let data;
try {
const content = fs.readFileSync(filePath, 'utf8');
data = JSON.parse(content);
} catch {
continue;
}
if (!data || typeof data !== 'object' || !Array.isArray(data.Results)) {
continue;
}
for (const result of data.Results) {
const target = result.Target || result.Type || 'App';
const vulns = Array.isArray(result.Vulnerabilities) ? result.Vulnerabilities : [];
for (const v of vulns) {
const vid = v.VulnerabilityID || '';
const pkg = v.PkgName || '';
if (!vid || !pkg) continue;
const key = `${vid}:::${pkg}`;
if (!cves.has(key)) {
cves.set(key, { ...v, _target: target, _archs: new Set([arch]) });
} else {
cves.get(key)._archs.add(arch);
}
}
}
}
return cves;
}
/**
* Formats a vulnerability record into a Markdown table row.
* @param {object} v
* @returns {string}
*/
function formatVulnRow(v) {
const sev = v.Severity || 'UNKNOWN';
const badge = SEVERITY_BADGES[sev] || `⚪ ${sev}`;
const vid = v.VulnerabilityID || '';
const url = v.PrimaryURL || '';
const link = url ? `[${vid}](${url})` : vid;
const pkg = v.PkgName || '';
const archs = v._archs || new Set(['all']);
let archBadge;
if ((archs.has('amd64') && archs.has('arm64')) || archs.has('all')) {
archBadge = '`all`';
} else {
archBadge = `\`${Array.from(archs).sort().join(', ')}\``;
}
const installed = v.InstalledVersion || '-';
const fixed = v.FixedVersion || '-';
let title = (v.Title || v.Description || '').replace(/\n/g, ' ').trim();
if (title.length > 80) {
title = title.slice(0, 77) + '...';
}
return `| ${badge} | ${link} | \`${pkg}\` | ${archBadge} | ${installed} | ${fixed} | ${title} |`;
}
/**
* Compares base and branch vulnerabilities and formats a Markdown diff.
* @param {Map<string, object>} baseCves
* @param {Map<string, object>} branchCves
* @param {string} baseName
* @returns {string}
*/
function renderMarkdownDiff(baseCves, branchCves, baseName = 'main') {
if (baseCves.size === 0) {
return [
`### 🔄 Vulnerability Delta (vs \`${baseName}\`)`,
'',
'> [!NOTE]',
`> No baseline scan from \`${baseName}\` was available to compare against.`,
'',
].join('\n');
}
const baseKeys = new Set(baseCves.keys());
const branchKeys = new Set(branchCves.keys());
const newKeys = [];
let unchangedCount = 0;
for (const key of branchKeys) {
if (!baseKeys.has(key)) {
newKeys.push(key);
} else {
unchangedCount++;
}
}
const resolvedKeys = [];
for (const key of baseKeys) {
if (!branchKeys.has(key)) {
resolvedKeys.push(key);
}
}
function sortVulns(a, b) {
const aSev = a.Severity || 'UNKNOWN';
const bSev = b.Severity || 'UNKNOWN';
const aIdx = SEVERITY_ORDER.indexOf(aSev) !== -1 ? SEVERITY_ORDER.indexOf(aSev) : 99;
const bIdx = SEVERITY_ORDER.indexOf(bSev) !== -1 ? SEVERITY_ORDER.indexOf(bSev) : 99;
if (aIdx !== bIdx) return aIdx - bIdx;
const aPkg = a.PkgName || '';
const bPkg = b.PkgName || '';
return aPkg.localeCompare(bPkg);
}
const newVulns = newKeys.map((k) => branchCves.get(k)).sort(sortVulns);
const resolvedVulns = resolvedKeys.map((k) => baseCves.get(k)).sort(sortVulns);
const lines = [
`### 🔄 Vulnerability Delta (vs \`${baseName}\`)`,
'',
'| Metric | Count |',
'| :--- | :--- |',
`| 🚨 **New Vulnerabilities** | ${newVulns.length > 0 ? `🚨 **${newVulns.length}**` : '0'} |`,
`| ✅ **Resolved Vulnerabilities** | ${resolvedVulns.length > 0 ? `✅ **${resolvedVulns.length}**` : '0'} |`,
`| ⚪ **Unchanged Vulnerabilities** | ${unchangedCount} |`,
'',
`#### 🚨 New Vulnerabilities Introduced (${newVulns.length})`,
];
if (newVulns.length > 0) {
lines.push('| Severity | CVE | Package | Arch | Installed | Fixed In | Title |');
lines.push('| :--- | :--- | :--- | :--- | :--- | :--- | :--- |');
for (const v of newVulns) {
lines.push(formatVulnRow(v));
}
} else {
lines.push('✅ No new vulnerabilities introduced in this build.');
}
lines.push('');
lines.push(`#### ✅ Resolved / Removed Vulnerabilities (${resolvedVulns.length})`);
if (resolvedVulns.length > 0) {
lines.push('| Severity | CVE | Package | Arch | Previously Installed | Fixed In | Title |');
lines.push('| :--- | :--- | :--- | :--- | :--- | :--- | :--- |');
for (const v of resolvedVulns) {
lines.push(formatVulnRow(v));
}
} else {
lines.push('⚪ No existing vulnerabilities were resolved.');
}
lines.push('');
return lines.join('\n');
}
function main() {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: cve-diff.js <base_report(s)> <branch_report(s)> [base_name]');
process.exit(1);
}
const baseArg = args[0];
const branchArg = args[1];
const baseName = args[2] || 'main';
const baseCves = loadReportsForArg(baseArg);
const branchCves = loadReportsForArg(branchArg);
const output = renderMarkdownDiff(baseCves, branchCves, baseName);
console.log(output);
}
if (require.main === module) {
main();
}
module.exports = {
loadReportsForArg,
formatVulnRow,
renderMarkdownDiff,
};
+67
View File
@@ -0,0 +1,67 @@
### 🛡️ Security Scan Report
{{- range . }}
{{- $targetName := .Target }}
{{- if eq $targetName "" }}
{{- if eq .Type "gobinary" }}
{{- $targetName = "Go binary" }}
{{- else if .Type }}
{{- $targetName = .Type }}
{{- else if .Class }}
{{- $targetName = .Class }}
{{- else }}
{{- $targetName = "Application / Package" }}
{{- end }}
{{- end }}
{{- $hasFindings := false }}
{{- if .Vulnerabilities }}
{{- $hasFindings = true }}
#### Target: `{{ $targetName }}` (Vulnerabilities)
| Severity | CVE | Package | Installed | Fixed In | Title |
| :--- | :--- | :--- | :--- | :--- | :--- |
{{- $vulns := .Vulnerabilities }}
{{- range $sev := list "CRITICAL" "HIGH" "MEDIUM" "LOW" "UNKNOWN" }}
{{- range $vuln := $vulns }}
{{- if eq $vuln.Severity $sev }}
| {{ if eq $vuln.Severity "CRITICAL" }}🔴 CRITICAL{{ else if eq $vuln.Severity "HIGH" }}🟠 HIGH{{ else if eq $vuln.Severity "MEDIUM" }}🟡 MEDIUM{{ else if eq $vuln.Severity "LOW" }}🔵 LOW{{ else }}⚪ {{ $vuln.Severity }}{{ end }} | [{{ $vuln.VulnerabilityID }}]({{ $vuln.PrimaryURL }}) | `{{ $vuln.PkgName }}` | {{ $vuln.InstalledVersion }} | {{ default "-" $vuln.FixedVersion }} | {{ $vuln.Title }} |
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Misconfigurations }}
{{- $hasFindings = true }}
#### Target: `{{ $targetName }}` (Misconfigurations)
| Severity | Rule ID | Title | Message | Resolution |
| :--- | :--- | :--- | :--- | :--- |
{{- $misconfs := .Misconfigurations }}
{{- range $sev := list "CRITICAL" "HIGH" "MEDIUM" "LOW" "UNKNOWN" }}
{{- range $m := $misconfs }}
{{- if eq $m.Severity $sev }}
| {{ if eq $m.Severity "CRITICAL" }}🔴 CRITICAL{{ else if eq $m.Severity "HIGH" }}🟠 HIGH{{ else if eq $m.Severity "MEDIUM" }}🟡 MEDIUM{{ else if eq $m.Severity "LOW" }}🔵 LOW{{ else }}⚪ {{ $m.Severity }}{{ end }} | [{{ $m.ID }}]({{ $m.PrimaryURL }}) | {{ $m.Title }} | {{ $m.Message }} | {{ default "-" $m.Resolution }} |
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Secrets }}
{{- $hasFindings = true }}
#### Target: `{{ $targetName }}` (Secrets)
| Severity | Rule ID | Title | Lines |
| :--- | :--- | :--- | :--- |
{{- range .Secrets }}
| {{ if eq .Severity "CRITICAL" }}🔴 CRITICAL{{ else if eq .Severity "HIGH" }}🟠 HIGH{{ else if eq .Severity "MEDIUM" }}🟡 MEDIUM{{ else if eq .Severity "LOW" }}🔵 LOW{{ else }}⚪ {{ .Severity }}{{ end }} | {{ .RuleID }} | {{ .Title }} | L{{ .StartLine }}-L{{ .EndLine }} |
{{- end }}
{{- end }}
{{- if not $hasFindings }}
#### Target: `{{ $targetName }}`
✅ No security issues found.
{{- end }}
{{- end }}
+39
View File
@@ -0,0 +1,39 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"customManagers": [
{
"customType": "regex",
"fileMatch": [
"^action\\.ya?ml$"
],
"matchStrings": [
"default:\\s*(?<currentValue>[0-9]+\\.[0-9]+\\.[0-9]+)\\s*#\\s*trivy-version"
],
"depNameTemplate": "aquasecurity/trivy",
"datasourceTemplate": "github-releases",
"versioningTemplate": "semver"
}
],
"packageRules": [
{
"matchDatasources": [
"github-releases"
],
"matchPackageNames": [
"aquasecurity/trivy"
],
"postUpgradeTasks": {
"commands": [
"curl -sfL https://github.com/aquasecurity/trivy/releases/download/v{{{newVersion}}}/trivy_{{{newVersion}}}_checksums.txt -o checksums.txt"
],
"fileFilters": [
"checksums.txt"
],
"executionMode": "update"
}
}
]
}