Skip to content

ci: route release create-release-tag shell through env, not shell interpolation - #12229

Merged
julian-risch merged 1 commit into
deepset-ai:mainfrom
camgrimsec:ci-release-tag-env-vars
Aug 4, 2026
Merged

ci: route release create-release-tag shell through env, not shell interpolation#12229
julian-risch merged 1 commit into
deepset-ai:mainfrom
camgrimsec:ci-release-tag-env-vars

Conversation

@camgrimsec

Copy link
Copy Markdown
Contributor

Proposed change

Follow-up in the same style as #11856 (release-notes reST check) and #11857 (release version input): route ${{ ... }} expressions in the create-release-tag job of release.yml through the step's env: block so the shell body works with plain variables instead of textual template expansion.

Before

- name: Update VERSION.txt and create tag
  env:
    GITHUB_TOKEN: ${{ secrets.HAYSTACK_BOT_TOKEN }}
  run: |
    git config --global user.name "github-actions[bot]"
    git config --global user.email "github-actions[bot]@users.noreply.github.com"

    git checkout ${{ needs.parse-validate-version.outputs.release_branch }}
    git pull origin ${{ needs.parse-validate-version.outputs.release_branch }}

    echo "${{ needs.parse-validate-version.outputs.version }}" > VERSION.txt
    git add VERSION.txt
    git commit -m "bump version to ${{ needs.parse-validate-version.outputs.version }}"
    git push origin ${{ needs.parse-validate-version.outputs.release_branch }}

    TAG="v${{ needs.parse-validate-version.outputs.version }}"
    git tag -m "$TAG" "$TAG"
    git push origin "$TAG"

After

- name: Update VERSION.txt and create tag
  env:
    GITHUB_TOKEN: ${{ secrets.HAYSTACK_BOT_TOKEN }}
    VERSION: ${{ needs.parse-validate-version.outputs.version }}
    RELEASE_BRANCH: ${{ needs.parse-validate-version.outputs.release_branch }}
  run: |
    git config --global user.name "github-actions[bot]"
    git config --global user.email "github-actions[bot]@users.noreply.github.com"

    git checkout "$RELEASE_BRANCH"
    git pull origin "$RELEASE_BRANCH"

    echo "$VERSION" > VERSION.txt
    git add VERSION.txt
    git commit -m "bump version to $VERSION"
    git push origin "$RELEASE_BRANCH"

    TAG="v$VERSION"
    git tag -m "$TAG" "$TAG"
    git push origin "$TAG"

Six direct ${{ needs.parse-validate-version.outputs.* }} expansions inside the shell body become two env: bindings plus normal shell variables.

Why

The version and release_branch values are produced by .github/utils/parse_validate_version.sh and validated there, so today's create-release-tag job is not exploitable. That's exactly why this is a cheap change: it's the same pattern already used in the release-notes-check step of the same workflow after #11856, and the same pattern applied to the top-level inputs.version after #11857.

The reason to keep extending it here:

  1. The step already holds secrets.HAYSTACK_BOT_TOKEN and runs with contents: write, so the blast radius of any future regression (a script tweak, a new input source, a caller invoking this job with different needs.* outputs) is a bot-token-authorized push to main / a release branch. Env-passing removes the whole class of "the validator missed a shell metacharacter" as a failure mode.
  2. GitHub's security-hardening guide for GitHub Actions recommends this pattern for any ${{ }} expression that flows into run:. Zizmor / actionlint's template-injection rule flags every one of these six expansions.
  3. It keeps the release workflow's style consistent with the neighboring jobs that have already been converted.

What is not changed

  • The commit message, tag name, and pushed branch name are the same strings the previous shell would have produced (no shell-quoting escape sequences are introduced).
  • git config, git add, git tag -m "$TAG" "$TAG", and git push origin "$TAG" are untouched — those already used shell variables.
  • The env: GITHUB_TOKEN: ${{ secrets.HAYSTACK_BOT_TOKEN }} line stays as-is.
  • The token: ${{ secrets.HAYSTACK_BOT_TOKEN }} on the actions/checkout step above is unchanged.
  • No other job in release.yml is touched.

Verification

  • python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))" succeeds.
  • Diff is 14 lines total (+8, -6) in one file.
  • Every ${{ needs.parse-validate-version.outputs.* }} expression that previously appeared inside the run: body of this step is gone; each is now bound once at the top of the step's env: block.

Checklist

  • I have read the contributors guidelines and the code of conduct
  • I have updated the related issue with new insights and changes
  • I added unit tests and updated the docstrings — N/A, this is a CI-only change with no runtime impact
  • I've used one of the conventional commit types for my PR title
  • I documented my code
  • I ran pre-commit hooks and fixed any issue — N/A, YAML file, no code hooks apply

Thanks for keeping the release workflow tidy.

@camgrimsec
camgrimsec requested a review from a team as a code owner August 4, 2026 00:25
@camgrimsec
camgrimsec requested review from julian-risch and removed request for a team August 4, 2026 00:25
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

@camgrimsec is attempting to deploy a commit to the deepset Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Hi @camgrimsec, thanks for your interest in contributing to Haystack! 🙏

⚠️ You currently have 4 open pull requests in this repository (#12230, #11742, #11737 and this one). Our review capacity is limited, so please hold off opening more PRs until we've had a chance to review your first 2 open PRs. This helps us give each contribution the attention it deserves. Thank you!

This is an automated message to help us keep the review queue healthy.

…erpolation

The Update VERSION.txt and create tag step in the release workflow ran
git checkout, git pull, git commit, git push, and git tag with
${{ needs.parse-validate-version.outputs.version }} and ${{ needs.parse-validate-version.outputs.release_branch }}
interpolated directly into the shell body.

Values come from .github/utils/parse_validate_version.sh, but any change
to that script (or its inputs) that let a metacharacter through would
land as a shell token in a step that already runs with a token scoped
to contents: write and secrets.HAYSTACK_BOT_TOKEN. GitHub's own
security-hardening guide flags this as a script-injection pattern and
recommends passing the values through env: so the shell sees them as
opaque strings.

Change:
- Add VERSION and RELEASE_BRANCH to the step's env: block
- Replace every ${{ ... }} in the run: body with quoted "$VERSION" and
  "$RELEASE_BRANCH"

Behavior is identical: the commit message, tag, and pushed branch are
the same strings the script would have produced before. Same env-var
pattern already merged in deepset-ai#11856 (release-notes reST check) and deepset-ai#11857
(release version input).

@julian-risch julian-risch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me! Thank you for your contribution!

@julian-risch
julian-risch merged commit 1a8deb1 into deepset-ai:main Aug 4, 2026
20 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants