Skip to content

fix(ci): harden GA publish guard — exact tag format, no shell interpolation - #767

Merged
andyne13 merged 1 commit into
mainfrom
hotfix/build-guard-hardening
Jul 24, 2026
Merged

fix(ci): harden GA publish guard — exact tag format, no shell interpolation#767
andyne13 merged 1 commit into
mainfrom
hotfix/build-guard-hardening

Conversation

@andyne13

@andyne13 andyne13 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Addresses the review findings on the verify-tag gate added in #764. Raised by @hedhoud on #765 and independently by CodeRabbit/zizmor. All three verified against the merged workflow.

Targets main rather than the back-merge branch (#765), because the affected guard is already on main — fixing it in the back-merge would leave main exposed while only develop got the fix.

1. GA tag format was too loose

The guard only rejected -rc., so these all passed and would publish GA images and move :latest:

Tag Before After
v2.0.1 builds builds
v2.0.1-rc.1 skips → build_rc.yml skips → build_rc.yml
v2.0.1-rc1 publishes as GA fails loud
v2.0.1-beta, v2.0.1-alpha.1 publishes as GA fails loud
vfoo, v-test publishes as GA fails loud

The -rc1 case is the sharp one: build_rc.yml triggers on v*-rc.*, which requires the dot — so a one-character typo matched neither workflow's intent and shipped a release candidate as GA.

Now validated against ^v[0-9]+\.[0-9]+\.[0-9]+$.

2. Shell injection via github.ref_name

${{ github.ref_name }} was expanded into the run body before the shell executed. git check-ref-format permits ;, $, backtick, ", |, & in ref names, so a crafted tag could execute arbitrary code — in a job holding packages: write and Docker Hub credentials.

Tag name and SHA are now passed via env: and referenced as "$TAG_NAME" / "$TAG_SHA".

3. Checkout persisted credentials

build_rc.yml already sets persist-credentials: false on all three checkouts (hardened in 95fd86fc); the new verify-tag checkout was inconsistent. Added. The repo is public, so the origin/main fetch still works without them.

Validation

Regex checked against every case above (plus v10.20.30, v2.0, v2.0.1.2, 2.0.1, and an injection-shaped tag — only exact vMAJOR.MINOR.PATCH passes). YAML re-parsed; confirmed no ${{ }} remains in the run body and all three build jobs are still gated by needs: verify-tag.

Once merged, #765 will be re-synced from main so the back-merge carries this too.

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened release validation for GA publishing.
    • Releases now require an exact semantic version tag format (vMAJOR.MINOR.PATCH).
    • Added verification that tagged commits are included in the main development branch.
    • Improved release-check error and success messaging.

…lation

Review findings on the verify-tag gate added in #764 (raised by @hedhoud and
CodeRabbit/zizmor), all three confirmed against the merged workflow:

1. Tag format was too loose. The guard only rejected '-rc.', so v2.0.1-rc1,
   v2.0.1-beta, vfoo etc. passed and would publish GA images and move :latest.
   The rc1 case is the sharp one: build_rc.yml triggers on 'v*-rc.*' which
   requires the dot, so a one-character typo matched neither workflow's intent.
   Now validated against ^v[0-9]+\.[0-9]+\.[0-9]+$ and failed loud.

2. Template injection. ${{ github.ref_name }} expanded into the run body before
   the shell ran, and git permits ; $ ` " | & in ref names — arbitrary code
   execution in a job that holds packages:write and Docker Hub credentials.
   Tag name and SHA now passed via env: and referenced as shell variables.

3. Checkout persisted credentials, inconsistent with build_rc.yml which already
   sets persist-credentials: false on all three checkouts (95fd86f). The repo
   is public, so the origin/main fetch still works without them.

Behavior: vX.Y.Z on main builds; vX.Y.Z-rc.N skips to build_rc.yml; malformed
or prerelease tags and off-main tags now fail loudly instead of publishing.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The verify-tag job now requires exact GA semantic version tags, verifies their commits are ancestors of origin/main, and disables persisted checkout credentials.

Changes

GA release gating

Layer / File(s) Summary
Tag and commit validation
.github/workflows/build.yml
The workflow disables persisted checkout credentials, validates vMAJOR.MINOR.PATCH tag names, and checks tagged commit ancestry against origin/main with updated failure and success messages.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • linagora/openrag#764: Also updates the build workflow’s GA tag verification and release-job dependencies.

Suggested labels: chore

Suggested reviewers: enjoybacon7

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main workflow hardening changes around tag validation and safer shell handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix/build-guard-hardening

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.

@coderabbitai coderabbitai Bot added the chore No production code impact, typically improve tooling, code quality, etc label Jul 24, 2026
@andyne13

Copy link
Copy Markdown
Contributor Author

@hedhoud this is the fix for both findings you raised on #765 — could you take a look when you have a moment?

Short version of what changed in the verify-tag step:

  • GA tags now validated against ^v[0-9]+\.[0-9]+\.[0-9]+$, so v2.0.1-rc1 / -beta / vfoo fail loudly instead of publishing GA images and moving :latest. Well-formed -rc. tags still skip to build_rc.yml as before.
  • Tag name and SHA passed through env: and referenced as "$TAG_NAME" / "$TAG_SHA" — no ${{ }} left in the run body.
  • Added persist-credentials: false, matching what build_rc.yml already does.

Worth flagging for prioritisation: the loose guard is what v2.0.1 shipped with and it is live on main until this merges. It needs push access to exploit, so not an emergency, but I would rather not leave it sitting.

Once this lands I will re-sync #765 from main so the back-merge carries it, then merge that.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build.yml:
- Around line 39-45: Update the tag gating around the workflow’s job-level if
condition so all tag refs, including non-v tags such as 1.2.3 and release-1,
reach the exact GA validation. Preserve the explicit valid-RC path handled by
build_rc.yml, while ensuring malformed or unsupported tags are rejected by the
existing TAG_NAME format check before publishing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 150b1581-2eda-4521-a0ea-bb78b99ac873

📥 Commits

Reviewing files that changed from the base of the PR and between 6a18a53 and 4446710.

📒 Files selected for processing (1)
  • .github/workflows/build.yml

Comment thread .github/workflows/build.yml

@hedhoud hedhoud left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved. This addresses the two blocking findings from #765: malformed v* prerelease/typo tags are rejected before any publish job can run, and the tag/SHA values are now passed through env instead of being interpolated into the shell script.

I do not consider the remaining CodeRabbit comment about non-v tags blocking here: this workflow is configured to trigger only on v* tag pushes, so tags like 1.2.3 or release-1 do not enter this publishing workflow and cannot publish GA images.

@andyne13

Copy link
Copy Markdown
Contributor Author

Re the remaining CodeRabbit thread (build.yml:45, "do not bypass validation for non-v tags") — declining this one, false positive.

The premise is that tags like 1.2.3 or release-1 reach the verify-tag step and escape validation. They do not: the workflow trigger is

on:
  push:
    tags: ["v*"]

so a non-v tag never triggers build.yml in the first place — the job-level if is never evaluated and nothing can be published. The startsWith(github.ref, 'refs/tags/v') clause is defensive redundancy with that filter (and correctly excludes workflow_dispatch, where github.ref is a branch).

Adopting the suggestion would mean broadening on: to all tag refs so malformed ones fail loudly. That is worse for this repo: build.yml would then run on every tag, including non-release ones such as archive/dev-github-flow, producing red failures on tags that were never meant to publish.

The forgot-the-v case (2.0.2 instead of v2.0.2) does silently do nothing — but it publishes nothing either, and Phase 6 of the release runbook already requires confirming the build actually started before moving on. The asymmetry is deliberate: publishing the wrong thing is the dangerous failure and is now blocked; not publishing is immediately visible.

Resolving the thread on that basis.

@andyne13
andyne13 merged commit 4483394 into main Jul 24, 2026
6 checks passed
@andyne13
andyne13 deleted the hotfix/build-guard-hardening branch July 24, 2026 10:06
@andyne13 andyne13 mentioned this pull request Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore No production code impact, typically improve tooling, code quality, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants