From c5dc7a26af70a0fb4bdc018ed0e4470555617655 Mon Sep 17 00:00:00 2001 From: WAHIB-EL-KHADIRI Date: Wed, 19 Aug 2026 12:25:36 +0200 Subject: [PATCH 1/2] [MNT] harden the PyPI release workflow against tag-name injection `check_tag` expanded the release tag straight into a shell body: TAG="${{ github.event.release.tag_name }}" A `${{ ... }}` expression is substituted as text before bash parses the script, so a tag name containing shell metacharacters is executed rather than compared. Passing the value through `env` makes it an ordinary variable, which is only ever data. The job matters because of where it sits: `check_tag` gates `build_wheels`, which produces the artifact `upload_wheels` publishes to PyPI. Code running in that chain can alter what is released. Also adds a workflow-level `permissions: contents: read`. There was no `permissions:` block, so every job inherited the repository default. Job-level permissions replace rather than extend the default, so `upload_wheels` keeps exactly its declared `id-token: write` for Trusted Publishing and is unaffected. Verified with zizmor: the template-injection finding on this file is gone and the YAML still parses with the same four jobs. Co-Authored-By: Claude Opus 5 --- .github/workflows/pypi_release.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pypi_release.yml b/.github/workflows/pypi_release.yml index 465c7efa2..93b0e8f75 100644 --- a/.github/workflows/pypi_release.yml +++ b/.github/workflows/pypi_release.yml @@ -4,6 +4,12 @@ on: release: types: [published] +# Least privilege by default. This workflow builds the artifact that is published +# to PyPI, so the token it carries is the most valuable one in the repository. +# `upload_wheels` declares its own `id-token: write` for Trusted Publishing. +permissions: + contents: read + jobs: check_tag: name: Check tag @@ -17,8 +23,14 @@ jobs: python-version: '3.11' - shell: bash + # The tag name is passed through `env` rather than expanded directly into + # the script. A `${{ ... }}` expansion is substituted as text before bash + # sees it, so a tag containing shell metacharacters would run as code; as + # an environment variable it is only ever data. + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} run: | - TAG="${{ github.event.release.tag_name }}" + TAG="${RELEASE_TAG}" GH_TAG_NAME="${TAG#v}" PY_VERSION=$(python - <<'PY' import pathlib, tomllib From 5f37e201e88a95b609b931915cae5388c663c76a Mon Sep 17 00:00:00 2001 From: WAHIB-EL-KHADIRI Date: Wed, 26 Aug 2026 00:25:52 +0200 Subject: [PATCH 2/2] [MNT] drop the explanatory comments from the release workflow Addresses review feedback: the comments explained the reasoning behind the change rather than the file, so they belong in the PR description. The hardening itself is unchanged. Co-Authored-By: Claude Opus 5 --- .github/workflows/pypi_release.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/pypi_release.yml b/.github/workflows/pypi_release.yml index 93b0e8f75..abbfaf8cc 100644 --- a/.github/workflows/pypi_release.yml +++ b/.github/workflows/pypi_release.yml @@ -4,9 +4,6 @@ on: release: types: [published] -# Least privilege by default. This workflow builds the artifact that is published -# to PyPI, so the token it carries is the most valuable one in the repository. -# `upload_wheels` declares its own `id-token: write` for Trusted Publishing. permissions: contents: read @@ -23,10 +20,6 @@ jobs: python-version: '3.11' - shell: bash - # The tag name is passed through `env` rather than expanded directly into - # the script. A `${{ ... }}` expansion is substituted as text before bash - # sees it, so a tag containing shell metacharacters would run as code; as - # an environment variable it is only ever data. env: RELEASE_TAG: ${{ github.event.release.tag_name }} run: |