Skip to content

ci: make Trivy blocking on the published Docker image scan #33

Description

@SckyzO

ci: make Trivy blocking on the published Docker image scan

Symptom

Rackscope runs Trivy in two CI workflows with inconsistent severity policies:

Workflow What it scans Blocking?
.github/workflows/security.yml Filesystem (source tree) exit-code: '1' — fails the workflow on CRITICAL findings
.github/workflows/docker.yml Built Docker image before push to GHCR exit-code: '0' — passes through regardless of findings

The asymmetry is exactly inverted from where security value sits: the image actually shipped to users is the artifact that matters, but its scan is non-blocking. A new CRITICAL CVE in a base-image dependency does not block the push, and the vulnerable image is published to GHCR as latest.

Technical Analysis

# .github/workflows/docker.yml (approximate, verify exact line at implementation)
- name: Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@<pinned-version>
  with:
    image-ref: ghcr.io/${{ github.repository_owner }}/rackscope-backend:${{ env.RACKSCOPE_VERSION }}
    format: sarif
    output: trivy-results.sarif
    severity: 'CRITICAL,HIGH'
    exit-code: '0'   # ← non-blocking

The scan does produce SARIF output uploaded to the GitHub Security tab, so findings are visible after the image is published. But the workflow does not gate the publication. There is no human approval step either. Result: the image with the CVE is in production registries before any human sees the SARIF report.

Meanwhile .trivyignore already exists at the repository root and is the documented escape hatch for accepted CVEs (false positives, vendored vulnerabilities, time-bounded exceptions). Making the scan blocking is feasible without breaking CI tomorrow, because the existing .trivyignore should already cover known accepted findings.

Proposed Fix

1. Make the Docker image scan blocking in docker.yml

 - name: Trivy vulnerability scanner
   uses: aquasecurity/trivy-action@<pinned-version>
   with:
     image-ref: ghcr.io/${{ github.repository_owner }}/rackscope-backend:${{ env.RACKSCOPE_VERSION }}
     format: sarif
     output: trivy-results.sarif
     severity: 'CRITICAL,HIGH'
-    exit-code: '0'
+    exit-code: '1'
+    trivyignores: '.trivyignore'
+    ignore-unfixed: true

Three changes:

  • exit-code: '1' — fail the workflow on CRITICAL or HIGH findings.
  • trivyignores: '.trivyignore' — explicit pointer (Trivy auto-discovers in the default location too, but being explicit avoids surprises if the workflow runs from a non-default working directory).
  • ignore-unfixed: true — do not block on CVEs that have no upstream fix yet. The operator cannot fix them; blocking the workflow would create a permanent red CI that the team learns to ignore. Unfixed CVEs are still visible in the SARIF report on the Security tab.

2. Same change for the frontend image scan (if present)

Repeat the same diff for the frontend image scan step if docker.yml builds both images.

3. Add a fallback path for accepted findings

If the scan blocks the workflow on a finding the team accepts (e.g., a CVE in a third-party dependency that Rackscope does not actually use the vulnerable code path of), the documented response is:

  1. Open a discussion thread or comment with the finding ID, the rationale, and a time-bound expiration (e.g., "ignore until upstream fix ETA 2026-Q3")
  2. Add the finding to .trivyignore with a comment explaining the rationale and the review date
  3. Re-run the workflow

Document this process in rackscope_documentation/docs/development/security.md (or wherever the existing security doc lives — there is one according to the audit cartography).

4. Pre-merge dry-run

Before merging, run the workflow once on the candidate branch to confirm the .trivyignore already covers the current image findings. If there are net-new blockers:

  • Either patch them (preferred, e.g., upgrade a dependency)
  • Or add them to .trivyignore with explicit comments

The PR description must include the dry-run result (Trivy summary or screenshot of the workflow run).

Test Checklist

  • On a known-clean image: workflow passes green
  • On a deliberately vulnerable image (e.g., add a temporary RUN apt-get install -y <package-with-known-CVE>): workflow fails at the Trivy step with the CVE ID in the log
  • Same vulnerable image with the CVE added to .trivyignore: workflow passes again (confirms the ignore mechanism works)
  • SARIF report is still uploaded to the Security tab regardless of pass/fail (existing behaviour, must not regress)
  • Unfixed CVE: workflow does not fail (confirms ignore-unfixed works), but the finding still appears in the SARIF report

Impact and Severity

  • Audience affected: every user pulling ghcr.io/sckyzO/rackscope-backend:latest or any tagged release. Today, those images can contain known CRITICAL CVEs that slipped through because nothing blocks the push.
  • Severity: high for supply-chain hygiene. Not actively exploited but textbook misalignment between scan and gate.
  • Priority: ship in the v1.0.0 security hardening series. Low risk if the pre-merge dry-run is done.

Breaking Changes

None at the user-facing level. Internal CI behaviour change:

  • A workflow that used to pass green on a vulnerable image now fails red.
  • The remediation path is documented and uses an existing mechanism (.trivyignore).

Release notes do not need to mention this — it is a CI hygiene improvement, invisible to end users.

Related

Out of Scope

  • Making bandit and pip-audit blocking (CICD-03) — same anti-pattern, but different signal/noise ratio (Python-level scans tend to surface more false positives that need triage). Filed separately if escalated.
  • Adding a separate workflow security-deep.yml with longer-running scans (Snyk, Grype, etc.) — out of scope, possible future addition.
  • Pre-build dependency scanning (pip-audit on requirements.txt) — already exists in security.yml, not in scope for this fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsecuritySecurity-related issues and fixes

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions