Skip to content

DAT-22061: Add vulnerability scanning to QA Docker image builds#500

Open
jandroav wants to merge 5 commits intomainfrom
dat-22061
Open

DAT-22061: Add vulnerability scanning to QA Docker image builds#500
jandroav wants to merge 5 commits intomainfrom
dat-22061

Conversation

@jandroav
Copy link
Contributor

Summary

  • Push QA Docker images to GHCR (alongside Nexus) so the reusable vulnerability scan workflow can access them
  • Add vulnerability-scan job using reusable-vulnerability-scan.yml from build-logic, matching the pattern used by trivy.yml
  • Scans run per matrix variant (community, alpine, secure) with fail_on_vulnerabilities: true to catch CVEs before QA testing

Changes

  • Added packages: write permission for GHCR push
  • Added ghcr_name field to all matrix items
  • Added GHCR login step + GHCR tag to the build-push step
  • Added vulnerability-scan job calling the reusable workflow

Test plan

  • Trigger Build QA Docker Images with All (Community + Alpine + Secure)
  • Verify images pushed to both Nexus and GHCR
  • Verify vulnerability-scan jobs run for each variant
  • Verify scan results appear in workflow artifacts

Generated with Claude Code

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Adds GHCR integration and tagging to the QA Docker build workflow, extends the build matrix with ghcr_name for image variants, inserts GHCR login/cleanup steps, and adds a reusable vulnerability-scan job that runs after per-image builds. Also updates Trivy workflow to run GHCR cleanup before failure notifications.

Changes

Cohort / File(s) Summary
QA Docker workflow
.github/workflows/build-qa-docker.yml
Added packages: write permission; extended matrix items with ghcr_name; dynamic job naming; added docker/login-action steps for ghcr.io; push to GHCR (tags include ghcr.io/${{ github.repository }}/liquibase-${{ matrix.ghcr_name }}:${{ github.sha }}) alongside existing pushes; added vulnerability-scan reusable-job invocation; added cleanup-ghcr job and GHCR package deletion steps; updated artifact-based and fallback build paths to use matrix.ghcr_name.
Trivy / cleanup updates
.github/workflows/trivy.yml
Introduced cleanup-ghcr job that deletes GHCR container versions for built images and made notify-failure depend on cleanup to ensure cleanup runs before notifications.

Sequence Diagram(s)

sequenceDiagram
  participant Trigger as Workflow Trigger
  participant Matrix as set-matrix
  participant Build as build-qa-docker
  participant GHCR as GHCR (ghcr.io)
  participant Nexus as Nexus / existing registry
  participant Scan as vulnerability-scan (reusable workflow)
  participant Cleanup as cleanup-ghcr

  Trigger->>Matrix: compute per-image matrix (includes ghcr_name)
  Matrix->>Build: start per-image build jobs
  Build->>GHCR: login to GHCR (docker/login-action)
  Build->>Build: build image (artifact-based or fallback)
  Build->>Nexus: push image to existing registry
  Build->>GHCR: push image to GHCR with ghcr_name tags
  Build->>Scan: invoke vulnerability-scan with GHCR image ref
  Scan->>Build: return scan results (workflow output)
  Build->>Cleanup: enqueue GHCR cleanup (always run)
  Cleanup->>GHCR: query & delete matching package versions
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

type: ci

Suggested reviewers

  • sayaliM0412
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding vulnerability scanning to the QA Docker image build process.
Description check ✅ Passed The description is directly related to the changeset, detailing how GHCR integration enables vulnerability scanning in QA builds.
Linked Issues check ✅ Passed All acceptance criteria from DAT-22061 are met: scans added to build-qa-docker.yml, run for all three variants, and CVE findings are surfaced.
Out of Scope Changes check ✅ Passed Changes are focused on vulnerability scanning and GHCR integration. The cleanup-ghcr jobs and trivy.yml updates are supporting infrastructure for the core objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dat-22061

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the QA Docker image build workflow by adding automated vulnerability scanning. It enables the workflow to push QA images to GitHub Container Registry (GHCR) in addition to the existing Nexus registry, allowing the reusable vulnerability scan workflow from build-logic to access and scan these images. The implementation follows the established pattern from the trivy.yml workflow.

Changes:

  • Added packages: write permission for GHCR push operations
  • Extended matrix configuration with ghcr_name field for all build variants (community, alpine, secure)
  • Added vulnerability scanning job that runs after successful builds using the reusable workflow from build-logic

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
.github/workflows/build-qa-docker.yml (2)

528-534: CodeQL: docker/login-action@v3 is an unpinned mutable tag.

This is flagged by the CodeQL static analysis. Pinning to a commit SHA ensures supply-chain integrity. The GitHub official documentation pins docker/login-action to a specific commit hash rather than a semver tag.

🔒 Pin to commit SHA
-        uses: docker/login-action@v3
+        uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-qa-docker.yml around lines 528 - 534, The step named
"Login to GHCR" uses the mutable tag "docker/login-action@v3"; replace this with
the action pinned to an immutable commit SHA (e.g.,
"docker/login-action@<commit-sha>") to satisfy CodeQL supply-chain checks—update
the uses field in that step to the specific commit SHA for docker/login-action
and commit the workflow change.

3-7: packages: write granted at workflow level rather than job level.

All three jobs (set-matrix, build-qa-docker, vulnerability-scan) now inherit packages: write. Only build-qa-docker needs write access; vulnerability-scan only needs packages: read, and set-matrix needs no package access. Consider moving this permission to the build-qa-docker job level to enforce least privilege.

🔒 Suggested job-level scoping
 permissions:
   contents: read
   id-token: write
   actions: read
-  packages: write

Then in the build-qa-docker job:

   build-qa-docker:
     name: "Build and Push QA Docker Images"
     runs-on: ubuntu-latest
     needs: set-matrix
+    permissions:
+      contents: read
+      id-token: write
+      actions: read
+      packages: write
     strategy:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-qa-docker.yml around lines 3 - 7, The workflow-level
permissions currently grant packages: write to all jobs; change this to enforce
least privilege by moving packages: write into the build-qa-docker job's
permissions block (so build-qa-docker keeps write access), set packages: read in
the vulnerability-scan job's permissions block, and remove or omit any packages
permission from the set-matrix job (so it has no package access); update the
permissions stanza references for the jobs named build-qa-docker,
vulnerability-scan, and set-matrix accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build-qa-docker.yml:
- Around line 622-638: The build matrix job "build-qa-docker" currently omits
fail-fast configuration so a single variant failure can short-circuit the entire
matrix and prevent downstream scans; open the job block for build-qa-docker and
add strategy.fail-fast: false (alongside the existing matrix: ${{
fromJSON(needs.set-matrix.outputs.matrix) }}) so each variant runs
independently; while there, optionally review the uses of
reusable-vulnerability-scan.yml (image_name/image_tag) for secrets forwarding
and consider pinning build_logic_ref, but the required fix is adding fail-fast:
false to the build-qa-docker strategy.

---

Nitpick comments:
In @.github/workflows/build-qa-docker.yml:
- Around line 528-534: The step named "Login to GHCR" uses the mutable tag
"docker/login-action@v3"; replace this with the action pinned to an immutable
commit SHA (e.g., "docker/login-action@<commit-sha>") to satisfy CodeQL
supply-chain checks—update the uses field in that step to the specific commit
SHA for docker/login-action and commit the workflow change.
- Around line 3-7: The workflow-level permissions currently grant packages:
write to all jobs; change this to enforce least privilege by moving packages:
write into the build-qa-docker job's permissions block (so build-qa-docker keeps
write access), set packages: read in the vulnerability-scan job's permissions
block, and remove or omit any packages permission from the set-matrix job (so it
has no package access); update the permissions stanza references for the jobs
named build-qa-docker, vulnerability-scan, and set-matrix accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
.github/workflows/build-qa-docker.yml (2)

547-547: Add a GHCR image cleanup step after scanning to prevent accumulation.

SHA-tagged QA images (ghcr.io/…/liquibase-{community|alpine|secure}:<sha>) are ephemeral by design but will accumulate in GHCR indefinitely. Consider adding a post-scan cleanup step using actions/delete-package-versions or a lifecycle policy on GHCR to auto-expire old untagged/SHA-tagged packages.

Also applies to: 632-632

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-qa-docker.yml at line 547, Add a GHCR image cleanup
step after the scan job to delete the ephemeral SHA-tagged images (e.g.,
ghcr.io/${{ github.repository }}/liquibase-${{ matrix.ghcr_name }}:${{
github.sha }}). Implement this by adding a new job step that runs
actions/delete-package-versions (or a similar action) and targets the package
name built from github.repository and liquibase-${{ matrix.ghcr_name }} with the
version equal to ${{ github.sha }} (or a filter for SHA tags), supplying the
required GITHUB_TOKEN and repository/package identifiers; ensure the step runs
after the scan step and/or is conditional on scan success so SHA-tagged QA
images are removed automatically.

3-7: Scope packages: write to the job that needs it.

Setting packages: write at the workflow top-level grants it to every job (set-matrix, vulnerability-scan, etc.). Only build-qa-docker pushes to GHCR; tightening this to job-level permissions follows least-privilege.

♻️ Proposed scoping
 permissions:
   contents: read
   id-token: write
   actions: read
-  packages: write

Then inside build-qa-docker:

   build-qa-docker:
     name: Build ${{ matrix.image_name }}
     runs-on: ubuntu-latest
     needs: set-matrix
+    permissions:
+      packages: write
     strategy:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-qa-docker.yml around lines 3 - 7, The top-level
permissions block currently grants packages: write globally; restrict it by
removing or changing packages at top-level to read (keep contents: read and
id-token: write as needed) and add packages: write specifically to the
build-qa-docker job's permissions block so only the build-qa-docker job has
write access to GHCR; update the workflow's permissions stanza and the
build-qa-docker job (identified as build-qa-docker) to reflect this
least-privilege change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/build-qa-docker.yml:
- Around line 622-638: The vulnerability-scan job comment flags three issues:
make the upstream build matrix non-fail-fast so needs.build-qa-docker.result
reflects per-variant results (add strategy: fail-fast: false to the
build-qa-docker job definition), ensure secrets are forwarded to the reusable
workflow (add secrets: inherit to the vulnerability-scan job that calls uses:
liquibase/build-logic/.github/workflows/reusable-vulnerability-scan.yml@main),
and pin the reusable workflow reference instead of using mutable `@main` (replace
uses: …@main and the build_logic_ref: main input with a fixed tag or commit
SHA); update the build-qa-docker job, the vulnerability-scan job’s uses
invocation, and the build_logic_ref input accordingly.

---

Nitpick comments:
In @.github/workflows/build-qa-docker.yml:
- Line 547: Add a GHCR image cleanup step after the scan job to delete the
ephemeral SHA-tagged images (e.g., ghcr.io/${{ github.repository
}}/liquibase-${{ matrix.ghcr_name }}:${{ github.sha }}). Implement this by
adding a new job step that runs actions/delete-package-versions (or a similar
action) and targets the package name built from github.repository and
liquibase-${{ matrix.ghcr_name }} with the version equal to ${{ github.sha }}
(or a filter for SHA tags), supplying the required GITHUB_TOKEN and
repository/package identifiers; ensure the step runs after the scan step and/or
is conditional on scan success so SHA-tagged QA images are removed
automatically.
- Around line 3-7: The top-level permissions block currently grants packages:
write globally; restrict it by removing or changing packages at top-level to
read (keep contents: read and id-token: write as needed) and add packages: write
specifically to the build-qa-docker job's permissions block so only the
build-qa-docker job has write access to GHCR; update the workflow's permissions
stanza and the build-qa-docker job (identified as build-qa-docker) to reflect
this least-privilege change.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

🔍 Vulnerabilities of liquibase/liquibase-secure:9535d63646e53a50a21eb41a41456ecd9fe1e6db

📦 Image Reference liquibase/liquibase-secure:9535d63646e53a50a21eb41a41456ecd9fe1e6db
digestsha256:42a7a103173ca3b0a2576705cea439cb32287a585f89c4bf37c1df4a158916f3
vulnerabilitiescritical: 0 high: 0 medium: 0 low: 0
platformlinux/amd64
size825 MB
packages476
📦 Base Image eclipse-temurin:21-jre
also known as
  • 21-jre-noble
  • 21.0.10_7-jre-noble
digestsha256:bb4d41d883e59e82cad021feb8e06401c15bff1d40bdaca23cabc48a80c3114b
vulnerabilitiescritical: 0 high: 0 medium: 9 low: 15

@liquibase liquibase deleted a comment from claude bot Feb 19, 2026
@liquibase liquibase deleted a comment from claude bot Feb 19, 2026
@liquibase liquibase deleted a comment from claude bot Feb 19, 2026
@claude
Copy link

claude bot commented Feb 19, 2026

Claude finished @jandroav's task in 1m 36s —— View job


Review

  • Read changed files and diff
  • Analyze current state of fixes
  • Post review findings

Previous issues have been addressed in follow-up commits: fail-fast: false was added to build-qa-docker, and the gh api graceful-failure fix (2>/dev/null || echo "") is now in both cleanup jobs. Two issues remain:

Unfixed - docker/login-action@v3 uses a mutable tag (build-qa-docker.yml:531): CodeQL still flags this. Pin to a commit SHA, e.g., docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.

Design limitation - scan condition blocks on any single build failure (build-qa-docker.yml:625): With needs.build-qa-docker.result == 'success', if any matrix variant fails to build, all vulnerability scans are skipped. With fail-fast: false, each variant fails independently, but the aggregate job result is still failure — so a failing secure build would also suppress community and alpine scans. Consider needs.build-qa-docker.result != 'cancelled' if partial scan coverage is acceptable.

Both are non-blocking for merge; the core implementation is correct.

@liquibase liquibase deleted a comment from claude bot Feb 19, 2026
jandroav and others added 5 commits February 19, 2026 11:06
Push QA images to GHCR alongside Nexus so the reusable vulnerability
scan workflow can pull and scan them before QA testing begins.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GHCR packages are namespaced under the repo name (docker/liquibase-*),
requiring URL-encoded slashes in API paths. Also adds fail-fast: false
to build-qa-docker strategy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Suppress gh api errors when package doesn't exist yet so the
cleanup step doesn't fail on first run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Feb 19, 2026

Claude finished @jnewton03's task in 1m 26s —— View job


Review

  • Read PR diff and changed files
  • Analyze current state vs previous review findings
  • Post review findings

Fixed since last review:

  • fail-fast: false added to build-qa-docker strategy ✅
  • GHCR cleanup job added (addresses ephemeral image accumulation) ✅
  • Graceful gh api failure handling (2>/dev/null || echo "") in both cleanup jobs ✅

One remaining issue:

docker/login-action@v3 (build-qa-docker.yml:531) still uses a mutable tag — CodeQL continues to flag this. A suggestion is on the inline comment. The existing Nexus login step above it has the same issue (docker/login-action@v3 at line 523), but that predates this PR.

Design note (non-blocking): The scan condition needs.build-qa-docker.result == 'success' means all variants are skipped if any single build fails — fail-fast: false ensures each build runs independently, but the aggregate job result is still failure if any variant fails, suppressing all scans. This was noted in the previous review and is acceptable for the current scope.


- name: Login to GHCR
if: ${{ steps.validate-build.outcome == 'success' }}
uses: docker/login-action@v3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker/login-action@v3 is still a mutable tag, flagged by CodeQL (same as the existing Nexus login step above it). Pin to a commit SHA to satisfy supply-chain checks:

Suggested change
uses: docker/login-action@v3
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.github/workflows/build-qa-docker.yml (1)

641-669: Extract duplicated cleanup script into a composite action.

The cleanup-ghcr step script (lines 651–669) is byte-for-byte identical to the one added in trivy.yml (lines 154–171). Any future changes (e.g., pagination, error handling, different API endpoint) need to be applied in two places.

Consider extracting this into a local composite action (e.g., .github/actions/cleanup-ghcr/action.yml) and invoking it from both workflows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-qa-docker.yml around lines 641 - 669, The inline
GHCR deletion script is duplicated in the cleanup-ghcr job (step "Delete GHCR
image") and in trivy.yml; extract it into a reusable composite action named
cleanup-ghcr and replace both inline steps with a single call to that action.
Implement the composite action to accept inputs/env for GH_TOKEN, repository
name/owner, ghcr_name and tag/sha, move the shell logic (gh api calls and
VERSION_ID handling) into the composite action's run step, and update both
workflows to invoke the cleanup-ghcr action and pass the required inputs/env so
future fixes are made in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/build-qa-docker.yml:
- Around line 91-96: The CI workflow's matrix strategy was missing fail-fast
behavior control; add or ensure strategy: fail-fast: false is present under the
workflow's strategy for the Build ${{ matrix.image_name }} job so individual
matrix variants won't cancel siblings—place the fail-fast: false key directly
under the strategy mapping (alongside matrix: ...) and verify YAML indentation
and syntax around the strategy block so the key is recognized.
- Around line 623-639: The vulnerability-scan job is skipping all scans because
its if condition checks needs.build-qa-docker.result == 'success' which becomes
false when any matrix build fails; remove that result check so the job runs
per-matrix for all variants that were produced. Concretely, keep needs:
[set-matrix, build-qa-docker] and strategy/fail-fast as-is, but change the if on
the vulnerability-scan job to not reference needs.build-qa-docker.result (e.g.,
if: ${{ !cancelled() }} or remove the if line) so the reusable workflow
invocation (uses: liquibase/build-logic/.../reusable-vulnerability-scan.yml)
runs for each matrix entry and will independently fail for missing images.

---

Nitpick comments:
In @.github/workflows/build-qa-docker.yml:
- Around line 641-669: The inline GHCR deletion script is duplicated in the
cleanup-ghcr job (step "Delete GHCR image") and in trivy.yml; extract it into a
reusable composite action named cleanup-ghcr and replace both inline steps with
a single call to that action. Implement the composite action to accept
inputs/env for GH_TOKEN, repository name/owner, ghcr_name and tag/sha, move the
shell logic (gh api calls and VERSION_ID handling) into the composite action's
run step, and update both workflows to invoke the cleanup-ghcr action and pass
the required inputs/env so future fixes are made in one place.

@rberezen
Copy link
Contributor

https://github.com/liquibase/docker/actions/runs/22169513679 this looks false positive, right?

@jandroav
Copy link
Contributor Author

https://github.com/liquibase/docker/actions/runs/22169513679 this looks false positive, right?

@rberezen it is also present in the production images https://github.com/liquibase/docker/actions/runs/22191852510

@filipelautert could you confirm?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments