You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(release): require verified version-tag pushes for GHCR - #1205
Harden GHCR publishing so an image can be published only from a real push of a version tag whose annotated tag and target commit pass the repository GitHub signature verifier, and whose exact commit passes the three-lockfile OSV audit. Manual Docker dispatch remains audit-only.
Security controls
Add a read-only tag-verification job using the existing signature verifier and full tag history.
Require both verifier success and exact-SHA audit success in the Docker publishing job.
Require the release-policy checker to match the complete approved conjunction and reject permissive alternatives.
Reject skipped or non-blocking steps in the verifier job.
Keep the proof workflow write-free; its explicit inputs simulate event, ref, and signature outcomes and do not verify a real tag.
Validation
Release-security tests: 42 passed, including negative cases for step-level if and continue-on-error.
Workflow-policy and release-security checks: passed.
The current main changelog checker was exercised with PR 1205 metadata: passed.
Biome, git diff check, PR budget, and pnpm run ci:prepush: passed.
Budget: 8 files, 621 meaningful lines, 4 signed commits; below hard limits, with the repository target-tier caution.
Exact-head hosted CodeScene, CodeQL, Security Audit, Verified Signatures, Workflow Policy, Changelog Guard, Semgrep, Sourcery, and CodeAnt checks: passed. Node 22 and Node 24 quality gates are still running.
All existing actionable inline review findings are resolved. CodeRabbit latest review attempt was rate-limited; this is not represented as a pass. The local CodeScene CLI could not complete its license check, so the hosted CodeScene result is the available evidence.
Release boundary
No Docker publishing workflow or release was dispatched. This PR does not authorize the v1.29.3 tag; release status remains HOLD pending its separate exact-candidate qualification.
Summary by Sourcery
Require GHCR images to publish only from fully verified protected version-tag pushes that pass the exact-commit security audit.
New Features:
Require GHCR publishing to originate from protected version-tag pushes whose annotated tag and target commit are GitHub-verified and bound to the pushed commit.
Add protected-main ancestry and tag-integrity checks to release signature verification.
Keep manual Docker dispatch available for audit diagnostics while preventing it from publishing images.
Bug Fixes:
Prevent retargeted, lightweight, unsigned, unprotected, or changed release tags from publishing container images.
Prevent permissive workflow conditions, skipped verifier steps, and ignored verifier failures from bypassing release security gates.
Enhancements:
Strengthen release-policy validation to require exact fail-closed conditions and the complete verifier-plus-audit dependency conjunction.
Add a no-write release-gate proof workflow that simulates valid and invalid publishing inputs.
Update release, CI, and testing documentation and published test metrics.
Documentation:
Document the protected-tag, signature-verification, exact-commit audit, and manual-dispatch publishing boundaries.
Tests:
Expand release-security policy coverage with negative cases for bypass conditions, environment injection, skipped steps, and non-blocking verifier failures.
Add coverage for tag target binding, protected-main ancestry, tag retargeting, tag-name mismatches, and verification races.
Chores:
Update the unreleased changelog with the GHCR publishing security requirements.
CodeAnt-AI Description
Require verified protected version tags for GHCR publishing
What Changed
GHCR images publish only after a real push of a protected v* tag whose annotated tag and target commit are signed and match the pushed commit in protected main history.
Publishing also requires a successful security audit of that exact commit. Manual Docker runs can audit but never publish.
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
@codeant-ai ask: Your question here
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
@codeant-ai ask: Can you suggest a safer alternative to storing this secret?
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
@codeant-ai: Your feedback here
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
@codeant-ai: Do not flag unused imports.
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
@codeant-ai: review
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit
Release Security
Container images now publish only from protected v* tag pushes after the tag signature and commit are verified and the exact commit passes the security audit. Manual dispatches do not publish images.
Release-tag checks confirm the signed tag matches the triggering commit and belongs to protected main history.
Documentation
Clarified release-publishing requirements and that the release-gate proof simulates inputs rather than verifying a real tag.
Require a real push event for v-prefixed tag refs in the Docker publish job while preserving the exact audited-SHA gate. Keep workflow_dispatch available only for the no-write release-gate proof, with explicit simulated tag-push inputs and negative coverage for manual dispatch.
Verify that the annotated tag and target commit are signed, match the pushed commit, belong to protected main history, and remain unchanged during verification.
scripts/signing/verify-remote.mjs L37-62, L106-120, L146-160 (+3 small edits)
Define and enforce exact fail-closed release conditions, including the trusted verifier job, its pinned checkout and environment, and mandatory verifier dependencies for Docker publishing.
scripts/release-security-gate.mjs L21-64, L200-222, L229-347 (+3 small edits)
Configuration
Gate GHCR publication on a protected real version-tag push, successful tag verification, and a successful exact-SHA security audit, while keeping manual dispatch non-publishing.
.github/workflows/docker.yml L8, L72-114
Extend the dispatch-only proof workflow with explicit simulated tag-push and signature inputs while retaining an echo-only, no-write stand-in.
Record the GHCR publishing boundary and update the README's synchronized test-count metrics.
CHANGELOG.md L10-11
README.md L23-1032 (4 small edits)
Document the additional GHCR tag-verification gate, its manual-dispatch behavior, and the simulated nature of the no-write proof; retain the surrounding CI reference and table formatting updates in this documentation change.
docs/CI.md L68-78, L139-158, L544-564 (+10 small edits)
Tests
Cover event-to-commit binding and verified annotated-tag checks in the existing GitHub signature API tests.
tests/unit/signing.test.ts L883-896 (+3 small edits)
Add positive and negative tests for the release workflow policy, including bypass conditions, verifier configuration, and simulated proof inputs.
tests/unit/tooling/releaseSecurityGate.test.ts L33-42, L57-259, L452-478 (+3 small edits)
Exercise release-tag SHA binding, signature checks, protected-main ancestry, tag stability, and fail-closed handling of malformed or lightweight tags.
tests/unit/tooling/verifyRemoteTag.test.ts L1-257
Tips and commands
Interacting with Sourcery
Trigger a new review: Comment @sourcery-ai review on the pull request.
Continue discussions: Reply directly to Sourcery's review comments.
Generate a GitHub issue from a review comment: Ask Sourcery to create an
issue from a review comment by replying to it. You can also reply to a
review comment with @sourcery-ai issue to create an issue from it.
Generate a pull request title: Write @sourcery-ai anywhere in the pull
request title to generate a title at any time. You can also comment @sourcery-ai title on the pull request to (re-)generate the title at any time.
Generate a pull request summary: Write @sourcery-ai summary anywhere in
the pull request body to generate a PR summary at any time exactly where you
want it. You can also comment @sourcery-ai summary on the pull request to
(re-)generate the summary at any time.
Generate reviewer's guide: Comment @sourcery-ai guide on the pull
request to (re-)generate the reviewer's guide at any time.
Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
pull request to resolve all Sourcery comments. Useful if you've already
addressed all the comments and don't want to see them anymore.
Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
request to dismiss all existing Sourcery reviews. Especially useful if you
want to start fresh with a new review - don't forget to comment @sourcery-ai review to trigger a new review!
Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).
Limit details: You’ve used the included review currently available. Your 80 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.
Reviewing files that changed from the base of the PR and between d9461b4 and 9fe3583.
Files selected for processing: 3
.github/workflows/docker.yml
scripts/release-security-gate.mjs
tests/unit/tooling/releaseSecurityGate.test.ts
Walkthrough
The release verifier now checks that a signed version tag targets the triggering commit and that the commit belongs to protected main. Docker publishing requires protected tag-push conditions, verifier success, and a successful exact-commit audit. The proof workflow simulates these inputs. The README updates its test totals.
Changes: Release Security Gates
Layer / File(s)
Summary
Bind tag verification to the pushed commit scripts/signing/verify-remote.mjs, scripts/signing/verify-remote.d.mts, scripts/signing/verify-github-signatures.mjs, tests/unit/signing.test.ts, tests/unit/tooling/verifyRemoteTag.test.ts
The verifier checks the pushed commit SHA, annotated tag name and target, protected-main ancestry, and tag-ref stability. Tests cover valid and rejected verification paths.
Verify tags before Docker publishing .github/workflows/docker.yml
A pinned verifier job runs for protected version-tag pushes. The publishing job requires verifier success, audit success, and an audited SHA that matches github.sha.
The proof workflow requires inputs that simulate a verified version-tag push. The changelog and CI documentation describe Docker publishing conditions and clarify that the proof does not verify a real tag.
README Test Count
Layer / File(s)
Summary
Update synchronized test counts README.md
The README updates its reported test count to 8,724+ across 656 files.
Merge Risk: Low · previous review d9461
The release-tag verification logic looks sound. Before merge, tidy the duplicated test calls that fail the CodeScene gate and bound the verifier-job slice in the policy test so it actually checks that job.
The verifier looks up this mutable tag name but never compares its target with github.sha; a retag between push and lookup can verify one commit while publishing another.
The reason will be displayed to describe this comment to others. Learn more.
Hey - I've found 3 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments### Comment 1
<locationpath=".github/workflows/docker.yml"line_range="77" />
<code_context>
needs: [security-audit]
if: >-
- ${{ needs.security-audit.result == 'success' && needs.security-audit.outputs.audited-sha == github.sha }}+ ${{ github.event_name == 'push' && github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v') && needs.security-audit.result == 'success' && needs.security-audit.outputs.audited-sha == github.sha }}
runs-on: ubuntu-latest
timeout-minutes: 20
</code_context>
<issue_to_address>
**Unsigned tags publish images**
When an unsigned `v*` tag is pushed and its commit passes the audit, `build-push` checks the tag ref and audit result but not the tag signature, so it publishes the image to GHCR from an unsigned tag.
Require successful signature verification of the exact tag before allowing `build-push` to publish.
Also at `.github/workflows/docker.yml:75`.
</issue_to_address>
### Comment 2
<locationpath="scripts/release-security-gate.mjs"line_range="179-180" />
<code_context>
-function checkGateCondition(jobName, condition, fail) {
- const missing = REQUIRED_GATE_CONDITIONS.filter((required) => !condition.includes(required));
+function checkGateCondition(jobName, condition, fail, requiredEventConditions = []) {
+ const missing = [...REQUIRED_GATE_CONDITIONS, ...requiredEventConditions].filter(
+ (required) => !condition.includes(required),
+ );
for (const required of missing) fail(`job "${jobName}" if: must require ${required}`);
</code_context>
<issue_to_address>
**Publish gate bypasses pass checks**
When a publish condition retains the required text in one branch but adds a permissive `||` branch, `checkGateCondition` and the substring assertions in `releaseSecurityGate.test.ts` accept the condition, so a workflow can publish through the alternate branch while the policy check and test pass.
Validate that every required predicate applies to every publishing branch, and add tests that reject conditions with permissive `||` branches.
Also at `scripts/release-security-gate.mjs:181`, `tests/unit/tooling/releaseSecurityGate.test.ts:46`, `tests/unit/tooling/releaseSecurityGate.test.ts:261`.
</issue_to_address>
### Comment 3
<locationpath=".github/workflows/release-gate-proof.yml"line_range="97" />
<code_context>
needs: [security-audit]
if: >-
- ${{ needs.security-audit.result == 'success' && needs.security-audit.outputs.audited-sha == github.sha }}+ ${{ github.event_name == 'workflow_dispatch' && inputs.simulated_event == 'push' && inputs.simulated_ref_type == 'tag' && startsWith(inputs.simulated_ref, 'refs/tags/v') && needs.security-audit.result == 'success' && needs.security-audit.outputs.audited-sha == github.sha }}
runs-on: ubuntu-latest
timeout-minutes: 2
</code_context>
<issue_to_address>
**Proof gate no longer matches publishing**
The stand-in now requires simulated-event inputs, while Docker checks the actual push event and ref. The proof therefore validates a different condition from the real publishing gate, so callers no longer get the required parity assurance.
Preserve the proof workflow’s required condition parity with the actual publish gate, or update the applicable repository guidance if the simulation model intentionally replaces that requirement.
</issue_to_address>
Sourcery assessment
Needs a human reviewer. 3 findings to address first, and a pushed tag beginning with v can now authorize the GHCR publish job once its commit passes the audit; the condition does not verify that the tag is signed or otherwise approved. If an unintended tag publishes an image, reverting restores the previous gate but does not retract an image that was already pushed.
The PR appears safe to merge; Docker publishing requires a protected signed tag push and a successful audit of the exact triggering commit.
The verifier binds the tag target to the push SHA, checks signatures and protected-main ancestry, then confirms the tag did not change during verification.
Reviewing files that changed from the base of the PR and between d9c02fc and e9b8e22.
Files selected for processing: 5
.github/workflows/docker.yml
.github/workflows/release-gate-proof.yml
README.md
scripts/release-security-gate.mjs
tests/unit/tooling/releaseSecurityGate.test.ts
Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.
Require Docker publication to depend on the repository's annotated-tag and target-commit signature verifier, in addition to the exact-SHA OSV audit and real version-tag push. Make the release-security checker require an exact conjunctive condition and reject alternate OR paths; model signature success explicitly in the no-write proof and document the distinction.
qnbs
changed the title
fix(release): require version-tag pushes for GHCR publishing
fix(release): require verified version-tag pushes for GHCR
Nov 8, 2026
The earlier CodeAnt PR Risk summary was assessed against e89e8d38491c; it is superseded by the later fixes. Signed commit c548ef2456c171cb3ee7ccb24f81b18960df9c40 added exact tag-signature verification and fail-closed condition validation; f5d843bbdd02fc46c38e96f9829de67939861574 additionally rejects verifier steps with if: or continue-on-error. The current CodeAnt Quality Gates report for f5d843bb is PASS, and the focused release-security suite is 42/42 locally. Please assess the current PR head for any remaining actionable finding.
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: The verifier looks up this mutable tag name but never compares its target with github.sha; a retag between push and lookup can verify one commit while publishing another.
This is a comment left during a code review.
**Path:** .github/workflows/docker.yml
**Line:** 89:89
**Comment:***Race Condition: The verifier looks up this mutable tag name but never compares its target with `github.sha`; a retag between push and lookup can verify one commit while publishing another.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Compare the annotated tag target with the push event's after SHA and GitHub's commit SHA before accepting the tag signature. Reject missing, malformed, or retargeted targets and cover the failure paths with focused tests.
The reason will be displayed to describe this comment to others. Learn more.
Validate ancestry using fields the compare API actually returns
For a valid signed release tag targeting protected main, this condition still throws because GitHub's compare-commits response contains base_commit, merge_base_commit, and commits, but no head_commit. Consequently, undefined !== mainSha is always true, blocking both GHCR publishing and the Tauri tag-release workflow. The new tests hide this by fabricating head_commit in their responses. Validate ancestry against the actual response contract and update the fixtures accordingly; also advance Docker's trusted-verifier pin, since revision 71dd3b6 contains the same check and would remain broken after a source-only fix.
The reason will be displayed to describe this comment to others. Learn more.
Nitpick comments: 2
tests/unit/tooling/verifyRemoteTag.test.ts: 1
76-82: Trivial · Maintainability & Code Quality · Low value
Extract a helper for the repeated verifyRemoteTag call.
Nearly every test repeats the same owner, repo, tag, and expectedCommitSha arguments. The CodeScene gate fails on this file with a code-duplication finding (score 9.39). A small verify(fetchImpl, overrides) helper clears the finding. It also keeps later argument changes in one place.
Bound the verifier slice to the verify-release-tag job.
The slice call runs from verify-release-tag: to the end of docker.yml. The build-push job is therefore inside verifier. Substring assertions such as persist-credentials: false, fetch-depth: 1, and GITHUB_TOKEN: ${{ github.token }} can pass because of text in a later job. The test then misses a regression in the verifier job.
Cut the slice at the next job boundary so that the assertions apply only to the verifier job.
Reviewing files that changed from the base of the PR and between e9b8e22 and d9461b4.
Files selected for processing: 13
.github/workflows/docker.yml
.github/workflows/release-gate-proof.yml
CHANGELOG.md
README.md
docs/CI.md
scripts/release-security-gate.d.mts
scripts/release-security-gate.mjs
scripts/signing/verify-github-signatures.mjs
scripts/signing/verify-remote.d.mts
scripts/signing/verify-remote.mjs
tests/unit/signing.test.ts
tests/unit/tooling/releaseSecurityGate.test.ts
tests/unit/tooling/verifyRemoteTag.test.ts
Files skipped from review as they are similar to previous changes: 1
README.md
Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
User description
Summary
Harden GHCR publishing so an image can be published only from a real push of a version tag whose annotated tag and target commit pass the repository GitHub signature verifier, and whose exact commit passes the three-lockfile OSV audit. Manual Docker dispatch remains audit-only.
Security controls
Validation
Release boundary
No Docker publishing workflow or release was dispatched. This PR does not authorize the v1.29.3 tag; release status remains HOLD pending its separate exact-candidate qualification.
Summary by Sourcery
Require GHCR images to publish only from fully verified protected version-tag pushes that pass the exact-commit security audit.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests:
Chores:
CodeAnt-AI Description
Require verified protected version tags for GHCR publishing
What Changed
v*tag whose annotated tag and target commit are signed and match the pushed commit in protected main history.Impact
✅ GHCR publishes only verified protected version tags✅ Manual dispatch cannot publish container images✅ Retargeted tags cannot publish images💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit
Release Security
v*tag pushes after the tag signature and commit are verified and the exact commit passes the security audit. Manual dispatches do not publish images.mainhistory.Documentation