ci: bind version/ref inputs via env before shell in release workflows
What changed, and why it matters
This commit hardens four GitHub release workflows by moving user-controlled values (version strings and branch/tag names) out of shell command text and into environment variables. This closes a class of attack where a maliciously crafted version string or tag name could be interpreted as shell commands, potentially altering release artifacts or stealing repository secrets. The change is defensive and does not claim to fix an active vulnerability.
Treat this as a routine security hardening improvement. Review the workflows to confirm all user-controlled inputs are now env-bound and quoted, verify that no remaining direct interpolations exist in these release workflows, and consider applying the same pattern to other workflows in the repository.
Security signals we found
Direct interpolation of GitHub Actions context values into run: shell blocks removed
User-controlled inputs (inputs.version, github.ref_name, github.ref_type) bound through env:
Shell variables are quoted where used
Follow-up to a prior docker/pypi env-binding hardening pass
Workflows involved in building and publishing releases were modified
Evidence from the diff
The patch applies GitHub’s recommended workflow hardening pattern: values such as inputs.version, github.ref_name, github.ref_type, and steps.resolve.outputs.version are now bound via env: keys and referenced inside run: blocks as quoted shell variables. Previously these values were interpolated directly into shell scripts, creating script-injection risk if an attacker controlled the input (e.g., via a tag name or workflow_dispatch version). The affected workflows are check-release-tag.yml, pypi-build.yml, release-build.yml, and release-publish.yml. A redundant CHANGELOG_VERSION assignment was also removed in release-publish.yml.
Changed components
.github/workflows/check-release-tag.yml.github/workflows/pypi-build.yml.github/workflows/release-build.yml.github/workflows/release-publish.ymlInspect captured patch +22 / −10
### .github/workflows/check-release-tag.yml
@@ -28,10 +28,14 @@ jobs:
- name: Validate tag matches .version file
id: resolve
+ env:
+ REF_TYPE: ${{ github.ref_type }}
+ INPUT_VERSION: ${{ inputs.version }}
+ REF_NAME: ${{ github.ref_name }}
run: |
- if [[ "${{ github.ref_type }}" != "tag" ]]; then
- echo "::notice::Not triggered by a tag push (ref_type=${{ github.ref_type }}); skipping .version check."
- echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
+ if [[ "$REF_TYPE" != "tag" ]]; then
+ echo "::notice::Not triggered by a tag push (ref_type=$REF_TYPE); skipping .version check."
+ echo "version=$INPUT_VERSION" >> "$GITHUB_OUTPUT"
exit 0
fi
@@ -41,7 +45,7 @@ jobs:
fi
FILE_VERSION="$(tr -d '[:space:]' < .version)"
- TAG_VERSION="${{ github.ref_name }}"
+ TAG_VERSION="$REF_NAME"
if [[ "$TAG_VERSION" != "$FILE_VERSION" ]]; then
echo "::error::Tag '$TAG_VERSION' does not match version in .version file ('$FILE_VERSION'). Refusing to release."
@@ -53,4 +57,6 @@ jobs:
- name: Validate release
if: ${{ !inputs.skip_validation }}
- run: tools/check-release.sh --version=${{ steps.resolve.outputs.version }}
+ env:
+ RESOLVED_VERSION: ${{ steps.resolve.outputs.version }}
+ run: tools/check-release.sh "--version=$RESOLVED_VERSION"
### .github/workflows/pypi-build.yml
@@ -34,8 +34,10 @@ jobs:
uses: astral-sh/setup-uv@v8.1.0
- name: Update pyln versions
+ env:
+ INPUT_VERSION: ${{ inputs.version }}
run: |
- make update-pyln-versions NEW_VERSION=${{ inputs.version }}
+ make update-pyln-versions NEW_VERSION="$INPUT_VERSION"
- name: Build distribution 📦
run: uv build --package ${{ matrix.PACKAGE }}
### .github/workflows/release-build.yml
@@ -40,9 +40,12 @@ jobs:
if: contains(matrix.target, 'Ubuntu')
- name: Build release
+ env:
+ SKIP_VALIDATION: ${{ inputs.skip_validation }}
+ INPUT_VERSION: ${{ inputs.version }}
run: |
- if [[ "${{ inputs.skip_validation }}" == "true" ]]; then
- tools/build-release.sh ${{ matrix.target }} --force-version "${{ inputs.version }}" --force-unclean --force-mtime "$(date +%Y-%m-%d)"
+ if [[ "$SKIP_VALIDATION" == "true" ]]; then
+ tools/build-release.sh ${{ matrix.target }} --force-version "$INPUT_VERSION" --force-unclean --force-mtime "$(date +%Y-%m-%d)"
else
tools/build-release.sh ${{ matrix.target }}
fi
### .github/workflows/release-publish.yml
@@ -49,9 +49,10 @@ jobs:
- name: Determine release data
id: release_data
+ env:
+ INPUT_VERSION: ${{ inputs.version }}
run: |
- CHANGELOG_VERSION=${VERSION#v}
- VERSION="${{ inputs.version }}"
+ VERSION="$INPUT_VERSION"
CHANGELOG_VERSION=${VERSION#v}
CHANGELOG_TITLE=$(grep "## \[${CHANGELOG_VERSION}\]" CHANGELOG.md)
RELEASE_TITLE=$(echo $CHANGELOG_TITLE | cut -d'"' -f2)Why this scored 46/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.