From c18ae9b40c3c6eea9f24752a3397228d28aff309 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Fri, 27 Mar 2026 15:29:10 +0100 Subject: [PATCH] Harden release workflows against tag injection and credential leakage Made-with: Cursor --- .github/workflows/create-major-tag.yml | 27 ++++++++++++++++++++++---- .github/workflows/required-labels.yml | 11 +++++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/.github/workflows/create-major-tag.yml b/.github/workflows/create-major-tag.yml index 4854188..85d831d 100644 --- a/.github/workflows/create-major-tag.yml +++ b/.github/workflows/create-major-tag.yml @@ -6,18 +6,37 @@ on: - published permissions: - contents: write + contents: read + +concurrency: + group: create-major-tag + cancel-in-progress: false jobs: create-major-tag: + permissions: + contents: write runs-on: ubuntu-slim steps: - uses: actions/checkout@v6 - - name: Get major version + with: + persist-credentials: false + + - name: Validate and extract major version + env: + TAG: ${{ github.event.release.tag_name }} run: | - MAJOR_VERSION=$(echo "${GITHUB_REF#refs/tags/}" | awk -F. '{print $1}') - echo "MAJOR_VERSION=${MAJOR_VERSION}" >> "${GITHUB_ENV}" + if [[ ! "$TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Tag '${TAG}' does not match expected semver format (N.N.N)" + exit 1 + fi + echo "MAJOR_VERSION=${TAG%%.*}" >> "${GITHUB_ENV}" + - name: Create major tag + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" git tag "v${MAJOR_VERSION}" git push -f origin "refs/tags/v${MAJOR_VERSION}" + git remote set-url origin "https://github.com/${GITHUB_REPOSITORY}.git" diff --git a/.github/workflows/required-labels.yml b/.github/workflows/required-labels.yml index 560b140..f1a252d 100644 --- a/.github/workflows/required-labels.yml +++ b/.github/workflows/required-labels.yml @@ -19,13 +19,20 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + with: + sparse-checkout: .github/release-drafter.yml + persist-credentials: false - name: Wait for PR to be ready (if just opened) if: github.event_name == 'pull_request_target' && github.event.action == 'opened' run: sleep 30 - id: get-labels run: | - labels=$(yq '[.categories[].labels] + .exclude-labels | flatten | unique | sort | @tsv' .github/release-drafter.yml | tr '\t' ',') - echo "labels=$labels" >> "${GITHUB_OUTPUT}" + delimiter="$(openssl rand -hex 16)" + { + echo "labels<<${delimiter}" + yq '[.categories[].labels] + .exclude-labels | flatten | unique | sort | @tsv' .github/release-drafter.yml | tr '\t' ',' + echo "${delimiter}" + } >> "${GITHUB_OUTPUT}" - id: check-labels uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5 with: