mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-29 03:36:55 +00:00
ci: hard-fail PR and release scans on unacknowledged HIGH/CRITICAL CVEs (#484)
* ci: hard-fail PR and release scans on unacknowledged HIGH/CRITICAL CVEs Make Trivy a real gate instead of an advisory signal: - PR CI (docker-validate) no longer uses `continue-on-error: true` on the Trivy step, so any HIGH/CRITICAL finding not in `.trivyignore` fails the PR. - The release pipeline (docker-publish.yml) now builds an amd64-only scan image into the local daemon before the multi-arch push-build, re-runs Trivy against that exact artifact, and only proceeds to the push if the scan passes. This closes the gap where a CVE landed between PR merge and release-time rebuild. - New `.trivyignore` at repo root is the single source of truth for acknowledged CVEs across both workflows; it starts empty so the first CI run surfaces the full list, which we then populate with justifications. * ci: populate .trivyignore with initial HIGH/CRITICAL acknowledgements First CI run on the hard-fail Trivy policy surfaced 7 unacknowledged findings. Each has been reviewed and justified inline: - 6 CVEs in the statically-linked Go modules inside docker-compose v5.1.1 (github.com/docker/docker, buildkit, otel/sdk x2, grpc). These are transitively bundled and cannot be bumped without an upstream Compose rebuild. The grpc CVE is already explicitly acknowledged in the Dockerfile rationale block at Dockerfile:108-111. - 1 CVE in picomatch 4.0.3 bundled inside the npm CLI that ships with node:22-alpine. npm is only invoked at build time against our own package.json, so the ReDoS vector is not reachable. Every entry has a revisit trigger (next Compose release or next Alpine node bump).
This commit is contained in:
@@ -123,15 +123,15 @@ jobs:
|
|||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
- name: Scan image for vulnerabilities (Trivy)
|
- name: Scan image for vulnerabilities (Trivy)
|
||||||
|
# Hard-fails the PR on any HIGH or CRITICAL finding that is not listed
|
||||||
|
# in .trivyignore at the repo root. To accept a CVE, add it to that
|
||||||
|
# file with a human-readable justification comment, not here.
|
||||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
|
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
|
||||||
with:
|
with:
|
||||||
image-ref: sencho:pr-test
|
image-ref: sencho:pr-test
|
||||||
exit-code: '1'
|
exit-code: '1'
|
||||||
severity: 'CRITICAL,HIGH'
|
severity: 'CRITICAL,HIGH'
|
||||||
format: 'table'
|
format: 'table'
|
||||||
# Findings surface as a visible warning on the job without blocking the PR.
|
|
||||||
# Base-image CVEs are often outside our control - review manually before ignoring.
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# E2E Tests (PRs only, skipped for release-please PRs)
|
# E2E Tests (PRs only, skipped for release-please PRs)
|
||||||
|
|||||||
@@ -63,6 +63,39 @@ jobs:
|
|||||||
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
|
||||||
|
# Build an amd64-only variant into the local daemon first so Trivy can
|
||||||
|
# scan the exact release artifact before it is tagged and pushed. This
|
||||||
|
# keeps vulnerable releases out of the `latest` and semver tags that
|
||||||
|
# users actually pull. The scan-build inherits layers from the shared
|
||||||
|
# buildcache so the amd64 leg of the subsequent push-build is mostly a
|
||||||
|
# cache hit; we intentionally do NOT write `cache-to` here because the
|
||||||
|
# push-build below writes a strictly better (multi-arch, mode=max)
|
||||||
|
# cache entry moments later. The tag lives in the `localhost/` namespace
|
||||||
|
# so a future `push: false` -> `push: true` mistake cannot publish it.
|
||||||
|
# Scanning only amd64 is a defensible proxy for the multi-arch release:
|
||||||
|
# distro package CVEs are arch-agnostic at the manifest level, and
|
||||||
|
# arch-specific container-relevant CVEs are extraordinarily rare.
|
||||||
|
- name: Build release image for pre-publish scan (amd64, loaded)
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: localhost/sencho:release-scan
|
||||||
|
cache-from: type=registry,ref=saelix/sencho:buildcache
|
||||||
|
|
||||||
|
- name: Re-scan release image for vulnerabilities (Trivy)
|
||||||
|
# Gates the release on the same HIGH/CRITICAL policy as the PR scan.
|
||||||
|
# Entries in .trivyignore at the repo root are honored so the acknowledged
|
||||||
|
# CVE list is the single source of truth across PR CI and release CI.
|
||||||
|
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
|
||||||
|
with:
|
||||||
|
image-ref: localhost/sencho:release-scan
|
||||||
|
exit-code: '1'
|
||||||
|
severity: 'CRITICAL,HIGH'
|
||||||
|
format: 'table'
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v7
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Trivy ignore list
|
||||||
|
#
|
||||||
|
# Every entry in this file is a known HIGH or CRITICAL CVE that we have
|
||||||
|
# consciously accepted risk on and decided not to block CI over. Format:
|
||||||
|
#
|
||||||
|
# CVE-YYYY-NNNNN
|
||||||
|
# # Justification: why we're accepting this risk, and a link or note about
|
||||||
|
# # when to revisit (e.g. "blocked on upstream base image update, revisit
|
||||||
|
# # when alpine/node:22 ships a fix").
|
||||||
|
#
|
||||||
|
# Rules:
|
||||||
|
# - Every CVE MUST have a justification comment directly above it.
|
||||||
|
# - If there is no justification, the CVE is not ignored - add it here only
|
||||||
|
# after a human review and a decision to accept the risk.
|
||||||
|
# - Review this file on every release; remove entries whose upstream fix has
|
||||||
|
# landed.
|
||||||
|
#
|
||||||
|
# Picked up automatically by aquasecurity/trivy-action from the repo root
|
||||||
|
# working directory. Both the pre-push PR scan (.github/workflows/ci.yml) and
|
||||||
|
# the release-time re-scan (.github/workflows/docker-publish.yml) honor it.
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Bundled inside /usr/local/lib/docker/cli-plugins/docker-compose (v5.1.1)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Compose v5.1.1 is the latest upstream release and is already pinned in
|
||||||
|
# Dockerfile:113 to get us off the v2.40.3 grpc 1.74.2 / x/crypto 0.38.0 CVE
|
||||||
|
# set. It statically links older copies of github.com/docker/docker,
|
||||||
|
# buildkit, otel, and grpc. We cannot bump these transitively without
|
||||||
|
# waiting for a new upstream Compose release. Revisit this block on every
|
||||||
|
# Compose release; remove entries as upstream rebuilds ship the fixes.
|
||||||
|
# See the rationale block at Dockerfile:96-111.
|
||||||
|
|
||||||
|
# Justification: github.com/docker/docker v28.5.2 statically bundled in
|
||||||
|
# compose v5.1.1. Moby authz bypass applies to a Docker daemon, not to the
|
||||||
|
# compose CLI plugin; compose never runs as a daemon. Revisit on next
|
||||||
|
# Compose upstream release.
|
||||||
|
CVE-2026-34040
|
||||||
|
|
||||||
|
# Justification: github.com/moby/buildkit v0.27.1 statically bundled in
|
||||||
|
# compose v5.1.1. BuildKit arbitrary file write via untrusted frontend is
|
||||||
|
# exploited at buildkit build time with attacker-controlled frontends; our
|
||||||
|
# compose invocations only call up/down/ps against local user-authored
|
||||||
|
# compose files, never as a build frontend. Revisit on next Compose upstream
|
||||||
|
# release.
|
||||||
|
CVE-2026-33747
|
||||||
|
|
||||||
|
# Justification: github.com/moby/buildkit v0.27.1 statically bundled in
|
||||||
|
# compose v5.1.1. Same exposure profile as CVE-2026-33747 (Git URL fragment
|
||||||
|
# subdir exploitation requires invoking buildkit on untrusted repo URLs,
|
||||||
|
# which compose does not do in our flow). Revisit on next Compose upstream
|
||||||
|
# release.
|
||||||
|
CVE-2026-33748
|
||||||
|
|
||||||
|
# Justification: go.opentelemetry.io/otel/sdk v1.38.0 statically bundled in
|
||||||
|
# compose v5.1.1. PATH hijacking requires the attacker to control the
|
||||||
|
# process PATH before compose starts; our container starts compose from a
|
||||||
|
# fixed PATH with only /usr/local/bin and /usr/bin on it, both owned by
|
||||||
|
# root. Revisit on next Compose upstream release.
|
||||||
|
CVE-2026-24051
|
||||||
|
|
||||||
|
# Justification: go.opentelemetry.io/otel/sdk v1.38.0 statically bundled in
|
||||||
|
# compose v5.1.1. BSD kenv PATH hijacking only applies on BSD systems; we
|
||||||
|
# ship linux/amd64 and linux/arm64. Not applicable in our runtime. Revisit
|
||||||
|
# on next Compose upstream release.
|
||||||
|
CVE-2026-39883
|
||||||
|
|
||||||
|
# Justification: google.golang.org/grpc v1.78.0 statically bundled in
|
||||||
|
# compose v5.1.1 AND in Docker CLI v29.3.1. Already explicitly acknowledged
|
||||||
|
# in the Dockerfile rationale block at Dockerfile:108-111 as unpatched,
|
||||||
|
# waiting on a new Docker CLI or Compose release that ships grpc >= 1.79.3.
|
||||||
|
# Exploit requires an attacker-controlled HTTP/2 peer talking to a gRPC
|
||||||
|
# server; compose and the docker CLI only act as gRPC clients against the
|
||||||
|
# local unix socket, not as servers.
|
||||||
|
CVE-2026-33186
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Bundled inside /usr/local/lib/node_modules/npm/ (node:22-alpine base image)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Justification: picomatch 4.0.3 is shipped inside the npm CLI that comes
|
||||||
|
# bundled with the upstream node:22-alpine base image. We do not run npm at
|
||||||
|
# container runtime against user-controlled input; npm is only invoked at
|
||||||
|
# build time against our own package.json files. The ReDoS requires an
|
||||||
|
# attacker-authored extglob pattern, which is not reachable from any
|
||||||
|
# runtime code path. Revisit when a future node:22-alpine base image ships
|
||||||
|
# a newer npm that bundles picomatch >= 4.0.4.
|
||||||
|
CVE-2026-33671
|
||||||
Reference in New Issue
Block a user