From 7017b6340e64aeaa8349cb379074cc539647c12f Mon Sep 17 00:00:00 2001 From: Jeff <158072326+jeffrey701@users.noreply.github.com> Date: Wed, 3 Jun 2026 02:53:15 -0700 Subject: [PATCH] fix(helm): normalize appVersion to strip leading v (#2050) (#2156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(helm): normalize appVersion to strip leading v (#2050) Releases triggered on a tag like `v0.7.1-rc.1` end up writing `appVersion: "v0.7.1-rc.1"` into Chart.yaml / the published index.yaml, but the Docker image is pushed to GHCR without the leading `v` (`ghcr.io/we-promise/sure:0.7.1-rc.1`). Flux CD / any consumer that pulls the chart then fails with `ImagePullBackoff` against `v0.7.1-rc.1` (a tag that doesn't exist). `normalize_version` is already applied to `CHART_VERSION`; route the two tag-derived `APP_VERSION` paths through the same helper so the appVersion matches the published image tag. Closes #2050 * chore(ci): bind helm-publish version inputs to step env (#2050) @coderabbitai (zizmor) flagged that the version-resolve step expanded ${{ inputs.chart_version }} and ${{ inputs.app_version }} directly into bash, which is a template-injection vector — a malicious caller of this reusable workflow could inject shell via an input like '; rm -rf … #'. Bind both inputs to step env (CHART_VERSION_INPUT, APP_VERSION_INPUT) and reference them as shell variables in the conditionals. Behaviour is unchanged; the values just arrive through the env table instead of the runner's template pass. --------- Co-authored-by: jeffrey701 --- .github/workflows/helm-publish.yml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/helm-publish.yml b/.github/workflows/helm-publish.yml index 510c64049..0588162c0 100644 --- a/.github/workflows/helm-publish.yml +++ b/.github/workflows/helm-publish.yml @@ -40,6 +40,12 @@ jobs: - name: Resolve chart and app versions id: version shell: bash + # Bind workflow inputs to env so the values arrive as shell variables + # instead of being interpolated verbatim by the `${{ }}` runner pass. + # zizmor flags the direct expansion as a template-injection risk. + env: + CHART_VERSION_INPUT: ${{ inputs.chart_version }} + APP_VERSION_INPUT: ${{ inputs.app_version }} run: | set -euo pipefail @@ -48,18 +54,24 @@ jobs: echo "${raw#v}" } - if [ -n "${{ inputs.chart_version }}" ]; then - CHART_VERSION="$(normalize_version "${{ inputs.chart_version }}")" + if [ -n "$CHART_VERSION_INPUT" ]; then + CHART_VERSION="$(normalize_version "$CHART_VERSION_INPUT")" elif [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME}" == v* ]]; then CHART_VERSION="$(normalize_version "${GITHUB_REF_NAME}")" else CHART_VERSION="0.0.0-nightly.$(date -u +'%Y%m%d.%H%M%S')" fi - if [ -n "${{ inputs.app_version }}" ]; then - APP_VERSION="${{ inputs.app_version }}" + # Normalize APP_VERSION the same way CHART_VERSION is — appVersion + # must match the OCI image tag in GHCR, which is published without a + # leading `v`. Without this, a release on tag `v0.7.1-rc.1` writes + # `appVersion: "v0.7.1-rc.1"` into Chart.yaml / index.yaml, and Helm + # then fails to pull `ghcr.io/we-promise/sure:v0.7.1-rc.1` (the real + # tag is `0.7.1-rc.1`). See #2050. + if [ -n "$APP_VERSION_INPUT" ]; then + APP_VERSION="$(normalize_version "$APP_VERSION_INPUT")" elif [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME}" == v* ]]; then - APP_VERSION="${GITHUB_REF_NAME}" + APP_VERSION="$(normalize_version "${GITHUB_REF_NAME}")" else APP_VERSION="${CHART_VERSION}" fi