From 865bf6eb9abef41369c704b198ac4f9b1dedb7ed Mon Sep 17 00:00:00 2001 From: Dan Barr <6922515+danbarr@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:12:15 -0400 Subject: [PATCH 1/3] fix(ci): make periodic security scan report findings The weekly periodic-security-scan has never filed an issue. Two independent problems, both fixed here. First, the scan steps relied on anchore/scan-action's default fail-build: true with a "low" severity cutoff and no only-fixed, so the SARIF scan failed on essentially any finding and aborted the job. Only "Upload SARIF to GitHub Security" survived via if: always(); the JSON scan, the count check, and the issue-filing step were all skipped. Both scan steps now set fail-build: false so the check step decides the outcome instead of the scanner aborting the job. Second, the thresholds did not match the publish gate. build-containers blocks publishing at severity-cutoff: high with only-fixed: true, while this workflow scanned at low without only-fixed and then filed an issue only when critical > 0. A high finding was therefore enough to block a release but produced no notification, and the already-computed "high" output went unused. Both scan steps now use severity-cutoff: high with only-fixed: true, and should_create_issue is true when critical + high is greater than zero. Also lists the top high-severity findings in the issue body alongside the criticals, and updates step names and summary wording to match the new thresholds. Issue dedup and labels are unchanged. Refs #829 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/periodic-security-scan.yml | 38 +++++++++++++++----- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/.github/workflows/periodic-security-scan.yml b/.github/workflows/periodic-security-scan.yml index 62d20ccc..b8a72ce8 100644 --- a/.github/workflows/periodic-security-scan.yml +++ b/.github/workflows/periodic-security-scan.yml @@ -92,7 +92,9 @@ jobs: uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 with: image: "${{ steps.meta.outputs.image_ref }}" - severity-cutoff: "low" + severity-cutoff: "high" + only-fixed: "true" + fail-build: "false" output-format: "sarif" - name: Upload SARIF to GitHub Security @@ -107,10 +109,12 @@ jobs: uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 with: image: "${{ steps.meta.outputs.image_ref }}" - severity-cutoff: "low" + severity-cutoff: "high" + only-fixed: "true" + fail-build: "false" output-format: "json" - - name: Check for critical issues + - name: Check for critical or high issues id: check-critical run: | critical=$(jq '[.matches[]? | select(.vulnerability.severity == "Critical")] | length' ${{ steps.grype-scan-json.outputs.json }}) @@ -119,13 +123,16 @@ jobs: echo "critical=$critical" >> $GITHUB_OUTPUT echo "high=$high" >> $GITHUB_OUTPUT - if [ "$critical" -gt 0 ]; then + # Match the publish gate in build-containers.yml: any fixable + # critical or high finding is enough to block a release, so it is + # also enough to warrant an issue. + if [ "$((critical + high))" -gt 0 ]; then echo "should_create_issue=true" >> $GITHUB_OUTPUT else echo "should_create_issue=false" >> $GITHUB_OUTPUT fi - - name: Create issue for critical findings + - name: Create issue for critical or high findings if: steps.check-critical.outputs.should_create_issue == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -139,7 +146,7 @@ jobs: const high = ${{ steps.check-critical.outputs.high }}; let body = `## 🚨 Security Scan Alert\n\n`; - body += `A periodic security scan found critical issues in the container image:\n\n`; + body += `A periodic security scan found fixable critical or high severity vulnerabilities in the container image. Findings at this level also block publishing in the build workflow.\n\n`; body += `- **Image**: \`${{ steps.meta.outputs.image_ref }}\`\n`; body += `- **Critical vulnerabilities**: ${critical}\n`; body += `- **High vulnerabilities**: ${high}\n\n`; @@ -162,6 +169,21 @@ jobs: } } + if (high > 0) { + body += `#### High Vulnerabilities\n\n`; + const highVulns = (results.matches || []) + .filter(m => m.vulnerability.severity === 'High') + .slice(0, 5); + + for (const match of highVulns) { + body += `- **${match.vulnerability.id}** in \`${match.artifact.name}@${match.artifact.version}\`: ${match.vulnerability.description || 'No description'}\n`; + } + + if (high > 5) { + body += `\n_... and ${high - 5} more. See Security tab for complete list._\n`; + } + } + body += `\n---\n`; body += `_Automated security scan from [periodic-security-scan workflow](../actions/workflows/periodic-security-scan.yml)_`; @@ -191,7 +213,7 @@ jobs: await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, - title: `🚨 Security: Critical issues in ${{ steps.meta.outputs.server_name }} container`, + title: `🚨 Security: Critical or high vulnerabilities in ${{ steps.meta.outputs.server_name }} container`, body: body, labels: ['security', 'grype', 'critical'] }); @@ -218,7 +240,7 @@ jobs: run: | echo "## Periodic Security Scan Complete" >> $GITHUB_STEP_SUMMARY echo "- **Scan Type**: Vulnerability scan (Grype)" >> $GITHUB_STEP_SUMMARY - echo "- **Severity Levels**: CRITICAL, HIGH, MEDIUM, LOW" >> $GITHUB_STEP_SUMMARY + echo "- **Severity Levels**: CRITICAL, HIGH (fixable only, matching the publish gate)" >> $GITHUB_STEP_SUMMARY echo "- **Status**: ${{ needs.scan-images.result }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "View detailed results in the [Security tab](../../security/code-scanning)." >> $GITHUB_STEP_SUMMARY From 4abc1e655740c43996235668503ec7be050fd196 Mon Sep 17 00:00:00 2001 From: Dan Barr <6922515+danbarr@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:22:03 -0400 Subject: [PATCH 2/3] fix(ci): keep the periodic SARIF feed reporting every finding Aligning both scan steps with the publish gate would have narrowed the Security tab: only-fixed drops findings that have no fix available, and the periodic-grype-* categories currently carry them. Keep only-fixed on the JSON step, which drives the issue-filing decision and should match what blocks a publish, and leave the SARIF feed broad so existing code scanning coverage is unchanged. --- .github/workflows/periodic-security-scan.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/periodic-security-scan.yml b/.github/workflows/periodic-security-scan.yml index b8a72ce8..ba5de30f 100644 --- a/.github/workflows/periodic-security-scan.yml +++ b/.github/workflows/periodic-security-scan.yml @@ -87,13 +87,17 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + # The SARIF feed exists for visibility in the Security tab, so it stays + # broad on purpose: every severity, including findings with no fix + # available. Narrowing it would silently drop existing coverage. Note + # severity-cutoff only feeds grype's --fail-on and does not filter the + # report, so with fail-build disabled it has no effect here; only-fixed is + # what would filter, and it is deliberately absent. - name: Run Grype vulnerability scan (SARIF) id: grype-scan uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 with: image: "${{ steps.meta.outputs.image_ref }}" - severity-cutoff: "high" - only-fixed: "true" fail-build: "false" output-format: "sarif" From a48b689e44da976286f273f37b97b8c68b8bdab1 Mon Sep 17 00:00:00 2001 From: Dan Barr <6922515+danbarr@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:57:49 -0400 Subject: [PATCH 3/3] fix(ci): label periodic scan issues by the severities actually found Now that a high finding can file an issue, a fixed label array would mark high-only issues as critical. Build the list from the counts instead, so critical and high are applied only when present. security and grype stay unconditional since the dedup query matches on them. --- .github/workflows/periodic-security-scan.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/periodic-security-scan.yml b/.github/workflows/periodic-security-scan.yml index ba5de30f..dbf9f6a4 100644 --- a/.github/workflows/periodic-security-scan.yml +++ b/.github/workflows/periodic-security-scan.yml @@ -191,6 +191,13 @@ jobs: body += `\n---\n`; body += `_Automated security scan from [periodic-security-scan workflow](../actions/workflows/periodic-security-scan.yml)_`; + // Label by what was actually found, so a high-only issue is not + // labelled critical. security+grype are always applied and are what + // the dedup query below matches on. + const labels = ['security', 'grype']; + if (critical > 0) labels.push('critical'); + if (high > 0) labels.push('high'); + // Check if an issue already exists for this image const { data: issues } = await github.rest.issues.listForRepo({ owner: context.repo.owner, @@ -219,7 +226,7 @@ jobs: repo: context.repo.repo, title: `🚨 Security: Critical or high vulnerabilities in ${{ steps.meta.outputs.server_name }} container`, body: body, - labels: ['security', 'grype', 'critical'] + labels: labels }); console.log('Created new security issue'); }