mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-21 05:13:00 +00:00
101 lines
4.2 KiB
JavaScript
101 lines
4.2 KiB
JavaScript
const fs = require('fs');
|
|
|
|
// Configuration from environment variables
|
|
const REPORT_FILE = process.env.GRYPE_REPORT_FILE || 'grype-report.json';
|
|
const IMAGE_NAME = process.env.IMAGE_NAME || 'container-image';
|
|
const GITHUB_SHA = process.env.GITHUB_SHA || 'unknown';
|
|
const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY || '';
|
|
const GITHUB_RUN_ID = process.env.GITHUB_RUN_ID || '';
|
|
const CUTOFF = process.env.CUTOFF || 'high';
|
|
|
|
// A cutoff of 'critical' blocks only on critical; anything else blocks on high and above
|
|
const blocking = CUTOFF === 'critical' ? ['Critical'] : ['Critical', 'High'];
|
|
|
|
const report = JSON.parse(fs.readFileSync(REPORT_FILE, 'utf-8'));
|
|
const matches = Array.isArray(report.matches) ? report.matches : [];
|
|
const ignored = Array.isArray(report.ignoredMatches) ? report.ignoredMatches : [];
|
|
|
|
const counts = { Critical: 0, High: 0, Medium: 0, Low: 0, Negligible: 0, Unknown: 0 };
|
|
const blockers = new Map();
|
|
|
|
for (const match of matches) {
|
|
const severity = match.vulnerability.severity;
|
|
if (counts[severity] !== undefined) {
|
|
counts[severity]++;
|
|
}
|
|
if (blocking.includes(severity)) {
|
|
const artifact = match.artifact;
|
|
const fixedIn = (match.vulnerability.fix && match.vulnerability.fix.versions || []).join(', ');
|
|
// Same CVE can match several install paths of one package; collapse them
|
|
blockers.set(`${match.vulnerability.id}|${artifact.name}`, {
|
|
id: match.vulnerability.id,
|
|
severity,
|
|
name: artifact.name,
|
|
version: artifact.version,
|
|
fixedIn
|
|
});
|
|
}
|
|
}
|
|
|
|
const ignoredBlocking = ignored.filter(m => blocking.includes(m.vulnerability.severity)).length;
|
|
const shortSha = GITHUB_SHA.substring(0, 7);
|
|
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
|
|
|
|
const severityConfig = {
|
|
Critical: { icon: '🔴', label: 'Critical' },
|
|
High: { icon: '🟠', label: 'High' },
|
|
Medium: { icon: '🟡', label: 'Medium' },
|
|
Low: { icon: '🔵', label: 'Low' }
|
|
};
|
|
|
|
let comment = '## 🔎 Container Security Scan (Grype)\n\n';
|
|
comment += `**Image:** \`${IMAGE_NAME}:${shortSha}\`\n`;
|
|
comment += `**Last scan:** ${timestamp}\n\n`;
|
|
|
|
if (blockers.size === 0) {
|
|
comment += '### ✅ Nothing Blocking\n\n';
|
|
comment += `No findings at **${blocking.join(' or ').toLowerCase()}** severity.\n`;
|
|
} else {
|
|
comment += `### ⚠️ ${blockers.size} Finding(s) Blocking This PR\n\n`;
|
|
comment += '| Severity | CVE | Package | Installed | Fixed in |\n';
|
|
comment += '|---|---|---|---|---|\n';
|
|
|
|
const order = { Critical: 0, High: 1 };
|
|
const rows = [...blockers.values()].sort((a, b) =>
|
|
(order[a.severity] - order[b.severity]) || a.name.localeCompare(b.name));
|
|
|
|
for (const row of rows) {
|
|
const config = severityConfig[row.severity];
|
|
comment += `| ${config.icon} ${config.label} | \`${row.id}\` | \`${row.name}\` | ${row.version} | ${row.fixedIn || '—'} |\n`;
|
|
}
|
|
|
|
comment += '\n**What to do:**\n';
|
|
comment += '- Upgrade the package to the version in the "Fixed in" column.\n';
|
|
comment += '- If it is pinned by another dependency, or the fix is otherwise out of reach, add it to `.grype.yaml` **with the reason**.\n';
|
|
comment += '- Findings with no published fix never appear here: the scan runs with `only-fixed`, so it reports only what can actually be acted on.\n';
|
|
}
|
|
|
|
const otherCounts = Object.entries(counts)
|
|
.filter(([severity, count]) => !blocking.includes(severity) && count > 0)
|
|
.map(([severity, count]) => `${severity.toLowerCase()}: ${count}`);
|
|
|
|
if (otherCounts.length > 0) {
|
|
comment += `\nNot blocking at this cutoff — ${otherCounts.join(', ')}.\n`;
|
|
}
|
|
|
|
if (ignoredBlocking > 0) {
|
|
comment += `\n${ignoredBlocking} finding(s) excluded by \`.grype.yaml\`, each with a documented reason.\n`;
|
|
}
|
|
|
|
comment += '\n---\n';
|
|
comment += '📋 **Resources:**\n';
|
|
|
|
if (GITHUB_REPOSITORY && GITHUB_RUN_ID) {
|
|
comment += `- [Download full report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) (see artifacts)\n`;
|
|
}
|
|
|
|
comment += '- [View in Security tab](https://github.com/' + (GITHUB_REPOSITORY || 'repository') + '/security/code-scanning)\n';
|
|
comment += '- Scanned with [Grype](https://github.com/anchore/grype), alongside Trivy\n';
|
|
|
|
module.exports = comment;
|