Skip to content

Add two-phase release process with automated workflows#494

Open
Boomatang wants to merge 1 commit into
mainfrom
release_process
Open

Add two-phase release process with automated workflows#494
Boomatang wants to merge 1 commit into
mainfrom
release_process

Conversation

@Boomatang

@Boomatang Boomatang commented Jun 9, 2026

Copy link
Copy Markdown
Member

Summary

Replaces the manual, multi-step release process with an automated two-phase workflow driven by GitHub Actions and a release.yaml version manifest.

  • Phase 1 (Pre-release): A workflow_dispatch workflow creates a release branch, bumps versions in release.yaml and both Cargo.toml files, and opens a PR against the release branch. For minor releases it also opens a post-release PR to main with the next -dev versions.
  • Phase 2 (Release): A second workflow_dispatch workflow reads versions from release.yaml, runs smoke tests (fmt, clippy, full test suite, publish dry-run), creates annotated git tags, publishes the crate to crates.io, builds multi-arch container images, and creates the GitHub Release only after all artifacts succeed.
  • Version gate: A new CI check on PRs targeting release-* branches validates that release.yaml contains non-sentinel, non-dev versions and that any declared dependency releases exist.
  • Reusable image build: The existing build-image.yaml workflow now accepts ref and tag inputs via workflow_call, allowing the release workflow to trigger image builds for a specific ref and tag while preserving the existing push-on-main behavior.
  • Adds publish = false to limitador-server/Cargo.toml to prevent accidental publication of the server binary crate.
  • Introduces release.yaml at the repo root as the single source of truth for version numbers, using 0.0.0 as the sentinel value on main.
  • Adds helper scripts (parse-version.sh, validate-release-yaml.sh) with documentation covering usage and error cases.
  • Rewrites RELEASE.md to document the new process including minor releases, patch releases, rollback procedures, and file inventory.

Examples

Pre-release Workflow: https://github.com/Boomatang/limitador/actions/runs/28022410045
PR to release branch: Boomatang#5
PR to main (dev version bump): Boomatang#6
Release Workflow: https://github.com/Boomatang/limitador/actions/runs/28028029786
GitHub release: https://github.com/Boomatang/limitador/releases

Summary by CodeRabbit

Release Notes

  • New Features

    • Added an automated, two-phase release pipeline with version validation and strict branch naming.
    • Introduced version gating checks for release-related changes.
    • Added automated tagging, smoke testing, crate publishing (dry-run) and container image building.
    • Enabled separate tracking of server vs crate versions for each release.
  • Documentation

    • Updated release documentation to reflect the new preparation-and-release process.
  • Chores

    • Disabled publishing of the server crate to the registry.

@Boomatang Boomatang self-assigned this Jun 9, 2026
@Boomatang Boomatang moved this to Ready For Review in Kuadrant Jun 9, 2026
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Boomatang, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 30 minutes and 3 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cddeacb9-a178-4291-8728-d5fd7c8faf22

📥 Commits

Reviewing files that changed from the base of the PR and between 38fcfc8 and afd9fe2.

📒 Files selected for processing (10)
  • .github/scripts/README.md
  • .github/scripts/parse-version.sh
  • .github/scripts/validate-release-yaml.sh
  • .github/workflows/build-image.yaml
  • .github/workflows/pre-release.yaml
  • .github/workflows/release.yaml
  • .github/workflows/version-gate.yaml
  • RELEASE.md
  • limitador-server/Cargo.toml
  • release.yaml
📝 Walkthrough

Walkthrough

This PR adds release metadata parsing and validation, new pre-release and release workflows, reusable image-building support, and updated release documentation and crate publishing settings.

Changes

Release Automation Pipeline

Layer / File(s) Summary
Release configuration and validation foundation
release.yaml, .github/scripts/README.md, .github/scripts/parse-version.sh, .github/scripts/validate-release-yaml.sh
release.yaml defines limitador server and crate versions plus dependencies. The scripts parse version components, derive the release branch name, validate semver and branch-specific rules, and check dependency release tags.
Version gate protection workflow
.github/workflows/version-gate.yaml
Adds a PR workflow for release-* branches that runs the release YAML validation script when release.yaml changes.
Build-image workflow reusability
.github/workflows/build-image.yaml
Adds reusable workflow-call inputs, checks out the supplied ref, and changes Docker tag selection to use the provided tag or branch-based defaults.
Pre-release preparation workflow
.github/workflows/pre-release.yaml
Adds a manual workflow that validates inputs, prepares release and pre-release branches, updates release.yaml and Cargo manifests, commits changes, opens a pre-release PR, and optionally creates a post-release branch and PR back to main.
Release orchestration workflow
.github/workflows/release.yaml
Replaces the release workflow with jobs that read version metadata, run smoke tests, create tags, publish the crate, build images through the reusable workflow, and create the GitHub release.
Documentation and manifest updates
RELEASE.md, limitador-server/Cargo.toml, README.md
RELEASE.md documents the workflow-based release process, limitador-server/Cargo.toml disables publishing, and README.md gains a new heading.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through branches, tags, and checks,
With release notes and tidy specs.
The workflows hum, the crates align,
A careful trail from branch to sign.

🚥 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 summarises the main change: replacing the release flow with a two-phase automated workflow.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release_process

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 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: 5

🧹 Nitpick comments (4)
.github/workflows/release.yaml (2)

32-36: ⚡ Quick win

Consider using a yq GitHub Action or verifying the download checksum.

Downloading yq directly from GitHub releases without checksum verification could expose the workflow to supply chain risks. This is the same pattern used in pre-release.yaml.

🔒 Alternative approaches

Option 1: Use a community action

- uses: mikefarah/yq@v4.34.2

Option 2: Verify checksum

- name: Install yq
  run: |
    sudo wget --quiet --output-document=/usr/local/bin/yq \
      https://github.com/mikefarah/yq/releases/download/v4.34.2/yq_linux_amd64
    echo "expected_sha256  /usr/local/bin/yq" | sha256sum --check
    sudo chmod +x /usr/local/bin/yq
🤖 Prompt for 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.

In @.github/workflows/release.yaml around lines 32 - 36, The "Install yq" step
currently downloads yq binary directly without verification; update that step
(the step named "Install yq") to either use the official GitHub Action (replace
the run block with uses: mikefarah/yq@v4.34.2) or, if keeping the direct
download, add a checksum verification after download (compute and check the
expected SHA256 for /usr/local/bin/yq before chmod +x) to ensure integrity and
mitigate supply-chain risk.

