Add two-phase release workflow#634
Conversation
📝 WalkthroughWalkthroughAdds a two-phase release automation system comprising a ChangesTwo-phase release automation
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer
participant PreRelease as pre-release workflow
participant VersionGate as Version Gate workflow
participant Release as release workflow
participant BuildImages as build-images workflow
participant GitHub as GitHub Remote
Dev->>PreRelease: manual dispatch (version, source-branch)
PreRelease->>GitHub: create release-MAJOR.MINOR branch
PreRelease->>GitHub: push pre-release-v branch (updated release.yaml + generated manifests)
PreRelease->>GitHub: gh pr create → release branch
GitHub->>VersionGate: PR event targeting release-** (release.yaml changed)
VersionGate->>VersionGate: validate-release-yaml.sh (version rules + dependency releases)
Dev->>Release: manual dispatch (release-branch) after PR merge
Release->>Release: parse-version.sh, smoke tests (lint/unit/CEL)
Release->>GitHub: git tag v{version}
Release->>BuildImages: workflow_call (version, ref=tag)
BuildImages->>GitHub: build and push multi-arch Docker image
Release->>GitHub: gh release create v{version}
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Signed-off-by: Jim Fitzpatrick <jfitzpat@redhat.com>
5ffa6f0 to
f17c4d8
Compare
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 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/validate-release-yaml.sh:
- Around line 20-24: The yq query on the line that assigns dep_version uses dot
notation with `.dependencies.${dep}`, which fails when dependency names contain
hyphens because yq interprets hyphens as subtraction operators rather than
literal characters. Replace the dot notation syntax with bracket notation by
changing `.dependencies.${dep}` to `.dependencies["${dep}"]` to properly handle
dependency names with special characters like hyphens.
In @.github/workflows/build-images.yaml:
- Around line 39-42: The conditional block starting at line 39 directly
interpolates the workflow input `${{ inputs.version }}` into shell code, which
creates a shell injection vulnerability if a caller passes values containing
shell metacharacters like `$(command)`. Instead of directly embedding the input
in the bash conditional and echo commands, set the input value as an environment
variable first, then reference that variable in the conditional check and the
subsequent echo statements that write to GITHUB_OUTPUT. This prevents the shell
from interpreting any special characters in the input value.
In @.github/workflows/pre-release.yaml:
- Line 27: The workflow file uses mutable version tags (such as `@v4` and `@v5`) for
GitHub Actions references on lines 27, 62, 82, and 108, which creates security
risks since these tags can be retargeted upstream. Replace each mutable version
tag with a specific immutable commit SHA. For the actions/checkout action on
line 27 and any other action references throughout the file, determine the
correct commit SHA for each action version (for example, `@v4` should be replaced
with the full commit SHA like `@e2f20f853a2a19803ed141a6f266e17681910f6d`) and
update all four occurrences to use the pinned commit hash instead of the version
tag.
In @.github/workflows/release.yaml:
- Around line 11-13: The `contents: write` permission is currently set at the
workflow level, granting write access to all jobs. Move this permission to only
the specific job(s) that create tags or releases by removing `contents: write`
from the workflow-level permissions block and adding it as a job-level
permission to only the job(s) that perform release operations. This ensures
other jobs in the workflow operate with minimal necessary privileges.
- Around line 91-94: The tag existence check in the release workflow is not
idempotent - it fails unconditionally whenever the tag already exists, blocking
recovery from partial failures. Modify the git tag validation logic to check if
the tag exists at the current HEAD commit rather than just checking if the tag
exists. If the tag already points to HEAD, allow the workflow to proceed as
success. Only exit with an error if the tag exists but points to a different
commit, indicating a genuine conflict.
- Around line 22-24: Replace all mutable GitHub Actions version tags in the
release.yaml workflow with pinned commit SHAs. Specifically, replace the `@v4`
tag in the actions/checkout action on line 22, and locate the other GitHub
Actions usages at lines 60, 65, 82, and 112, then replace their mutable version
tags (such as `@v4` or `@v5`) with their full commit SHAs. You can find the correct
commit SHAs by looking up each action's releases on GitHub or using the GitHub
CLI. This ensures that the workflow uses immutable action references and
prevents silent upstream changes that could affect supply-chain security.
- Around line 38-43: The step "Validate branch matches version" directly
interpolates the workflow input `inputs.release-branch` into the bash script,
creating a shell injection vulnerability. Move `inputs.release-branch` to an
environment variable by adding an `env` block to the step that sets a variable
(e.g., `RELEASE_BRANCH: ${{ inputs.release-branch }}`), then update the bash
script to reference this environment variable using native shell syntax (e.g.,
`$RELEASE_BRANCH`) instead of the direct `${{ inputs.release-branch }}`
interpolation on lines 41-42.
In @.github/workflows/version-gate.yaml:
- Around line 28-30: The validate-release-yaml.sh script is currently receiving
the source branch via github.head_ref, but it should receive the target branch
via github.base_ref to properly validate against the correct release branch.
Additionally, to prevent shell-template injection attacks through crafted branch
names, pass the branch reference as an environment variable rather than directly
interpolating it in the shell command. Modify the run block to export
github.base_ref as an environment variable and pass that variable to the
validate-release-yaml.sh script instead of the direct interpolation.
🪄 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: 9c0cdb3d-3870-4fdc-ae44-d790d6b496b1
📒 Files selected for processing (8)
.github/scripts/parse-version.sh.github/scripts/validate-release-yaml.sh.github/workflows/build-images.yaml.github/workflows/pre-release.yaml.github/workflows/release.yaml.github/workflows/version-gate.yamlRELEASE.mdrelease.yaml
| 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 | ||
| if ! gh release view "v${dep_version}" --repo "${ORG}/${dep}" &>/dev/null; then |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
# First, locate and read the validate-release-yaml.sh file
git ls-files | grep -i "validate-release-yaml"Repository: Kuadrant/authorino
Length of output: 197
🏁 Script executed:
# Read the file to see the current state
cat -n .github/scripts/validate-release-yaml.sh | head -40Repository: Kuadrant/authorino
Length of output: 1386
🏁 Script executed:
# Test yq behaviour with keys containing hyphens
# Create a test YAML to verify the concern
cat > /tmp/test-release.yaml <<'EOF'
dependencies:
my-service: "1.0.0"
another-component: "2.0.0"
simple: "3.0.0"
EOF
# Test current approach (without quotes)
echo "=== Testing current approach (unquoted key) ==="
yq '.dependencies | keys | .[]' /tmp/test-release.yaml
echo ""
echo "=== Testing current approach with unquoted variable ==="
dep="my-service"
yq ".dependencies.${dep}" /tmp/test-release.yaml 2>&1 || echo "FAILED"
echo ""
echo "=== Testing proposed fix with bracket notation ==="
yq -r ".dependencies[\"${dep}\"]" /tmp/test-release.yamlRepository: Kuadrant/authorino
Length of output: 435
🏁 Script executed:
# Check if there are any RELEASE_YAML files in the repo and their content
find . -name "*release*.yaml" -o -name "*release*.yml" | head -10Repository: Kuadrant/authorino
Length of output: 241
🌐 Web query:
yq key quoting hyphenated keys bracket notation syntax
💡 Result:
To access keys containing hyphens in yq, you must use bracket notation with quoted strings [1][2]. Because hyphenated keys can be misinterpreted by the parser as subtraction operations, wrapping the key name in quotes (either double or single) is necessary to ensure it is treated as a literal string [1][2]. The standard syntax for accessing such a key is: yq '.["your-hyphenated-key"]' file.yaml Alternatively, you can use the syntax: yq '."your-hyphenated-key"' file.yaml It is critical to use shell-level quoting (the outer single quotes) to prevent your shell from interpreting the brackets or quotes before they are passed to yq [1]. Common pitfalls: 1. Shell Interpretation: If you do not wrap the entire expression in single quotes (e.g., yq.["key-name"]), your shell may attempt to interpret the brackets or quotes, leading to errors. 2. Operator Confusion: If you omit quotes around the key name, yq may treat the hyphen as a subtraction operator rather than a character in the key name [1]. 3. Leading Dashes: If the key name itself starts with a hyphen, you may also need to use the flag terminator (--) to stop the command from misinterpreting the key as a command-line flag [3].
Citations:
- 1: How do I query for fields with dashes in them? kislyuk/yq#32
- 2: https://blog.hashhackers.com/blog/yq-guide/
- 3: https://mikefarah.gitbook.io/yq/v3.x/usage/path-expressions.md
🌐 Web query:
yq .dependencies.key-name vs .dependencies["key-name"] parsing difference
💡 Result:
In yq, the primary difference between dot notation (e.g.,.dependencies.key-name) and bracket notation (e.g.,.dependencies["key-name"]) is how they handle key names containing special characters, such as dots, hyphens, or spaces [1][2][3]. Dot notation is designed for simple, alphanumeric key names [4]. When a key contains special characters like a period, hyphen, or space, yq may misinterpret these characters as part of the expression's syntax—for example, treating a dot as a nested object separator rather than part of the key name [2][5][3]. Bracket notation with quotes (e.g.,.dependencies["key-name"]) acts as a robust mechanism to treat the contents within the brackets as a literal string [1][3]. This allows you to access keys that would otherwise be syntactically ambiguous or invalid in dot notation [2][6]. Key takeaways: 1. Dot notation: Suitable for standard, simple key names without special characters [4]. It cannot correctly reference keys containing dots (e.g.,.a.b.c interprets b.c as a nested structure) or other reserved symbols [2][5]. 2. Bracket notation: Required for keys containing dots, spaces, or leading dashes [1][2][3][7]. It is generally safer to use bracket notation whenever you are uncertain if a key name might conflict with yq expression syntax [6]. 3. Shell considerations: When using bracket notation in a terminal, it is important to wrap the entire expression in single quotes (e.g., '.dependencies["key-name"]') to prevent the shell from interpreting the double quotes or brackets prematurely [2][5][8]. For example, if you have a key named b.c, you must use.["b.c"] or."b.c" to access it, as.b.c would attempt to look for a key named b containing a sub-key named c [2][5].
Citations:
- 1: https://mikefarah.gitbook.io/yq/operators/traverse-read
- 2: https://mikefarah.gitbook.io/yq/v3.x/usage/path-expressions
- 3: Looking for a way to escape period for yaml params that have dots in them mikefarah/yq#2247
- 4: https://mikefarah.gitbook.io/yq/how-it-works
- 5: https://stackoverflow.com/questions/64983036/how-to-read-values-with-yq-when-the-key-has-a-dot
- 6: https://blog.hashhackers.com/blog/yq-guide/
- 7: https://mikefarah.gitbook.io/yq/v3.x/usage/path-expressions.md
- 8: How do I query for fields with dashes in them? kislyuk/yq#32
🏁 Script executed:
# Let me check the release.yaml file in the repo to see what dependencies actually exist
cat release.yaml 2>/dev/null | head -50Repository: Kuadrant/authorino
Length of output: 186
🏁 Script executed:
# Also check if there are any dependencies with hyphens in their names
# by examining any release YAML files
if [ -f release.yaml ]; then
echo "=== Content of release.yaml ==="
cat release.yaml
fiRepository: Kuadrant/authorino
Length of output: 218
Use bracket notation with quoted keys when querying yq for dynamic dependency names.
Line 22 currently builds .dependencies.${dep} using dot notation, which fails when dependency names contain hyphens. The yq parser misinterprets hyphens as subtraction operators rather than literal characters. This causes dependency version checks to fail silently or be skipped entirely for dependencies like my-service.
🔧 Proposed fix
-DEPS=$(yq '.dependencies | keys | .[]' "$RELEASE_YAML" 2>/dev/null || true)
-for dep in $DEPS; do
- dep_version=$(yq ".dependencies.${dep}" "$RELEASE_YAML")
+mapfile -t deps < <(yq -r '.dependencies // {} | keys[]' "$RELEASE_YAML")
+for dep in "${deps[@]}"; do
+ dep_version=$(yq -r ".dependencies[\"${dep}\"]" "$RELEASE_YAML")🤖 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 20 - 24, The yq query
on the line that assigns dep_version uses dot notation with
`.dependencies.${dep}`, which fails when dependency names contain hyphens
because yq interprets hyphens as subtraction operators rather than literal
characters. Replace the dot notation syntax with bracket notation by changing
`.dependencies.${dep}` to `.dependencies["${dep}"]` to properly handle
dependency names with special characters like hyphens.
| if [[ -n "${{ inputs.version }}" ]]; then | ||
| echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT | ||
| echo "version_tag=v${{ inputs.version }}" >> $GITHUB_OUTPUT | ||
| elif [[ ${GITHUB_REF_NAME/\//-} =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]]; then |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/build-images.yaml | sed -n '1,50p'Repository: Kuadrant/authorino
Length of output: 2070
🏁 Script executed:
cat -n .github/workflows/build-images.yaml | sed -n '9,18p;75,76p;130,132p'Repository: Kuadrant/authorino
Length of output: 837
🏁 Script executed:
# Check if there are any uses of inputs.version elsewhere in the workflow
rg 'inputs\.version' .github/workflows/build-images.yamlRepository: Kuadrant/authorino
Length of output: 347
Harden workflow_call input handling before using it in bash.
Lines 39–41 interpolate ${{ inputs.version }} directly into shell code. If a caller passes a crafted value containing shell metacharacters (e.g. $(command)), the shell will expand it and execute arbitrary code, poisoning the workflow outputs. Pass the input via an environment variable instead to prevent expansion.
Suggested fix
- name: Set Authorino build info
id: build-info
+ env:
+ INPUT_VERSION: ${{ inputs.version }}
run: |
- if [[ -n "${{ inputs.version }}" ]]; then
- echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT
- echo "version_tag=v${{ inputs.version }}" >> $GITHUB_OUTPUT
+ if [[ -n "$INPUT_VERSION" ]]; then
+ if ! [[ "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
+ echo "::error::Invalid version input: $INPUT_VERSION"
+ exit 1
+ fi
+ printf 'version=%s\n' "$INPUT_VERSION" >> "$GITHUB_OUTPUT"
+ printf 'version_tag=v%s\n' "$INPUT_VERSION" >> "$GITHUB_OUTPUT"
elif [[ ${GITHUB_REF_NAME/\/,-} =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]]; 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.
| if [[ -n "${{ inputs.version }}" ]]; then | |
| echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT | |
| echo "version_tag=v${{ inputs.version }}" >> $GITHUB_OUTPUT | |
| elif [[ ${GITHUB_REF_NAME/\//-} =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]]; then | |
| - name: Set Authorino build info | |
| id: build-info | |
| env: | |
| INPUT_VERSION: ${{ inputs.version }} | |
| run: | | |
| if [[ -n "$INPUT_VERSION" ]]; then | |
| if ! [[ "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then | |
| echo "::error::Invalid version input: $INPUT_VERSION" | |
| exit 1 | |
| fi | |
| printf 'version=%s\n' "$INPUT_VERSION" >> "$GITHUB_OUTPUT" | |
| printf 'version_tag=v%s\n' "$INPUT_VERSION" >> "$GITHUB_OUTPUT" | |
| elif [[ ${GITHUB_REF_NAME/\/,-} =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]]; then |
🧰 Tools
🪛 zizmor (1.26.1)
[error] 39-39: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 40-40: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 41-41: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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-images.yaml around lines 39 - 42, The conditional
block starting at line 39 directly interpolates the workflow input `${{
inputs.version }}` into shell code, which creates a shell injection
vulnerability if a caller passes values containing shell metacharacters like
`$(command)`. Instead of directly embedding the input in the bash conditional
and echo commands, set the input value as an environment variable first, then
reference that variable in the conditional check and the subsequent echo
statements that write to GITHUB_OUTPUT. This prevents the shell from
interpreting any special characters in the input value.
Source: Linters/SAST tools
| version: ${{ steps.validate.outputs.version }} | ||
| release-branch: ${{ steps.validate.outputs.release-branch }} | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/pre-release.yaml | sed -n '20,115p'Repository: Kuadrant/authorino
Length of output: 4142
Pin all action references to immutable commit SHAs.
Lines 27, 62, 82, and 108 use mutable version tags (@v4/@v5), leaving the workflow vulnerable to upstream tag retargeting. Replace with specific commit SHAs (e.g., actions/checkout@e2f20f853a2a19803ed141a6f266e17681910f6d).
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 27-31: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 27-27: 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/pre-release.yaml at line 27, The workflow file uses
mutable version tags (such as `@v4` and `@v5`) for GitHub Actions references on
lines 27, 62, 82, and 108, which creates security risks since these tags can be
retargeted upstream. Replace each mutable version tag with a specific immutable
commit SHA. For the actions/checkout action on line 27 and any other action
references throughout the file, determine the correct commit SHA for each action
version (for example, `@v4` should be replaced with the full commit SHA like
`@e2f20f853a2a19803ed141a6f266e17681910f6d`) and update all four occurrences to
use the pinned commit hash instead of the version tag.
Source: Linters/SAST tools
| permissions: | ||
| contents: write | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Reduce workflow token scope to least privilege.
contents: write at workflow scope grants write access to jobs that do not need it. Scope write permission only to jobs that create tags/releases.
Suggested fix
permissions:
- contents: write
+ contents: read
jobs:
+ tag:
+ permissions:
+ contents: write
+ needs: [read-version, smoke-tests]
+ runs-on: ubuntu-latest
+ steps:
+ ...
+
+ create-release:
+ permissions:
+ contents: write
+ needs: [read-version, build-image]
+ runs-on: ubuntu-latest
+ steps:
+ ...🧰 Tools
🪛 zizmor (1.26.1)
[error] 12-12: 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 11 - 13, The `contents: write`
permission is currently set at the workflow level, granting write access to all
jobs. Move this permission to only the specific job(s) that create tags or
releases by removing `contents: write` from the workflow-level permissions block
and adding it as a job-level permission to only the job(s) that perform release
operations. This ensures other jobs in the workflow operate with minimal
necessary privileges.
Source: Linters/SAST tools
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ inputs.release-branch }} |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/release.yamlRepository: Kuadrant/authorino
Length of output: 4881
Pin GitHub Actions to commit SHAs instead of mutable version tags.
Using @v4/@v5 allows silent upstream changes and weakens supply-chain security guarantees. Pin all GitHub Actions to their full commit SHAs: actions/checkout@<commit-sha> and actions/setup-go@<commit-sha>.
This applies to lines 22, 60, 65, 82, and 112 in this workflow file.
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 22-24: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 22-22: 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 22 - 24, Replace all mutable
GitHub Actions version tags in the release.yaml workflow with pinned commit
SHAs. Specifically, replace the `@v4` tag in the actions/checkout action on line
22, and locate the other GitHub Actions usages at lines 60, 65, 82, and 112,
then replace their mutable version tags (such as `@v4` or `@v5`) with their full
commit SHAs. You can find the correct commit SHAs by looking up each action's
releases on GitHub or using the GitHub CLI. This ensures that the workflow uses
immutable action references and prevents silent upstream changes that could
affect supply-chain security.
Source: Linters/SAST tools
| - name: Validate branch matches version | ||
| run: | | ||
| EXPECTED_BRANCH="release-${{ steps.parse.outputs.major }}.${{ steps.parse.outputs.minor }}" | ||
| if [[ "${{ inputs.release-branch }}" != "$EXPECTED_BRANCH" ]]; then | ||
| echo "::error::Branch '${{ inputs.release-branch }}' does not match version ${{ steps.parse.outputs.version }} (expected branch: $EXPECTED_BRANCH)" | ||
| exit 1 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the release.yaml file
git ls-files | grep -E 'release\.yaml|release\.yml'Repository: Kuadrant/authorino
Length of output: 235
🏁 Script executed:
# Check the content around lines 38-43 of the release.yaml file
cat -n .github/workflows/release.yaml | sed -n '30,50p'Repository: Kuadrant/authorino
Length of output: 1127
🌐 Web query:
GitHub Actions security best practices context interpolation shell script injection
💡 Result:
The primary risk of context interpolation in GitHub Actions arises because workflow expressions (the ${{ ... }} syntax) are evaluated by the GitHub Actions runner before the shell executes your script [1][2][3]. When you directly interpolate untrusted context values—such as github.event.issue.title or github.event.pull_request.head_ref—into a run step, you inadvertently allow an attacker to inject arbitrary shell commands [1][4][3]. To secure your workflows against this script injection vulnerability, follow these best practices: 1. Use Intermediate Environment Variables (Recommended) Never place ${{ ... }} expressions directly inside your shell command [5][6][3]. Instead, map the untrusted value to an environment variable in the env block of the step, and then reference that variable using the native shell syntax (e.g., $VAR or "$VAR") [7][5][3]. Vulnerable Example: run: echo "Processing PR: ${{ github.event.pull_request.title }}" Secure Example: env: PR_TITLE: ${{ github.event.pull_request.title }} run: echo "Processing PR: $PR_TITLE" 2. Avoid Re-interpolation Do not use ${{ env.VAR }} inside a run step to access an environment variable [5][6][3]. This re-introduces the interpolation vulnerability because the expression is evaluated before the shell executes the command [6][3]. Always use native shell syntax (e.g., $VAR) to access variables created in the env block [5][3]. 3. Use Actions for Complex Logic When possible, replace inline shell scripts with custom actions or existing community actions that process data as arguments rather than generating shell scripts [7][8]. Because arguments are passed directly to the action's code rather than being concatenated into a shell command string, this approach is inherently resistant to script injection [7][8]. 4. Treat All Contexts as Untrusted Any context that can be influenced by external users, such as PR titles, issue bodies, branch names, or author names, must be treated as untrusted input [1][4]. Be especially cautious with workflows triggered by events like pull_request, issue_comment, or workflow_run [8][3]. 5. Enable Security Scanning Use tools like CodeQL or ESLint plugins (e.g., eslint-plugin-github-actions) to automatically detect and flag unsafe interpolation of context values in your workflow YAML files [9][10]. Enabling code scanning helps prevent these vulnerabilities from being introduced in future changes [9].
Citations:
- 1: https://docs.github.com/en/actions/concepts/security/script-injections
- 2: https://phpunit.expert/articles/hardening-github-actions-workflows.html
- 3: https://cloud.hacktricks.wiki/en/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.html
- 4: https://securitylab.github.com/resources/github-actions-untrusted-input/
- 5: https://github.com/github/codeql/blob/main/actions/ql/src/Security/CWE-094/CodeInjectionMedium.md
- 6: https://codeql.github.com/codeql-query-help/actions/actions-code-injection-critical/
- 7: https://docs.github.com/en/enterprise-server@2.22/actions/security-guides/security-hardening-for-github-actions
- 8: https://docs.github.com/en/actions/reference/security/secure-use
- 9: https://github.blog/security/supply-chain-security/four-tips-to-keep-your-github-actions-workflows-secure/
- 10: https://nick2bad4u.github.io/eslint-plugin-github-actions-2/docs/rules/no-untrusted-input-in-run/
🏁 Script executed:
# Check the workflow trigger and input definitions
cat -n .github/workflows/release.yaml | sed -n '1,30p'Repository: Kuadrant/authorino
Length of output: 1092
Pass inputs.release-branch via an environment variable to prevent shell injection.
Line 41–42 directly interpolates the workflow dispatch input into a bash script, which allows shell command injection if the input contains metacharacters. GitHub Actions evaluates ${{ ... }} expressions before the shell executes, making the input available for expansion. Pass the input through the env block and reference it with native shell syntax instead.
Suggested fix
- name: Validate branch matches version
+ env:
+ INPUT_RELEASE_BRANCH: ${{ inputs.release-branch }}
run: |
EXPECTED_BRANCH="release-${{ steps.parse.outputs.major }}.${{ steps.parse.outputs.minor }}"
- if [[ "${{ inputs.release-branch }}" != "$EXPECTED_BRANCH" ]]; then
- echo "::error::Branch '${{ inputs.release-branch }}' does not match version ${{ steps.parse.outputs.version }} (expected branch: $EXPECTED_BRANCH)"
+ if [[ "$INPUT_RELEASE_BRANCH" != "$EXPECTED_BRANCH" ]]; then
+ echo "::error::Branch '$INPUT_RELEASE_BRANCH' does not match version ${{ steps.parse.outputs.version }} (expected branch: $EXPECTED_BRANCH)"
exit 1
fi🧰 Tools
🪛 zizmor (1.26.1)
[info] 40-40: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 40-40: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 41-41: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 42-42: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 42-42: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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 38 - 43, The step "Validate
branch matches version" directly interpolates the workflow input
`inputs.release-branch` into the bash script, creating a shell injection
vulnerability. Move `inputs.release-branch` to an environment variable by adding
an `env` block to the step that sets a variable (e.g., `RELEASE_BRANCH: ${{
inputs.release-branch }}`), then update the bash script to reference this
environment variable using native shell syntax (e.g., `$RELEASE_BRANCH`) instead
of the direct `${{ inputs.release-branch }}` interpolation on lines 41-42.
Source: Linters/SAST tools
| if git rev-parse "$TAG" >/dev/null 2>&1; then | ||
| echo "::error::Tag $TAG already exists" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Make tag creation idempotent for safe reruns.
If a previous run pushed the tag but failed later, reruns will always fail here and block recovery. Treat “tag already exists at HEAD” as success.
Suggested fix
if git rev-parse "$TAG" >/dev/null 2>&1; then
- echo "::error::Tag $TAG already exists"
- exit 1
+ TAG_SHA="$(git rev-list -n1 "$TAG")"
+ HEAD_SHA="$(git rev-parse HEAD)"
+ if [[ "$TAG_SHA" != "$HEAD_SHA" ]]; then
+ echo "::error::Tag $TAG already exists and points to $TAG_SHA, not $HEAD_SHA"
+ exit 1
+ fi
+ echo "Tag $TAG already exists at HEAD; continuing"
+ exit 0
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.
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| echo "::error::Tag $TAG already exists" | |
| exit 1 | |
| fi | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| TAG_SHA="$(git rev-list -n1 "$TAG")" | |
| HEAD_SHA="$(git rev-parse HEAD)" | |
| if [[ "$TAG_SHA" != "$HEAD_SHA" ]]; then | |
| echo "::error::Tag $TAG already exists and points to $TAG_SHA, not $HEAD_SHA" | |
| exit 1 | |
| fi | |
| echo "Tag $TAG already exists at HEAD; continuing" | |
| exit 0 | |
| 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 91 - 94, The tag existence check
in the release workflow is not idempotent - it fails unconditionally whenever
the tag already exists, blocking recovery from partial failures. Modify the git
tag validation logic to check if the tag exists at the current HEAD commit
rather than just checking if the tag exists. If the tag already points to HEAD,
allow the workflow to proceed as success. Only exit with an error if the tag
exists but points to a different commit, indicating a genuine conflict.
| run: | | ||
| chmod +x .github/scripts/validate-release-yaml.sh | ||
| .github/scripts/validate-release-yaml.sh "${{ github.head_ref }}" "Kuadrant" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Use github.base_ref via env to avoid injection and enforce the correct branch rule.
Lines 28-30 interpolate github.head_ref directly in the shell and pass the source branch, but the gate logic should evaluate the target release branch. This can both weaken validation semantics and allow shell-template injection through crafted PR branch names.
🔧 Proposed fix
- name: Validate release.yaml
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BASE_REF: ${{ github.base_ref }}
run: |
chmod +x .github/scripts/validate-release-yaml.sh
- .github/scripts/validate-release-yaml.sh "${{ github.head_ref }}" "Kuadrant"
+ .github/scripts/validate-release-yaml.sh "$BASE_REF" "Kuadrant"📝 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.
| run: | | |
| chmod +x .github/scripts/validate-release-yaml.sh | |
| .github/scripts/validate-release-yaml.sh "${{ github.head_ref }}" "Kuadrant" | |
| - name: Validate release.yaml | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| BASE_REF: ${{ github.base_ref }} | |
| run: | | |
| chmod +x .github/scripts/validate-release-yaml.sh | |
| .github/scripts/validate-release-yaml.sh "$BASE_REF" "Kuadrant" |
🧰 Tools
🪛 actionlint (1.7.12)
[error] 28-28: "github.head_ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details
(expression)
🪛 zizmor (1.26.1)
[error] 30-30: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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 around lines 28 - 30, The
validate-release-yaml.sh script is currently receiving the source branch via
github.head_ref, but it should receive the target branch via github.base_ref to
properly validate against the correct release branch. Additionally, to prevent
shell-template injection attacks through crafted branch names, pass the branch
reference as an environment variable rather than directly interpolating it in
the shell command. Modify the run block to export github.base_ref as an
environment variable and pass that variable to the validate-release-yaml.sh
script instead of the direct interpolation.
Source: Linters/SAST tools
Summary
Introduces an automated two-phase release process for Authorino, replacing the previous manual git-tag-and-build approach. The new workflow splits every release into a Pre-release phase (branch creation, version bump, code generation, PR) and a Release phase (smoke tests, tagging, image build, GitHub Release), with a mandatory human review gate between them.
This change implements the design specified in RFC: Two-Phase Release Workflow.
Motivation
The previous release process was entirely manual: a maintainer checked out a ref, created a signed tag, pushed it, manually triggered the image build, and hand-wrote release notes. This was error-prone, undocumented in automation, and lacked pre-release validation.
The two-phase approach automates the mechanical steps while preserving human oversight at the critical review point before a release is finalized.
What changed
New workflows
pre-release.yaml) — triggered manually with a target version. Creates therelease-X.Ybranch (if needed), opens apre-release-vX.Y.Zbranch that bumpsrelease.yaml, runsmake generateandmake manifests, and opens a PR against the release branch.release.yaml) — triggered manually with a release branch name. Reads the version fromrelease.yaml, validates the branch matches, runs smoke tests (lint, unit tests, CEL tests), creates an annotated tag, builds and pushes the multi-arch container image viabuild-images.yaml, and creates the GitHub Release.version-gate.yaml) — runs on PRs targetingrelease-**branches whenrelease.yamlchanges. Validates that the version is not0.0.0on non-main branches and that any declared dependencies have published releases.New scripts
parse-version.sh— extracts and validates semver components fromrelease.yaml, used by the release workflow.validate-release-yaml.sh— validatesrelease.yamlcontent and dependency versions, used by the version gate.Modified workflows
build-images.yaml) — now supportsworkflow_callwithversionandrefinputs so the release workflow can invoke it directly. Image tagging logic updated to use the version from inputs when provided.New files
release.yaml— version source-of-truth file at the repository root. Set to0.0.0onmain(active development) and updated to the target version on release branches.Updated documentation
RELEASE.md— rewritten to document the new two-phase process, including step-by-step instructions, artifact locations.Examples
Summary by CodeRabbit
Release Notes
Documentation
Chores