From 3b577907e4912bfe397d9892fda8e59e3dbbf84f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:23:29 +0200 Subject: [PATCH] ci(container): gate Grype only on fixable findings, and comment results on the PR (#12341) --- .github/actions/grype-scan/action.yml | 51 +++++++ .github/actions/trivy-scan/action.yml | 35 +++-- .github/scripts/grype-pr-comment.js | 100 +++++++++++++ .github/workflows/api-container-checks.yml | 6 +- .github/workflows/mcp-container-checks.yml | 6 +- .github/workflows/sdk-container-checks.yml | 6 +- .github/workflows/ui-container-checks.yml | 6 +- .grype.yaml | 161 ++------------------- .trivyignore.yaml | 92 ++++++++++++ 9 files changed, 294 insertions(+), 169 deletions(-) create mode 100644 .github/scripts/grype-pr-comment.js diff --git a/.github/actions/grype-scan/action.yml b/.github/actions/grype-scan/action.yml index 1cac3f7674..76d3004c11 100644 --- a/.github/actions/grype-scan/action.yml +++ b/.github/actions/grype-scan/action.yml @@ -18,6 +18,10 @@ inputs: description: 'Upload results to GitHub Security tab' required: false default: 'true' + create-pr-comment: + description: 'Create a comment on the PR with scan results' + required: false + default: 'true' artifact-retention-days: description: 'Days to retain the Grype report artifact' required: false @@ -45,6 +49,7 @@ runs: output-file: 'grype-report.json' fail-build: 'false' by-cve: 'true' # Report CVE ids rather than GHSA, so findings line up with Trivy's + only-fixed: 'true' # A finding with no available fix is not actionable, so it must not gate cache-db: 'true' grype-version: 'v0.116.1' @@ -58,6 +63,7 @@ runs: fail-build: 'false' severity-cutoff: 'high' by-cve: 'true' + only-fixed: 'true' # A finding with no available fix is not actionable, so it must not gate cache-db: 'true' grype-version: 'v0.116.1' @@ -101,6 +107,51 @@ runs: INPUTS_IMAGE_NAME: ${{ inputs.image-name }} INPUTS_IMAGE_TAG: ${{ inputs.image-tag }} + # Before the gate, so the comment is there to explain a failure rather than absent because of it + - name: Comment scan results on PR + if: >- + inputs.create-pr-comment == 'true' + && github.event_name == 'pull_request' + && github.event.pull_request.head.repo.full_name == github.repository + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + IMAGE_NAME: ${{ inputs.image-name }} + GITHUB_SHA: ${{ inputs.image-tag }} + CUTOFF: ${{ inputs.fail-on-severity }} + with: + script: | + const comment = require('./.github/scripts/grype-pr-comment.js'); + + // Unique identifier to find our comment + const marker = ``; + const body = marker + '\n' + comment; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const existingComment = comments.find(c => c.body?.includes(marker)); + + if (existingComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body: body + }); + console.log('✅ Updated existing Grype scan comment'); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body + }); + console.log('✅ Created new Grype scan comment'); + } + - name: Check for blocking vulnerabilities if: inputs.fail-on-severity != 'none' shell: bash diff --git a/.github/actions/trivy-scan/action.yml b/.github/actions/trivy-scan/action.yml index 4aa1e47047..b5159767cd 100644 --- a/.github/actions/trivy-scan/action.yml +++ b/.github/actions/trivy-scan/action.yml @@ -14,10 +14,10 @@ inputs: description: 'Severities to scan for (comma-separated)' required: false default: 'CRITICAL,HIGH,MEDIUM,LOW' - fail-on-critical: - description: 'Fail the build if critical vulnerabilities are found' + fail-on-severity: + description: 'Fail the build on findings at this severity or above: critical, high, or none' required: false - default: 'false' + default: 'high' upload-sarif: description: 'Upload results to GitHub Security tab' required: false @@ -62,6 +62,7 @@ runs: severity: ${{ inputs.severity }} exit-code: '0' scanners: 'vuln' + ignore-unfixed: 'true' # A finding with no available fix is not actionable, so it must not gate timeout: '5m' version: 'v0.71.2' # Not trivyignores: that input drops the .yaml extension Trivy parses by. @@ -78,6 +79,7 @@ runs: severity: 'CRITICAL,HIGH' exit-code: '0' scanners: 'vuln' + ignore-unfixed: 'true' # A finding with no available fix is not actionable, so it must not gate timeout: '5m' version: 'v0.71.2' # Not trivyignores: that input drops the .yaml extension Trivy parses by. @@ -169,13 +171,28 @@ runs: console.log('✅ Created new Trivy scan comment'); } - - name: Check for critical vulnerabilities - if: inputs.fail-on-critical == 'true' && steps.security-check.outputs.critical != '0' + - name: Check for blocking vulnerabilities + if: inputs.fail-on-severity != 'none' shell: bash run: | - echo "::error::Found ${STEPS_SECURITY_CHECK_OUTPUTS_CRITICAL} critical vulnerabilities" - echo "::warning::Please update packages or use a different base image" - exit 1 + if [ "$CUTOFF" = "critical" ]; then + BLOCKING=$CRITICAL + SEVERITIES='["CRITICAL"]' + else + BLOCKING=$((CRITICAL + HIGH)) + SEVERITIES='["CRITICAL","HIGH"]' + fi + if [ "$BLOCKING" -gt 0 ]; then + echo "::error::Found $BLOCKING vulnerabilities at severity ${CUTOFF} or above ($CRITICAL critical, $HIGH high)" + echo "::warning::Update the package, or add it to .trivyignore.yaml with a reason if nothing can be done" + jq -r --argjson severities "$SEVERITIES" \ + '.Results[]?.Vulnerabilities[]? | select(.Severity | IN($severities[])) + | " \(.Severity)\t\(.VulnerabilityID)\t\(.PkgName) \(.InstalledVersion)"' \ + trivy-report.json | sort -u + exit 1 + fi env: - STEPS_SECURITY_CHECK_OUTPUTS_CRITICAL: ${{ steps.security-check.outputs.critical }} + CUTOFF: ${{ inputs.fail-on-severity }} + CRITICAL: ${{ steps.security-check.outputs.critical }} + HIGH: ${{ steps.security-check.outputs.high }} diff --git a/.github/scripts/grype-pr-comment.js b/.github/scripts/grype-pr-comment.js new file mode 100644 index 0000000000..62e81dfddc --- /dev/null +++ b/.github/scripts/grype-pr-comment.js @@ -0,0 +1,100 @@ +const fs = require('fs'); + +// Configuration from environment variables +const REPORT_FILE = process.env.GRYPE_REPORT_FILE || 'grype-report.json'; +const IMAGE_NAME = process.env.IMAGE_NAME || 'container-image'; +const GITHUB_SHA = process.env.GITHUB_SHA || 'unknown'; +const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY || ''; +const GITHUB_RUN_ID = process.env.GITHUB_RUN_ID || ''; +const CUTOFF = process.env.CUTOFF || 'high'; + +// A cutoff of 'critical' blocks only on critical; anything else blocks on high and above +const blocking = CUTOFF === 'critical' ? ['Critical'] : ['Critical', 'High']; + +const report = JSON.parse(fs.readFileSync(REPORT_FILE, 'utf-8')); +const matches = Array.isArray(report.matches) ? report.matches : []; +const ignored = Array.isArray(report.ignoredMatches) ? report.ignoredMatches : []; + +const counts = { Critical: 0, High: 0, Medium: 0, Low: 0, Negligible: 0, Unknown: 0 }; +const blockers = new Map(); + +for (const match of matches) { + const severity = match.vulnerability.severity; + if (counts[severity] !== undefined) { + counts[severity]++; + } + if (blocking.includes(severity)) { + const artifact = match.artifact; + const fixedIn = (match.vulnerability.fix && match.vulnerability.fix.versions || []).join(', '); + // Same CVE can match several install paths of one package; collapse them + blockers.set(`${match.vulnerability.id}|${artifact.name}`, { + id: match.vulnerability.id, + severity, + name: artifact.name, + version: artifact.version, + fixedIn + }); + } +} + +const ignoredBlocking = ignored.filter(m => blocking.includes(m.vulnerability.severity)).length; +const shortSha = GITHUB_SHA.substring(0, 7); +const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC'; + +const severityConfig = { + Critical: { icon: '🔴', label: 'Critical' }, + High: { icon: '🟠', label: 'High' }, + Medium: { icon: '🟡', label: 'Medium' }, + Low: { icon: '🔵', label: 'Low' } +}; + +let comment = '## 🔎 Container Security Scan (Grype)\n\n'; +comment += `**Image:** \`${IMAGE_NAME}:${shortSha}\`\n`; +comment += `**Last scan:** ${timestamp}\n\n`; + +if (blockers.size === 0) { + comment += '### ✅ Nothing Blocking\n\n'; + comment += `No findings at **${blocking.join(' or ').toLowerCase()}** severity.\n`; +} else { + comment += `### ⚠️ ${blockers.size} Finding(s) Blocking This PR\n\n`; + comment += '| Severity | CVE | Package | Installed | Fixed in |\n'; + comment += '|---|---|---|---|---|\n'; + + const order = { Critical: 0, High: 1 }; + const rows = [...blockers.values()].sort((a, b) => + (order[a.severity] - order[b.severity]) || a.name.localeCompare(b.name)); + + for (const row of rows) { + const config = severityConfig[row.severity]; + comment += `| ${config.icon} ${config.label} | \`${row.id}\` | \`${row.name}\` | ${row.version} | ${row.fixedIn || '—'} |\n`; + } + + comment += '\n**What to do:**\n'; + comment += '- Upgrade the package to the version in the "Fixed in" column.\n'; + comment += '- If it is pinned by another dependency, or the fix is otherwise out of reach, add it to `.grype.yaml` **with the reason**.\n'; + comment += '- Findings with no published fix never appear here: the scan runs with `only-fixed`, so it reports only what can actually be acted on.\n'; +} + +const otherCounts = Object.entries(counts) + .filter(([severity, count]) => !blocking.includes(severity) && count > 0) + .map(([severity, count]) => `${severity.toLowerCase()}: ${count}`); + +if (otherCounts.length > 0) { + comment += `\nNot blocking at this cutoff — ${otherCounts.join(', ')}.\n`; +} + +if (ignoredBlocking > 0) { + comment += `\n${ignoredBlocking} finding(s) excluded by \`.grype.yaml\`, each with a documented reason.\n`; +} + +comment += '\n---\n'; +comment += '📋 **Resources:**\n'; + +if (GITHUB_REPOSITORY && GITHUB_RUN_ID) { + comment += `- [Download full report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) (see artifacts)\n`; +} + +comment += '- [View in Security tab](https://github.com/' + (GITHUB_REPOSITORY || 'repository') + '/security/code-scanning)\n'; +comment += '- Scanned with [Grype](https://github.com/anchore/grype), alongside Trivy\n'; + +module.exports = comment; diff --git a/.github/workflows/api-container-checks.yml b/.github/workflows/api-container-checks.yml index 19e6de3a0f..4694c34e27 100644 --- a/.github/workflows/api-container-checks.yml +++ b/.github/workflows/api-container-checks.yml @@ -84,6 +84,7 @@ jobs: raw.githubusercontent.com:443 objects.githubusercontent.com:443 grype.anchore.io:443 + get.anchore.io:443 debian.map.fastlydns.net:80 release-assets.githubusercontent.com:443 objects.githubusercontent.com:443 @@ -113,6 +114,7 @@ jobs: .github/actions/trivy-scan/** .github/actions/grype-scan/** .grype.yaml + .github/scripts/grype-pr-comment.js files_ignore: | api/docs/** api/README.md @@ -150,8 +152,8 @@ jobs: with: image-name: ${{ env.IMAGE_NAME }} image-tag: ${{ github.sha }} - fail-on-critical: 'true' - severity: 'CRITICAL' + fail-on-severity: 'high' + severity: 'CRITICAL,HIGH' - name: Scan container with Grype if: steps.check-changes.outputs.any_changed == 'true' diff --git a/.github/workflows/mcp-container-checks.yml b/.github/workflows/mcp-container-checks.yml index a9c19f716d..a2efee62ce 100644 --- a/.github/workflows/mcp-container-checks.yml +++ b/.github/workflows/mcp-container-checks.yml @@ -89,6 +89,7 @@ jobs: objects.githubusercontent.com:443 raw.githubusercontent.com:443 grype.anchore.io:443 + get.anchore.io:443 - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -105,6 +106,7 @@ jobs: .github/actions/trivy-scan/** .github/actions/grype-scan/** .grype.yaml + .github/scripts/grype-pr-comment.js files_ignore: | mcp_server/README.md mcp_server/CHANGELOG.md @@ -131,8 +133,8 @@ jobs: with: image-name: ${{ env.IMAGE_NAME }} image-tag: ${{ github.sha }} - fail-on-critical: 'true' - severity: 'CRITICAL' + fail-on-severity: 'high' + severity: 'CRITICAL,HIGH' - name: Scan MCP container with Grype if: steps.check-changes.outputs.any_changed == 'true' diff --git a/.github/workflows/sdk-container-checks.yml b/.github/workflows/sdk-container-checks.yml index b98207b978..1ec8fbdfb3 100644 --- a/.github/workflows/sdk-container-checks.yml +++ b/.github/workflows/sdk-container-checks.yml @@ -86,6 +86,7 @@ jobs: raw.githubusercontent.com:443 objects.githubusercontent.com:443 grype.anchore.io:443 + get.anchore.io:443 debian.map.fastlydns.net:80 release-assets.githubusercontent.com:443 objects.githubusercontent.com:443 @@ -119,6 +120,7 @@ jobs: .github/actions/trivy-scan/** .github/actions/grype-scan/** .grype.yaml + .github/scripts/grype-pr-comment.js files_ignore: | prowler/CHANGELOG.md prowler/changelog.d/** @@ -145,8 +147,8 @@ jobs: with: image-name: ${{ env.IMAGE_NAME }} image-tag: ${{ github.sha }} - fail-on-critical: 'true' - severity: 'CRITICAL' + fail-on-severity: 'high' + severity: 'CRITICAL,HIGH' - name: Scan SDK container with Grype if: steps.check-changes.outputs.any_changed == 'true' diff --git a/.github/workflows/ui-container-checks.yml b/.github/workflows/ui-container-checks.yml index 5da65da3f1..1b22b97a8f 100644 --- a/.github/workflows/ui-container-checks.yml +++ b/.github/workflows/ui-container-checks.yml @@ -90,6 +90,7 @@ jobs: objects.githubusercontent.com:443 raw.githubusercontent.com:443 grype.anchore.io:443 + get.anchore.io:443 - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -106,6 +107,7 @@ jobs: .github/actions/trivy-scan/** .github/actions/grype-scan/** .grype.yaml + .github/scripts/grype-pr-comment.js files_ignore: | ui/CHANGELOG.md ui/changelog.d/** @@ -136,8 +138,8 @@ jobs: with: image-name: ${{ env.IMAGE_NAME }} image-tag: ${{ github.sha }} - fail-on-critical: 'true' - severity: 'CRITICAL' + fail-on-severity: 'high' + severity: 'CRITICAL,HIGH' - name: Scan UI container with Grype if: steps.check-changes.outputs.any_changed == 'true' diff --git a/.grype.yaml b/.grype.yaml index 970fda5319..f6c98b9db7 100644 --- a/.grype.yaml +++ b/.grype.yaml @@ -1,154 +1,12 @@ # Findings excluded from the Grype gate, each with a reason. # Anything not listed here blocks the pull request at critical or high severity. # Pairs are explicit: a new CVE against an already-listed package still blocks. +# +# Every entry below has a published fix we cannot take. Findings with no fix at all are +# not listed: the scan runs with only-fixed, so they never reach the gate. ignore: - # Operating-system packages with no fix available. - # Debian and Alpine have either published no fix or marked these will-not-fix. - # perl-base additionally cannot be removed: the distribution marks it Essential. - - vulnerability: CVE-2026-41992 - package: - name: gzip - - vulnerability: CVE-2026-54369 - package: - name: libacl1 - - vulnerability: CVE-2026-54370 - package: - name: libacl1 - - vulnerability: CVE-2026-5435 - package: - name: libc-bin - - vulnerability: CVE-2026-5450 - package: - name: libc-bin - - vulnerability: CVE-2026-5928 - package: - name: libc-bin - - vulnerability: CVE-2026-5435 - package: - name: libc6 - - vulnerability: CVE-2026-5450 - package: - name: libc6 - - vulnerability: CVE-2026-5928 - package: - name: libc6 - - vulnerability: CVE-2026-10536 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-11856 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-12064 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-8286 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-8924 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-8926 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-8927 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-8932 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-9079 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-9080 - package: - name: libcurl4t64 - - vulnerability: CVE-2026-9545 - package: - name: libcurl4t64 - - vulnerability: CVE-2025-69720 - package: - name: libncursesw6 - - vulnerability: CVE-2026-11822 - package: - name: libsqlite3-0 - - vulnerability: CVE-2026-11824 - package: - name: libsqlite3-0 - - vulnerability: CVE-2026-66032 - package: - name: libssh2-1t64 - - vulnerability: CVE-2026-66033 - package: - name: libssh2-1t64 - - vulnerability: CVE-2026-66034 - package: - name: libssh2-1t64 - - vulnerability: CVE-2026-66035 - package: - name: libssh2-1t64 - - vulnerability: CVE-2025-69720 - package: - name: libtinfo6 - - vulnerability: CVE-2025-69720 - package: - name: ncurses-base - - vulnerability: CVE-2025-69720 - package: - name: ncurses-bin - - vulnerability: CVE-2026-12087 - package: - name: perl-base - - vulnerability: CVE-2026-13221 - package: - name: perl-base - - vulnerability: CVE-2026-42496 - package: - name: perl-base - - vulnerability: CVE-2026-42497 - package: - name: perl-base - - vulnerability: CVE-2026-48959 - package: - name: perl-base - - vulnerability: CVE-2026-48961 - package: - name: perl-base - - vulnerability: CVE-2026-48962 - package: - name: perl-base - - vulnerability: CVE-2026-57432 - package: - name: perl-base - - vulnerability: CVE-2026-57433 - package: - name: perl-base - - vulnerability: CVE-2026-7017 - package: - name: perl-base - - vulnerability: CVE-2026-8376 - package: - name: perl-base - - vulnerability: CVE-2026-9538 - package: - name: perl-base - - vulnerability: CVE-2026-11822 - package: - name: sqlite-libs - - vulnerability: CVE-2026-11824 - package: - name: sqlite-libs - - vulnerability: CVE-2026-58469 - package: - name: wget - - vulnerability: CVE-2026-58471 - package: - name: wget - - vulnerability: CVE-2026-58472 - package: - name: wget - # Modules compiled into the Trivy binary we ship. # Only a Trivy rebuild by its vendor can change these; the version is pinned in our Dockerfile. - vulnerability: CVE-2026-56852 @@ -167,18 +25,17 @@ ignore: package: name: Microsoft.Bcl.Memory - # cryptography is capped below 49 by alibabacloud-tea-openapi==0.4.5, - # which requires cryptography>=3.0.0,<49.0.0. The fix is in 50.0.0, so it is - # unreachable until that dependency raises its ceiling. + # cryptography is capped below 49 by alibabacloud-tea-openapi==0.4.5, which requires + # cryptography>=3.0.0,<49.0.0. The fix is in 50.0.0, so it is unreachable until that + # dependency raises its ceiling. Verified by attempting the upgrade: resolution fails. - vulnerability: CVE-2026-69247 package: name: cryptography # The CPython interpreter, compiled into the official base image. - # TEMPORARY, unlike the entries above: these are reachable by moving to Python 3.13, - # which is a runtime upgrade pending its own evaluation. Three of them (CVE-2026-11940, - # CVE-2026-11972, CVE-2026-15308) need 3.15 and remain unfixable either way -- the MCP - # image already runs 3.13.14 and still reports them. + # TEMPORARY, unlike the entries above: moving to Python 3.13 clears seven of these, and + # that is a runtime upgrade pending its own evaluation. The remaining three need 3.15 and + # are unfixable either way -- the MCP image already runs 3.13.14 and still reports them. - vulnerability: CVE-2026-11940 package: name: python diff --git a/.trivyignore.yaml b/.trivyignore.yaml index f7f39e292c..da7c123015 100644 --- a/.trivyignore.yaml +++ b/.trivyignore.yaml @@ -9,6 +9,11 @@ # # `expired_at` forces re-review. Keep the dates staggered. # +# The four entries below are currently redundant: the scan runs with ignore-unfixed, +# and none of them has a published fix, so they never reach the gate either way. They +# are kept because the reasoning is what justifies accepting them, and because they +# apply again the moment any of them gains a fix we do not take. +# # perl-base is Debian "Essential: yes". Trivy spreads src:perl CVEs across every # binary package built from that source, so perl-base is flagged for modules only # perl-modules-* ships. Neither image installs those, and nothing in either @@ -61,3 +66,90 @@ vulnerabilities: purls: - "pkg:deb/debian/perl-base" expired_at: 2026-11-30 + + # Declared in the SPDX manifest that ships inside PowerShell's MicrosoftTeams module + # (Modules/MicrosoftTeams/7.9.0/_manifest/spdx_2.2/manifest.spdx.json). Trivy reads that + # SBOM and reports what it declares, which is not the same as what the image contains: + # there is no Node runtime and no node_modules anywhere in the image, and the .NET + # assemblies target net472, a Windows-only framework. Nothing here is reachable, and none + # of it is a dependency we declare -- only Microsoft can change the module's contents. + - id: CVE-2020-0606 + purls: + - "pkg:nuget/Microsoft.WindowsDesktop.App.Ref" + expired_at: 2027-01-31 + - id: CVE-2019-0820 + purls: + - "pkg:nuget/System.Text.RegularExpressions" + expired_at: 2027-01-31 + - id: CVE-2026-47302 + purls: + - "pkg:nuget/System.Security.Cryptography.Xml" + expired_at: 2027-01-31 + - id: CVE-2026-47304 + purls: + - "pkg:nuget/System.Security.Cryptography.Xml" + expired_at: 2027-01-31 + - id: CVE-2026-50525 + purls: + - "pkg:nuget/System.Security.Cryptography.Xml" + expired_at: 2027-01-31 + - id: CVE-2026-50527 + purls: + - "pkg:nuget/System.Security.Cryptography.Xml" + expired_at: 2027-01-31 + - id: CVE-2026-50648 + purls: + - "pkg:nuget/System.Security.Cryptography.Xml" + expired_at: 2027-01-31 + - id: CVE-2026-13676 + purls: + - "pkg:npm/fast-uri" + expired_at: 2027-01-31 + - id: CVE-2026-16221 + purls: + - "pkg:npm/fast-uri" + expired_at: 2027-01-31 + - id: CVE-2026-18446 + purls: + - "pkg:npm/fast-uri" + expired_at: 2027-01-31 + - id: CVE-2026-69192 + purls: + - "pkg:npm/ip-address" + expired_at: 2027-01-31 + + # Modules compiled into the Trivy binary the images ship. The binary is pinned by version + # and verified by checksum in the Dockerfile; only a rebuild by its vendor moves these. + - id: CVE-2026-56852 + purls: + - "pkg:golang/golang.org/x/text" + expired_at: 2026-12-31 + - id: GHSA-hrxh-6v49-42gf + purls: + - "pkg:golang/google.golang.org/grpc" + expired_at: 2026-12-31 + - id: CVE-2026-50151 + purls: + - "pkg:golang/oras.land/oras-go/v2" + expired_at: 2026-12-31 + - id: CVE-2026-50163 + purls: + - "pkg:golang/oras.land/oras-go/v2" + expired_at: 2026-12-31 + - id: CVE-2026-39822 + purls: + - "pkg:golang/stdlib" + expired_at: 2026-12-31 + + # cryptography is held below 49 by alibabacloud-tea-openapi==0.4.5, which requires + # cryptography>=3.0.0,<49.0.0. The fixes are in 49.0.0 and 50.0.0, so both sit above the + # ceiling. Verified by attempting the upgrade: resolution fails. Reachable again only when + # that dependency raises its bound. + - id: CVE-2026-69247 + purls: + - "pkg:pypi/cryptography" + expired_at: 2026-11-30 + - id: CVE-2026-69249 + purls: + - "pkg:pypi/cryptography" + expired_at: 2026-11-30