159-162: 🏗️ Heavy lift

Consider migrating to trusted publishing for crates.io.

Using CARGO_REGISTRY_TOKEN requires managing a long-lived secret. Crates.io supports OIDC-based trusted publishing, which eliminates the need for secrets and provides better security through short-lived tokens.

See: https://doc.rust-lang.org/cargo/reference/publishing.html#trusted-publishing

🤖 Prompt for 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.

In @.github/workflows/release.yaml around lines 159 - 162, The current workflow
step "Publish to crates.io" uses a long‑lived secret via the
CARGO_REGISTRY_TOKEN env variable and runs `cargo publish --package limitador`;
replace this with OIDC trusted publishing by removing the CARGO_REGISTRY_TOKEN
usage and updating the step to request short‑lived tokens via GitHub Actions
OIDC (enable id-token and use the cargo/registry publish pattern supported by
crates.io), adjust the step named "Publish to crates.io" to request id-token:
write permissions and perform `cargo publish --package limitador` without
reading CARGO_REGISTRY_TOKEN, and ensure the workflow uses the recommended OIDC
configuration per the crates.io trusted publishing docs.
.github/workflows/build-image.yaml (1)

112-119: 💤 Low value

Consider making the tag selection logic more explicit.

The current implementation checks inputs.tag == '' to determine whether to use the provided tag or generate one based on the trigger. Whilst this works (optional string inputs default to empty string), the logic could be clearer by checking the event type explicitly.

♻️ Alternative approach
          tags: |
            # When called from release workflow, use the provided tag
-           type=raw,value=${{ inputs.tag }},enable=${{ inputs.tag != '' }}
+           type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_call' && inputs.tag != '' }}
            # SHA tag for main branch
-           type=raw,value=${{ github.sha }},enable=${{ inputs.tag == '' && github.ref_name == env.MAIN_BRANCH_NAME }}
+           type=raw,value=${{ github.sha }},enable=${{ github.event_name != 'workflow_call' && github.ref_name == env.MAIN_BRANCH_NAME }}
            # set latest tag for main branch
-           type=raw,value=latest,enable=${{ inputs.tag == '' && github.ref_name == env.MAIN_BRANCH_NAME }}
+           type=raw,value=latest,enable=${{ github.event_name != 'workflow_call' && github.ref_name == env.MAIN_BRANCH_NAME }}
            # set ref name tag for non-main branches
-           type=raw,value=${{ github.ref_name }},enable=${{ inputs.tag == '' && github.ref_name != env.MAIN_BRANCH_NAME }}
+           type=raw,value=${{ github.ref_name }},enable=${{ github.event_name != 'workflow_call' && github.ref_name != env.MAIN_BRANCH_NAME }}
🤖 Prompt for 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.

In @.github/workflows/build-image.yaml around lines 112 - 119, The tag selection
block that currently relies on inputs.tag == '' is ambiguous; update the
conditional logic to explicitly detect a provided release tag or a release event
by using github.event_name and inputs.tag presence: when inputs.tag is non-empty
(inputs.tag != '') use that value, else if github.event_name == 'release' use
github.event.release.tag_name, else if github.ref_name == env.MAIN_BRANCH_NAME
emit both github.sha and latest as before, else use github.ref_name for
branch-based tags—apply these checks to the four type=raw entries (the tag
selection lines) so the intent is explicit and correct.
.github/workflows/pre-release.yaml (1)

95-99: ⚡ Quick win

Consider using a yq GitHub Action or verifying the download checksum.

Downloading yq directly from GitHub releases without checksum verification could expose the workflow to supply chain risks if the release is compromised or the download is intercepted.

🔒 Alternative approaches

Option 1: Use a community action

- uses: mikefarah/yq@v4.34.2

Option 2: Verify checksum

- name: Install yq
  run: |
    sudo wget --quiet --output-document=/usr/local/bin/yq \
      https://github.com/mikefarah/yq/releases/download/v4.34.2/yq_linux_amd64
    echo "expected_sha256  /usr/local/bin/yq" | sha256sum --check
    sudo chmod +x /usr/local/bin/yq
🤖 Prompt for 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.

In @.github/workflows/pre-release.yaml around lines 95 - 99, The workflow step
named "Install yq" currently downloads yq_linux_amd64 directly; replace this
with a safer approach by either using the official GitHub Action
(mikefarah/yq@v4.34.2) instead of manual wget, or keep the manual install but
add checksum verification before chmod (compute/verify the expected SHA256 for
the v4.34.2 binary and run sha256sum --check on the downloaded file), and ensure
the step logs which approach it used for traceability.
🤖 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/scripts/README.md:
- Around line 39-49: The fenced code block showing version/major/minor/patch
values is missing a language identifier causing markdownlint MD040; update the
opening fence from ``` to ```text to explicitly mark it as a text block so the
linter passes and the block renders correctly, i.e., modify the fence that
precedes the lines "version=2.5.0" ... "crate-patch=0" in the README.md snippet.

In @.github/scripts/validate-release-yaml.sh:
- Around line 13-35: The current checks in validate-release-yaml.sh only reject
"0.0.0" and "-dev" but allow null/empty or malformed versions; update the
validation for VERSION and CRATE_VERSION (the variables set from yq) to also
fail when the value is null/empty and to enforce a basic semver pattern (e.g.
major.minor.patch with optional prerelease/build parts) using a regex match;
modify the existing branch-release block that references VERSION and
CRATE_VERSION so it first checks for empty/null values and then applies the
semver regex before the existing "0.0.0" and "-dev" checks, making sure the
script exits with the same error pattern when the checks fail.
- Around line 38-41: The current lookup builds a dot-path via yq
".dependencies.${dep}" which fails for keys with characters like '-' — update
the dep_version lookup to use bracketed key indexing with yq and strenv: export
the loop variable into an environment variable (e.g., DEP="$dep") and call yq
with the expression ".dependencies[strenv(DEP)]" (using the same RELEASE_YAML
variable) so keys are matched exactly; adjust the dep_version assignment in the
for-loop that defines DEPS/dep to export DEP and use the bracketed strenv lookup
instead of the dot-path.

In @.github/workflows/build-image.yaml:
- Around line 11-14: The workflow input "tag" is incorrectly marked required:
true while downstream logic expects an optional value; update the "tag" input
definition (the input named tag) to not be required (set required: false) and
add a default: '' (empty string) so inputs.tag is defined when omitted, ensuring
the tag-generation logic that checks inputs.tag != '' works without validation
errors.

In @.github/workflows/version-gate.yaml:
- Line 18: Replace the floating ref for actions/checkout (currently
"actions/checkout@v6") with the specific v6 commit SHA and add the checkout
input persist-credentials: false; specifically update the checkout step that
references actions/checkout@v6 to use the exact commit SHA for v6 and include
persist-credentials: false under that step so the job does not retain Git
credentials and the action is pinned to a fixed commit.

---

Nitpick comments:
In @.github/workflows/build-image.yaml:
- Around line 112-119: The tag selection block that currently relies on
inputs.tag == '' is ambiguous; update the conditional logic to explicitly detect
a provided release tag or a release event by using github.event_name and
inputs.tag presence: when inputs.tag is non-empty (inputs.tag != '') use that
value, else if github.event_name == 'release' use github.event.release.tag_name,
else if github.ref_name == env.MAIN_BRANCH_NAME emit both github.sha and latest
as before, else use github.ref_name for branch-based tags—apply these checks to
the four type=raw entries (the tag selection lines) so the intent is explicit
and correct.

In @.github/workflows/pre-release.yaml:
- Around line 95-99: The workflow step named "Install yq" currently downloads
yq_linux_amd64 directly; replace this with a safer approach by either using the
official GitHub Action (mikefarah/yq@v4.34.2) instead of manual wget, or keep
the manual install but add checksum verification before chmod (compute/verify
the expected SHA256 for the v4.34.2 binary and run sha256sum --check on the
downloaded file), and ensure the step logs which approach it used for
traceability.

In @.github/workflows/release.yaml:
- Around line 32-36: The "Install yq" step currently downloads yq binary
directly without verification; update that step (the step named "Install yq") to
either use the official GitHub Action (replace the run block with uses:
mikefarah/yq@v4.34.2) or, if keeping the direct download, add a checksum
verification after download (compute and check the expected SHA256 for
/usr/local/bin/yq before chmod +x) to ensure integrity and mitigate supply-chain
risk.
- Around line 159-162: The current workflow step "Publish to crates.io" uses a
long‑lived secret via the CARGO_REGISTRY_TOKEN env variable and runs `cargo
publish --package limitador`; replace this with OIDC trusted publishing by
removing the CARGO_REGISTRY_TOKEN usage and updating the step to request
short‑lived tokens via GitHub Actions OIDC (enable id-token and use the
cargo/registry publish pattern supported by crates.io), adjust the step named
"Publish to crates.io" to request id-token: write permissions and perform `cargo
publish --package limitador` without reading CARGO_REGISTRY_TOKEN, and ensure
the workflow uses the recommended OIDC configuration per the crates.io trusted
publishing docs.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 92ad8861-8318-4ba7-8b9b-a88d49a4b3c6

📥 Commits

Reviewing files that changed from the base of the PR and between f280c20 and edd8ea5.

📒 Files selected for processing (10)
  • .github/scripts/README.md
  • .github/scripts/parse-version.sh
  • .github/scripts/validate-release-yaml.sh
  • .github/workflows/build-image.yaml
  • .github/workflows/pre-release.yaml
  • .github/workflows/release.yaml
  • .github/workflows/version-gate.yaml
  • RELEASE.md
  • limitador-server/Cargo.toml
  • release.yaml

Comment thread .github/scripts/README.md
Comment on lines +39 to +49
```
version=2.5.0
major=2
minor=5
patch=0
release-branch=release-2.5
crate-version=0.13.0
crate-major=0
crate-minor=13
crate-patch=0
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Specify the fenced-code language for the expected output block.

Line 39 opens a fence without a language identifier, which will keep markdownlint MD040 failing.

Suggested patch
-```
+```text
 version=2.5.0
 major=2
 minor=5
@@
 crate-minor=13
 crate-patch=0
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 39-39: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/README.md around lines 39 - 49, The fenced code block
showing version/major/minor/patch values is missing a language identifier
causing markdownlint MD040; update the opening fence from totext to
explicitly mark it as a text block so the linter passes and the block renders
correctly, i.e., modify the fence that precedes the lines "version=2.5.0" ...
"crate-patch=0" in the README.md snippet.


</details>

<!-- fingerprinting:phantom:poseidon:hawk -->

<!-- cr-comment:v1:7b90f9e6996194495610fec1 -->

_Source: Linters/SAST tools_

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +13 to +35
VERSION=$(yq '.limitador.version' "$RELEASE_YAML")
CRATE_VERSION=$(yq '.limitador."crate-version"' "$RELEASE_YAML")

if [[ "$BRANCH" =~ ^release- ]]; then
if [[ "$VERSION" == "0.0.0" ]]; then
echo "::error::release.yaml version is 0.0.0 on branch '$BRANCH' -- must specify a release version on release branches"
exit 1
fi

if [[ "$VERSION" == *-dev* ]]; then
echo "::error::release.yaml version '${VERSION}' is a dev version on branch '$BRANCH' -- release versions must not contain '-dev'"
exit 1
fi

if [[ "$CRATE_VERSION" == "0.0.0" ]]; then
echo "::error::release.yaml crate-version is 0.0.0 on branch '$BRANCH' -- must specify a crate version on release branches"
exit 1
fi

if [[ "$CRATE_VERSION" == *-dev* ]]; then
echo "::error::release.yaml crate-version '${CRATE_VERSION}' is a dev version on branch '$BRANCH' -- release versions must not contain '-dev'"
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce required/non-null semver checks in the gate, not only sentinel/dev checks.

Line 16-Line 35 rejects 0.0.0 and -dev, but null or malformed values can still pass this workflow and only fail later.

Suggested patch
 VERSION=$(yq '.limitador.version' "$RELEASE_YAML")
 CRATE_VERSION=$(yq '.limitador."crate-version"' "$RELEASE_YAML")
+
+if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then
+  echo "::error::No version found in $RELEASE_YAML under limitador.version"
+  exit 1
+fi
+if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
+  echo "::error::Invalid semver for version: $VERSION"
+  exit 1
+fi
+
+if [[ -z "$CRATE_VERSION" || "$CRATE_VERSION" == "null" ]]; then
+  echo "::error::No crate-version found in $RELEASE_YAML under limitador.crate-version"
+  exit 1
+fi
+if ! [[ "$CRATE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
+  echo "::error::Invalid semver for crate-version: $CRATE_VERSION"
+  exit 1
+fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
VERSION=$(yq '.limitador.version' "$RELEASE_YAML")
CRATE_VERSION=$(yq '.limitador."crate-version"' "$RELEASE_YAML")
if [[ "$BRANCH" =~ ^release- ]]; then
if [[ "$VERSION" == "0.0.0" ]]; then
echo "::error::release.yaml version is 0.0.0 on branch '$BRANCH' -- must specify a release version on release branches"
exit 1
fi
if [[ "$VERSION" == *-dev* ]]; then
echo "::error::release.yaml version '${VERSION}' is a dev version on branch '$BRANCH' -- release versions must not contain '-dev'"
exit 1
fi
if [[ "$CRATE_VERSION" == "0.0.0" ]]; then
echo "::error::release.yaml crate-version is 0.0.0 on branch '$BRANCH' -- must specify a crate version on release branches"
exit 1
fi
if [[ "$CRATE_VERSION" == *-dev* ]]; then
echo "::error::release.yaml crate-version '${CRATE_VERSION}' is a dev version on branch '$BRANCH' -- release versions must not contain '-dev'"
exit 1
fi
VERSION=$(yq '.limitador.version' "$RELEASE_YAML")
CRATE_VERSION=$(yq '.limitador."crate-version"' "$RELEASE_YAML")
if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then
echo "::error::No version found in $RELEASE_YAML under limitador.version"
exit 1
fi
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::Invalid semver for version: $VERSION"
exit 1
fi
if [[ -z "$CRATE_VERSION" || "$CRATE_VERSION" == "null" ]]; then
echo "::error::No crate-version found in $RELEASE_YAML under limitador.crate-version"
exit 1
fi
if ! [[ "$CRATE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::Invalid semver for crate-version: $CRATE_VERSION"
exit 1
fi
if [[ "$BRANCH" =~ ^release- ]]; then
if [[ "$VERSION" == "0.0.0" ]]; then
echo "::error::release.yaml version is 0.0.0 on branch '$BRANCH' -- must specify a release version on release branches"
exit 1
fi
if [[ "$VERSION" == *-dev* ]]; then
echo "::error::release.yaml version '${VERSION}' is a dev version on branch '$BRANCH' -- release versions must not contain '-dev'"
exit 1
fi
if [[ "$CRATE_VERSION" == "0.0.0" ]]; then
echo "::error::release.yaml crate-version is 0.0.0 on branch '$BRANCH' -- must specify a crate version on release branches"
exit 1
fi
if [[ "$CRATE_VERSION" == *-dev* ]]; then
echo "::error::release.yaml crate-version '${CRATE_VERSION}' is a dev version on branch '$BRANCH' -- release versions must not contain '-dev'"
exit 1
fi
🤖 Prompt for 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.

In @.github/scripts/validate-release-yaml.sh around lines 13 - 35, The current
checks in validate-release-yaml.sh only reject "0.0.0" and "-dev" but allow
null/empty or malformed versions; update the validation for VERSION and
CRATE_VERSION (the variables set from yq) to also fail when the value is
null/empty and to enforce a basic semver pattern (e.g. major.minor.patch with
optional prerelease/build parts) using a regex match; modify the existing
branch-release block that references VERSION and CRATE_VERSION so it first
checks for empty/null values and then applies the semver regex before the
existing "0.0.0" and "-dev" checks, making sure the script exits with the same
error pattern when the checks fail.

Comment on lines +38 to +41
DEPS=$(yq '.dependencies | keys | .[]' "$RELEASE_YAML" 2>/dev/null || true)
for dep in $DEPS; do
dep_version=$(yq ".dependencies.${dep}" "$RELEASE_YAML")
if [[ "$dep_version" != "0.0.0" && "$dep_version" != "null" && -n "$dep_version" ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat > /tmp/release-deps.yaml <<'EOF'
dependencies:
  some-repo: "1.2.3"
EOF

dep="some-repo"
echo "Current query:"
yq ".dependencies.${dep}" /tmp/release-deps.yaml || true
echo "Indexed query:"
DEP="$dep" yq '.dependencies[strenv(DEP)]' /tmp/release-deps.yaml

Repository: Kuadrant/limitador

Length of output: 196


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant portion of the script
sed -n '1,120p' .github/scripts/validate-release-yaml.sh

Repository: Kuadrant/limitador

Length of output: 1753


🌐 Web query:

mikefarah yq strenv(DEP) .dependencies[strenv(DEP)] syntax env var indexed key access

💡 Result:

To perform dynamic key access using an environment variable in mikefarah/yq, you must use the bracketed index syntax:.[env(VAR_NAME)] or.[strenv(VAR_NAME)] [1][2]. When you need to access a property dynamically, placing the environment variable operation inside square brackets tells yq to treat the result of that operation as a path or key [3]. Examples: 1. Dynamic lookup: To select a field where the key is stored in an environment variable named DEP: DEP="some_key" yq '.[env(DEP)]' file.yaml [1][2] 2. Updating a key: To assign a value to a dynamic key: DEP="new_key" yq -i '.[env(DEP)] = "new_value"' file.yaml [2] Important Notes: - env(VAR) vs strenv(VAR): env parses the variable value as a YAML node (which can be a map, array, etc.), while strenv(VAR) forces the variable to be treated strictly as a string [1][4]. - Nested Paths: Dynamic key lookup using env/strenv works for a single level [5]. If you need to access a nested path stored in a single environment variable (e.g., NODE="a.b"), you cannot simply use.[env(NODE)]. Instead, you must split the path and reduce it [5]: NODE="a.b" yq '(strenv(NODE) | split(".") |.[]) as $i ireduce (.;.[$i])' file.yaml [5] - Complex Paths: If your environment variable contains a full dot-notation path (e.g.,.key1.key2), use eval with strenv to force evaluation [6]: export KEY=.key1.key2; yq -n 'eval(strenv(KEY))="value"' [6]

Citations:


Fix yq dependency version lookup to use bracketed key indexing
Line 40 builds a dot-path via yq ".dependencies.${dep}", which breaks for dependency names that aren’t safe dot-notation keys (e.g., containing -). Use bracket indexing with strenv(DEP) for exact key lookup.

Suggested patch
 for dep in $DEPS; do
-  dep_version=$(yq ".dependencies.${dep}" "$RELEASE_YAML")
+  dep_version=$(DEP="$dep" yq '.dependencies[strenv(DEP)]' "$RELEASE_YAML")
   if [[ "$dep_version" != "0.0.0" && "$dep_version" != "null" && -n "$dep_version" ]]; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DEPS=$(yq '.dependencies | keys | .[]' "$RELEASE_YAML" 2>/dev/null || true)
for dep in $DEPS; do
dep_version=$(yq ".dependencies.${dep}" "$RELEASE_YAML")
if [[ "$dep_version" != "0.0.0" && "$dep_version" != "null" && -n "$dep_version" ]]; then
DEPS=$(yq '.dependencies | keys | .[]' "$RELEASE_YAML" 2>/dev/null || true)
for dep in $DEPS; do
dep_version=$(DEP="$dep" yq '.dependencies[strenv(DEP)]' "$RELEASE_YAML")
if [[ "$dep_version" != "0.0.0" && "$dep_version" != "null" && -n "$dep_version" ]]; then
🤖 Prompt for 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.

In @.github/scripts/validate-release-yaml.sh around lines 38 - 41, The current
lookup builds a dot-path via yq ".dependencies.${dep}" which fails for keys with
characters like '-' — update the dep_version lookup to use bracketed key
indexing with yq and strenv: export the loop variable into an environment
variable (e.g., DEP="$dep") and call yq with the expression
".dependencies[strenv(DEP)]" (using the same RELEASE_YAML variable) so keys are
matched exactly; adjust the dep_version assignment in the for-loop that defines
DEPS/dep to export DEP and use the bracketed strenv lookup instead of the
dot-path.

Comment on lines +11 to +14
tag:
description: "Image tag to apply"
required: true
type: string

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Tag input should be optional.

The tag input is marked as required: true, but the tag generation logic in lines 113-119 treats it as optional by checking inputs.tag != ''. When this workflow is triggered via workflow_dispatch or push events, inputs.tag will be undefined, causing the workflow to fail validation.

🐛 Proposed fix
      tag:
        description: "Image tag to apply"
-       required: true
+       required: false
        type: string
🤖 Prompt for 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.

In @.github/workflows/build-image.yaml around lines 11 - 14, The workflow input
"tag" is incorrectly marked required: true while downstream logic expects an
optional value; update the "tag" input definition (the input named tag) to not
be required (set required: false) and add a default: '' (empty string) so
inputs.tag is defined when omitted, ensuring the tag-generation logic that
checks inputs.tag != '' works without validation errors.

validate-release-yaml:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/version-gate.yaml"
if [ ! -f "$file" ]; then
  echo "Missing file: $file" >&2
  exit 1
fi

# Show the full workflow with line numbers (keep output small)
nl -ba "$file" | sed -n '1,160p'

Repository: Kuadrant/limitador

Length of output: 106


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/version-gate.yaml"
nl -ba "$file" | sed -n '1,220p'

Repository: Kuadrant/limitador

Length of output: 105


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file=".github/workflows/version-gate.yaml"
nl -ba "$file" | sed -n '1,220p'

Repository: Kuadrant/limitador

Length of output: 105


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/version-gate.yaml"

# Confirm file exists and show around the referenced line using cat -n
echo "== head =="
head -n 60 "$file" | cat -n

echo "== around line 18 =="
sed -n '1,120p' "$file" | cat -n | sed -n '10,40p'

echo "== actions/checkout occurrences =="
rg -n "uses:\s*actions/checkout@" "$file" || true

echo "== persist-credentials occurrences =="
rg -n "persist-credentials" "$file" || true

Repository: Kuadrant/limitador

Length of output: 2117


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/version-gate.yaml"

echo "== uses: in workflow =="
rg -n "uses:\s*" "$file"

echo "== validate script header =="
sed -n '1,200p' .github/scripts/validate-release-yaml.sh | cat -n

echo "== validate script checkout/git usage =="
rg -n "git |checkout|token|GH_TOKEN|GITHUB_TOKEN|persist-credentials" .github/scripts/validate-release-yaml.sh || true

Repository: Kuadrant/limitador

Length of output: 2227


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/version-gate.yaml"

echo "== uses: in workflow =="
rg -n "uses:\s*" "$file" || true

echo "== validate script (first 200 lines) =="
sed -n '1,200p' .github/scripts/validate-release-yaml.sh | cat -n

echo "== validate script token/git usage =="
rg -n "GH_TOKEN|GITHUB_TOKEN|git |checkout|persist-credentials" .github/scripts/validate-release-yaml.sh || true

Repository: Kuadrant/limitador

Length of output: 2235


Pin actions/checkout to a full commit SHA (and disable credential persistence).

Line 18 uses a floating ref (actions/checkout@v6), which weakens supply-chain guarantees; pin to the v6 commit SHA. Also set persist-credentials: false since this job doesn’t require git credentials and only uses GH_TOKEN for gh API calls.

Suggested patch
-      - uses: actions/checkout@v6
+      - uses: actions/checkout@<full_commit_sha_for_v6>
+        with:
+          persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 18-18: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 18-18: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for 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.

In @.github/workflows/version-gate.yaml at line 18, Replace the floating ref for
actions/checkout (currently "actions/checkout@v6") with the specific v6 commit
SHA and add the checkout input persist-credentials: false; specifically update
the checkout step that references actions/checkout@v6 to use the exact commit
SHA for v6 and include persist-credentials: false under that step so the job
does not retain Git credentials and the action is pinned to a fixed commit.

Source: Linters/SAST tools

@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: 7

🧹 Nitpick comments (1)
.github/workflows/pre-release.yaml (1)

149-151: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Set persist-credentials: false on checkout-only jobs.

The open-pr job (and the post-release checkout at lines 192-195) authenticate via GH_TOKEN for gh and never push with git, so persisting git credentials in the checkout is unnecessary. Disabling it addresses the zizmor artipacked finding without affecting the push-based jobs (setup, prepare-release).

🔒 Proposed change
       - uses: actions/checkout@v6
+        with:
+          persist-credentials: false
🤖 Prompt for 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.

In @.github/workflows/pre-release.yaml around lines 149 - 151, The checkout step
in the open-pr workflow job and the later post-release checkout should not
persist git credentials because those jobs only use GH_TOKEN with gh and never
push via git. Update the actions/checkout usage in the checkout-only paths to
set persist-credentials to false, while leaving the push-based jobs such as
setup and prepare-release unchanged; use the checkout step in the pre-release
workflow as the target reference.

Source: Linters/SAST tools

🤖 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/release.yaml:
- Around line 20-30: The release workflow is re-checking out a mutable branch in
multiple jobs, so the release steps can drift across different commits. In the
read-version job, capture the checked-out commit SHA after actions/checkout and
expose it as an output, then update the downstream checkout steps in the
affected jobs to use that fixed SHA instead of inputs.release-branch. Keep the
version parsing, crate publish, image build, and release note generation aligned
by referencing the captured SHA from read-version and using it in the relevant
checkout/ref fields.
- Around line 135-143: The release workflow tag/publish step is not resumable
because it blindly fails on existing tags and duplicate crate versions. Update
the logic around the tag loop and crate publish/release steps to treat
already-existing artefacts as success only if they match the intended
commit/version, and to fail only when an existing tag/version points to a
different commit or release state. Use the existing release shell block in the
workflow and the tag names/version variables to locate the checks, and apply the
same resumable handling to the related steps referenced by the duplicate
comment.
- Around line 12-13: Workflow-wide contents: write is too broad; scope write
access only to the publish/tagging jobs in the release workflow. Update the
checkout steps in the read/test/publish jobs so they do not persist credentials
by default, and only enable repository write credentials where the job actually
needs to push tags or releases. Use the release workflow job names and their
checkout steps to apply the hardening consistently.
- Around line 44-56: The release workflow validation in the version-check step
only rejects the 0.0.0 sentinel, so `-dev` prerelease values can still reach
tagging and publishing. Update the validation in the version validation block
that reads `steps.parse.outputs.version` and `steps.parse.outputs.crate-version`
to also reject any `-dev` suffix, or reuse the existing release validator logic
here so both `VERSION` and `CRATE_VERSION` are blocked before any release side
effects.
- Line 28: The release workflow still references external actions by tag instead
of immutable commit SHAs, which is unsafe for a job with publish secrets and
write permissions. Update each external action usage in release.yaml, including
actions/checkout, actions-rust-lang/setup-rust-toolchain, Swatinem/rust-cache,
abelfodil/protoc-action, and supercharge/redis-github-action, to pin them to
their full commit SHA refs so the release job uses fixed, reviewable versions.
- Around line 164-170: The build-images job is currently inheriting all secrets
through the reusable workflow call, which is too broad; update the release
workflow to pass only the registry credentials needed by the reusable
build-image.yaml workflow. Replace the broad secret inheritance in build-images
with explicit secret mapping for IMG_REGISTRY_USERNAME and IMG_REGISTRY_TOKEN so
the reusable workflow only receives those two values.
- Around line 81-117: Add a manifest/version consistency check to the release
workflow so it cannot proceed when release metadata and crate versions diverge.
Update the smoke-tests job in release.yaml to compare the version read from
read-version with the versions in limitador/Cargo.toml and the server manifest
before the Publish dry-run step. Make the check fail fast if any manifest
disagrees, using the existing release metadata and cargo-published crate as the
comparison points.

---

Nitpick comments:
In @.github/workflows/pre-release.yaml:
- Around line 149-151: The checkout step in the open-pr workflow job and the
later post-release checkout should not persist git credentials because those
jobs only use GH_TOKEN with gh and never push via git. Update the
actions/checkout usage in the checkout-only paths to set persist-credentials to
false, while leaving the push-based jobs such as setup and prepare-release
unchanged; use the checkout step in the pre-release workflow as the target
reference.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 03785948-6dab-4da6-b75b-c5d1452ab1ab

📥 Commits

Reviewing files that changed from the base of the PR and between edd8ea5 and 38fcfc8.

📒 Files selected for processing (11)
  • .github/scripts/README.md
  • .github/scripts/parse-version.sh
  • .github/scripts/validate-release-yaml.sh
  • .github/workflows/build-image.yaml
  • .github/workflows/pre-release.yaml
  • .github/workflows/release.yaml
  • .github/workflows/version-gate.yaml
  • README.md
  • RELEASE.md
  • limitador-server/Cargo.toml
  • release.yaml
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • limitador-server/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (2)
  • release.yaml
  • .github/scripts/validate-release-yaml.sh

Comment on lines +12 to +13
permissions:
contents: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Scope write credentials to the jobs that need them.

contents: write is currently granted workflow-wide, and checkout persists credentials by default. That gives read/test/publish steps unnecessary repository write credentials while they run branch-controlled code.

Suggested hardening
 permissions:
-  contents: write
+  contents: read
@@
   tag:
     needs: [read-version, smoke-tests]
     runs-on: ubuntu-latest
+    permissions:
+      contents: write
@@
   create-release:
     needs: [read-version, publish-crate, build-images]
     runs-on: ubuntu-latest
+    permissions:
+      contents: write

For checkout steps that do not push tags, also add:

       - uses: actions/checkout@v6
         with:
           ref: ${{ needs.read-version.outputs.commit-sha }}
+          persist-credentials: false

Also applies to: 28-30, 85-87, 149-151, 176-178

🧰 Tools
🪛 zizmor (1.26.1)

[error] 13-13: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level

(excessive-permissions)

🤖 Prompt for 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.

In @.github/workflows/release.yaml around lines 12 - 13, Workflow-wide contents:
write is too broad; scope write access only to the publish/tagging jobs in the
release workflow. Update the checkout steps in the read/test/publish jobs so
they do not persist credentials by default, and only enable repository write
credentials where the job actually needs to push tags or releases. Use the
release workflow job names and their checkout steps to apply the hardening
consistently.

Source: Linters/SAST tools

Comment on lines +20 to +30
read-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.parse.outputs.version }}
crate-version: ${{ steps.parse.outputs.crate-version }}
major: ${{ steps.parse.outputs.major }}
minor: ${{ steps.parse.outputs.minor }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.release-branch }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Pin the release run to one commit SHA.

Each job re-checks out the mutable release branch. If the branch advances after read-version, the parsed versions, tags, crate publish, image build, and release notes can refer to different commits. Capture the checked-out SHA once and use it downstream.

Suggested fix
     outputs:
       version: ${{ steps.parse.outputs.version }}
       crate-version: ${{ steps.parse.outputs.crate-version }}
       major: ${{ steps.parse.outputs.major }}
       minor: ${{ steps.parse.outputs.minor }}
+      commit-sha: ${{ steps.release-rev.outputs.commit-sha }}
@@
       - uses: actions/checkout@v6
         with:
           ref: ${{ inputs.release-branch }}
+
+      - name: Capture release commit
+        id: release-rev
+        run: echo "commit-sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
@@
-          ref: ${{ inputs.release-branch }}
+          ref: ${{ needs.read-version.outputs.commit-sha }}
@@
-      ref: ${{ inputs.release-branch }}
+      ref: ${{ needs.read-version.outputs.commit-sha }}

Also applies to: 85-87, 122-125, 149-151, 168-169, 176-178

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 28-30: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for 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.

In @.github/workflows/release.yaml around lines 20 - 30, The release workflow is
re-checking out a mutable branch in multiple jobs, so the release steps can
drift across different commits. In the read-version job, capture the checked-out
commit SHA after actions/checkout and expose it as an output, then update the
downstream checkout steps in the affected jobs to use that fixed SHA instead of
inputs.release-branch. Keep the version parsing, crate publish, image build, and
release note generation aligned by referencing the captured SHA from
read-version and using it in the relevant checkout/ref fields.

major: ${{ steps.parse.outputs.major }}
minor: ${{ steps.parse.outputs.minor }}
steps:
- uses: actions/checkout@v6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Lists external action refs in this workflow. Expect each to be pinned to a full 40-character SHA.
rg -n 'uses:\s+[^./][^@\s]+@[^#\s]+' .github/workflows/release.yaml

Repository: Kuadrant/limitador

Length of output: 647


Pin the workflow’s external actions to full commit SHAs. .github/workflows/release.yaml still uses tag-based refs for every external action (actions/checkout, actions-rust-lang/setup-rust-toolchain, Swatinem/rust-cache, abelfodil/protoc-action, supercharge/redis-github-action). This release job has publishing secrets and write permissions, so immutable SHAs are required.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 28-30: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for 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.

In @.github/workflows/release.yaml at line 28, The release workflow still
references external actions by tag instead of immutable commit SHAs, which is
unsafe for a job with publish secrets and write permissions. Update each
external action usage in release.yaml, including actions/checkout,
actions-rust-lang/setup-rust-toolchain, Swatinem/rust-cache,
abelfodil/protoc-action, and supercharge/redis-github-action, to pin them to
their full commit SHA refs so the release job uses fixed, reviewable versions.

Source: Linters/SAST tools

Comment on lines +44 to +56
- name: Validate versions are not sentinel
env:
VERSION: ${{ steps.parse.outputs.version }}
CRATE_VERSION: ${{ steps.parse.outputs.crate-version }}
run: |
if [[ "$VERSION" == "0.0.0" ]]; then
echo "::error::Version is 0.0.0 -- has the pre-release PR been merged?"
exit 1
fi
if [[ "$CRATE_VERSION" == "0.0.0" ]]; then
echo "::error::Crate version is 0.0.0 -- has the pre-release PR been merged?"
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Block -dev versions before release side effects.

parse-version.sh accepts prerelease suffixes, and the branch check still passes for values like 2.5.0-dev. This workflow can therefore tag and publish a dev version unless it reuses the release validator or rejects -dev here.

Suggested fix
-      - name: Validate versions are not sentinel
+      - name: Validate versions are releasable
@@
-          if [[ "$VERSION" == "0.0.0" ]]; then
-            echo "::error::Version is 0.0.0 -- has the pre-release PR been merged?"
+          if [[ "$VERSION" == "0.0.0" || "$VERSION" == *-dev* ]]; then
+            echo "::error::Version '${VERSION}' is not releasable -- has the pre-release PR been merged?"
             exit 1
           fi
-          if [[ "$CRATE_VERSION" == "0.0.0" ]]; then
-            echo "::error::Crate version is 0.0.0 -- has the pre-release PR been merged?"
+          if [[ "$CRATE_VERSION" == "0.0.0" || "$CRATE_VERSION" == *-dev* ]]; then
+            echo "::error::Crate version '${CRATE_VERSION}' is not releasable -- has the pre-release PR been merged?"
             exit 1
           fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Validate versions are not sentinel
env:
VERSION: ${{ steps.parse.outputs.version }}
CRATE_VERSION: ${{ steps.parse.outputs.crate-version }}
run: |
if [[ "$VERSION" == "0.0.0" ]]; then
echo "::error::Version is 0.0.0 -- has the pre-release PR been merged?"
exit 1
fi
if [[ "$CRATE_VERSION" == "0.0.0" ]]; then
echo "::error::Crate version is 0.0.0 -- has the pre-release PR been merged?"
exit 1
fi
- name: Validate versions are releasable
env:
VERSION: ${{ steps.parse.outputs.version }}
CRATE_VERSION: ${{ steps.parse.outputs.crate-version }}
run: |
if [[ "$VERSION" == "0.0.0" || "$VERSION" == *-dev* ]]; then
echo "::error::Version '${VERSION}' is not releasable -- has the pre-release PR been merged?"
exit 1
fi
if [[ "$CRATE_VERSION" == "0.0.0" || "$CRATE_VERSION" == *-dev* ]]; then
echo "::error::Crate version '${CRATE_VERSION}' is not releasable -- has the pre-release PR been merged?"
exit 1
fi
🤖 Prompt for 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.

In @.github/workflows/release.yaml around lines 44 - 56, The release workflow
validation in the version-check step only rejects the 0.0.0 sentinel, so `-dev`
prerelease values can still reach tagging and publishing. Update the validation
in the version validation block that reads `steps.parse.outputs.version` and
`steps.parse.outputs.crate-version` to also reject any `-dev` suffix, or reuse
the existing release validator logic here so both `VERSION` and `CRATE_VERSION`
are blocked before any release side effects.

Comment on lines +81 to +117
smoke-tests:
needs: read-version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.release-branch }}

- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: rustfmt, clippy

- uses: Swatinem/rust-cache@v2

- uses: abelfodil/protoc-action@v1
with:
protoc-version: "3.19.4"

- uses: supercharge/redis-github-action@1.8.1
with:
redis-version: 7

- name: Format check
run: cargo fmt --all -- --check

- name: Clippy
run: cargo clippy --locked --all-features --all-targets -- -D warnings

- name: Check
run: cargo check --locked --all-features

- name: Test
run: cargo test --locked --all-features

- name: Publish dry-run
run: cargo publish --dry-run --package limitador

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Expect the limitador Cargo.toml package version to match limitador.crate-version,
# and the server Cargo.toml version to match limitador.version.
sed -n '1,120p' release.yaml
rg -n '^(name|version)\s*=' Cargo.toml limitador-server/Cargo.toml

Repository: Kuadrant/limitador

Length of output: 337


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Repository files ==\n'
git ls-files | sed -n '1,200p'

printf '\n== release.yaml ==\n'
sed -n '1,120p' release.yaml

printf '\n== workflow relevant sections ==\n'
sed -n '1,240p' .github/workflows/release.yaml

printf '\n== Cargo manifests ==\n'
for f in Cargo.toml limitador/Cargo.toml limitador-server/Cargo.toml; do
  if [[ -f "$f" ]]; then
    echo "--- $f ---"
    sed -n '1,120p' "$f"
  fi
done

Repository: Kuadrant/limitador

Length of output: 23402


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Repository files ==\n'
git ls-files | sed -n '1,200p'

printf '\n== release.yaml ==\n'
sed -n '1,120p' release.yaml

printf '\n== workflow relevant sections ==\n'
sed -n '1,240p' .github/workflows/release.yaml

printf '\n== Cargo manifests ==\n'
for f in Cargo.toml limitador/Cargo.toml limitador-server/Cargo.toml; do
  if [[ -f "$f" ]]; then
    echo "--- $f ---"
    sed -n '1,120p' "$f"
  fi
done

Repository: Kuadrant/limitador

Length of output: 23402


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== validate-release-yaml.sh ==\n'
sed -n '1,220p' .github/scripts/validate-release-yaml.sh

printf '\n== parse-version.sh ==\n'
sed -n '1,220p' .github/scripts/parse-version.sh

printf '\n== version-gate workflow ==\n'
sed -n '1,220p' .github/workflows/version-gate.yaml

printf '\n== release notes / docs references to release.yaml ==\n'
rg -n "crate-version|release.yaml|cargo publish|publish --dry-run|manifest version|Cargo.toml" .github README.md RELEASE.md limitador-server/README.md -A2 -B2

Repository: Kuadrant/limitador

Length of output: 29551


Add a manifest/version consistency check. The release workflow reads the release metadata from release.yaml, but cargo publish uses the version in limitador/Cargo.toml. A pre-publish check should compare release.yaml against the crate and server manifests so the workflow cannot tag/release one version and publish another.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 85-87: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 85-85: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 89-89: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 93-93: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 95-95: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 99-99: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for 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.

In @.github/workflows/release.yaml around lines 81 - 117, Add a manifest/version
consistency check to the release workflow so it cannot proceed when release
metadata and crate versions diverge. Update the smoke-tests job in release.yaml
to compare the version read from read-version with the versions in
limitador/Cargo.toml and the server manifest before the Publish dry-run step.
Make the check fail fast if any manifest disagrees, using the existing release
metadata and cargo-published crate as the comparison points.

Comment on lines +135 to +143
for TAG in "v${VERSION}" "server-v${VERSION}" "crate-v${CRATE_VERSION}"; do
if git tag --list "$TAG" | grep --quiet .; then
echo "::error::Tag $TAG already exists"
exit 1
fi
git tag --annotate "$TAG" --message "Release $TAG"
done

git push origin "v${VERSION}" "server-v${VERSION}" "crate-v${CRATE_VERSION}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make release side effects resumable.

A full rerun after tags or the crate publish have already succeeded will fail on existing tags or duplicate crate versions. Treat existing artefacts as success only when they match the intended commit/version, and fail only on mismatches.

Also applies to: 159-162

🤖 Prompt for 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.

In @.github/workflows/release.yaml around lines 135 - 143, The release workflow
tag/publish step is not resumable because it blindly fails on existing tags and
duplicate crate versions. Update the logic around the tag loop and crate
publish/release steps to treat already-existing artefacts as success only if
they match the intended commit/version, and to fail only when an existing
tag/version points to a different commit or release state. Use the existing
release shell block in the workflow and the tag names/version variables to
locate the checks, and apply the same resumable handling to the related steps
referenced by the duplicate comment.

Comment on lines +164 to +170
build-images:
needs: [read-version, tag]
uses: ./.github/workflows/build-image.yaml
with:
ref: ${{ inputs.release-branch }}
tag: v${{ needs.read-version.outputs.version }}
secrets: inherit

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the reusable workflow's actual secret usage, then replace `secrets: inherit`
# with an explicit secrets map containing only those names.
rg -n 'workflow_call:|secrets:|secrets\.' .github/workflows/build-image.yaml

Repository: Kuadrant/limitador

Length of output: 411


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- .github/workflows/build-image.yaml (top/middle) ---'
sed -n '1,180p' .github/workflows/build-image.yaml

echo
echo '--- .github/workflows/release.yaml (build-images job) ---'
sed -n '150,185p' .github/workflows/release.yaml

Repository: Kuadrant/limitador

Length of output: 6344


Pass only the registry credentials to the reusable workflow. secrets: inherit exposes every repo/release secret here; pass IMG_REGISTRY_USERNAME and IMG_REGISTRY_TOKEN explicitly instead.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 166-166: secrets unconditionally inherited by called workflow (secrets-inherit): this reusable workflow

(secrets-inherit)

🤖 Prompt for 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.

In @.github/workflows/release.yaml around lines 164 - 170, The build-images job
is currently inheriting all secrets through the reusable workflow call, which is
too broad; update the release workflow to pass only the registry credentials
needed by the reusable build-image.yaml workflow. Replace the broad secret
inheritance in build-images with explicit secret mapping for
IMG_REGISTRY_USERNAME and IMG_REGISTRY_TOKEN so the reusable workflow only
receives those two values.

Source: Linters/SAST tools

Automates the release of limitador, including the manual steps from
before. Also aligns the release process with the up-and-coming RFC.

Signed-off-by: Jim Fitzpatrick <jfitzpat@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready For Review

Development

Successfully merging this pull request may close these issues.

1 participant