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:
- 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")
- Add the finding to
.trivyignore with a comment explaining the rationale and the review date
- 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
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.
ci: make Trivy blocking on the published Docker image scan
Symptom
Rackscope runs Trivy in two CI workflows with inconsistent severity policies:
.github/workflows/security.ymlexit-code: '1'— fails the workflow on CRITICAL findings.github/workflows/docker.ymlexit-code: '0'— passes through regardless of findingsThe 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
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
.trivyignorealready 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.trivyignoreshould 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: trueThree 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.ymlbuilds 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:
.trivyignorewith a comment explaining the rationale and the review dateDocument 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
.trivyignorealready covers the current image findings. If there are net-new blockers:.trivyignorewith explicit commentsThe PR description must include the dry-run result (Trivy summary or screenshot of the workflow run).
Test Checklist
RUN apt-get install -y <package-with-known-CVE>): workflow fails at the Trivy step with the CVE ID in the log.trivyignore: workflow passes again (confirms the ignore mechanism works)ignore-unfixedworks), but the finding still appears in the SARIF reportImpact and Severity
ghcr.io/sckyzO/rackscope-backend:latestor any tagged release. Today, those images can contain known CRITICAL CVEs that slipped through because nothing blocks the push.Breaking Changes
None at the user-facing level. Internal CI behaviour change:
.trivyignore).Release notes do not need to mention this — it is a CI hygiene improvement, invisible to end users.
Related
AUDIT_ARCHITECTURAL.mdOut of Scope
banditandpip-auditblocking (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.security-deep.ymlwith longer-running scans (Snyk, Grype, etc.) — out of scope, possible future addition.pip-auditonrequirements.txt) — already exists insecurity.yml, not in scope for this fix.