From 2dbfc35c50a4034f0905a6e4b1999aafcff44f69 Mon Sep 17 00:00:00 2001 From: Szymon Wlodarski Date: Mon, 8 Dec 2025 13:54:33 +0100 Subject: [PATCH 1/2] [#47] Push release to Readme.com changelog --- .github/workflows/release.yml | 138 ++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..30c66ef --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,138 @@ +name: Release + +on: + workflow_dispatch: + inputs: + branch: + description: "Branch to release from" + required: true + default: "master" + type: string + version: + description: "Version number (e.g., 2.1.4)" + required: true + type: string + overview: + description: "Release overview (will be placed at top of notes)" + required: true + type: string + +jobs: + release: + name: Create tag and release + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch }} + fetch-depth: 0 + + - name: Validate version format + id: version + run: | + if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Version must be in format X.Y.Z (e.g., 9.3.3)" + exit 1 + fi + version="${{ inputs.version }}" + echo "version=$version" >> $GITHUB_OUTPUT + + - name: Configure Git + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + + - name: Create and push tag + run: | + git tag "${{ steps.version.outputs.version }}" + git push origin "${{ steps.version.outputs.version }}" + + - name: Get merged PR titles and format release notes + id: changelog + run: | + git fetch --tags + + # Get most recent and previous tags + tags=($(git tag --sort=-creatordate)) + new_tag="${tags[0]}" + prev_tag="${tags[1]}" + + if [ -z "$prev_tag" ]; then + echo "Warning: No previous tag found. Skipping full changelog link." + changelog="" + else + changelog="**Full Changelog**: https://github.com/${{ github.repository }}/compare/$prev_tag...$new_tag" + fi + + prs=$(gh pr list --state merged --base "${{ inputs.branch }}" --json title,mergedAt --jq '[.[] | select(.mergedAt != null) | .title]') + joined=$(echo "$prs" | jq -r '.[]' | sed 's/^/* /') + + echo "RELEASE_NOTES<> $GITHUB_ENV + echo "${{ github.event.inputs.overview }}" >> $GITHUB_ENV + echo "" >> $GITHUB_ENV + echo "## What's Changed" >> $GITHUB_ENV + echo "$joined" >> $GITHUB_ENV + echo "" >> $GITHUB_ENV + echo "$changelog" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create GitHub Release + run: | + gh release create "${{ steps.version.outputs.version }}" \ + --title "${{ steps.version.outputs.version }}" \ + --notes "${{ env.RELEASE_NOTES }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + readme-changelog: + name: Publish changelog to Readme + needs: release + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Extract release data + id: release_data + run: | + echo "title=${{ needs.release.outputs.version }}" >> $GITHUB_OUTPUT + body=$(gh release view ${{ needs.release.outputs.version }} --json body --jq .body) + body_escaped=$(echo "$body" \ + | sed 's/&/\&/g' \ + | sed 's//\>/g' \ + | sed 's/{/\{/g' \ + | sed 's/}/\}/g') + { + echo "body<> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish changelog to Readme + env: + README_API_KEY: ${{ secrets.README_API_KEY }} + run: | + jq -n --arg title "BitPay Utils cryptography pack v${{ steps.release_data.outputs.title }}" \ + --arg body "${{ steps.release_data.outputs.body }}" \ + '{ + title: $title, + content: { + body: $body + }, + privacy: { view: "public" } + }' > payload.json + + curl --location 'https://api.readme.com/v2/changelogs' \ + --header "Authorization: Bearer $README_API_KEY" \ + --header 'Content-Type: application/json' \ + --data @payload.json From 99385cc572cb921b1f7e29351ccf38f0b641197b Mon Sep 17 00:00:00 2001 From: Szymon Wlodarski Date: Mon, 2 Feb 2026 10:24:30 +0100 Subject: [PATCH 2/2] #47 Security: Fix untrusted input vulnerability in release workflow --- .github/workflows/release.yml | 45 +++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 30c66ef..88698f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,13 +33,14 @@ jobs: - name: Validate version format id: version + env: + VERSION_INPUT: ${{ inputs.version }} run: | - if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + if [[ ! "$VERSION_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Error: Version must be in format X.Y.Z (e.g., 9.3.3)" exit 1 fi - version="${{ inputs.version }}" - echo "version=$version" >> $GITHUB_OUTPUT + echo "version=$VERSION_INPUT" >> $GITHUB_OUTPUT - name: Configure Git run: | @@ -53,6 +54,11 @@ jobs: - name: Get merged PR titles and format release notes id: changelog + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_INPUT: ${{ inputs.branch }} + OVERVIEW_INPUT: ${{ inputs.overview }} + REPOSITORY: ${{ github.repository }} run: | git fetch --tags @@ -65,30 +71,30 @@ jobs: echo "Warning: No previous tag found. Skipping full changelog link." changelog="" else - changelog="**Full Changelog**: https://github.com/${{ github.repository }}/compare/$prev_tag...$new_tag" + changelog="**Full Changelog**: https://github.com/$REPOSITORY/compare/$prev_tag...$new_tag" fi - prs=$(gh pr list --state merged --base "${{ inputs.branch }}" --json title,mergedAt --jq '[.[] | select(.mergedAt != null) | .title]') + prs=$(gh pr list --state merged --base "$BRANCH_INPUT" --json title,mergedAt --jq '[.[] | select(.mergedAt != null) | .title]') joined=$(echo "$prs" | jq -r '.[]' | sed 's/^/* /') echo "RELEASE_NOTES<> $GITHUB_ENV - echo "${{ github.event.inputs.overview }}" >> $GITHUB_ENV + echo "$OVERVIEW_INPUT" >> $GITHUB_ENV echo "" >> $GITHUB_ENV echo "## What's Changed" >> $GITHUB_ENV echo "$joined" >> $GITHUB_ENV echo "" >> $GITHUB_ENV echo "$changelog" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create GitHub Release - run: | - gh release create "${{ steps.version.outputs.version }}" \ - --title "${{ steps.version.outputs.version }}" \ - --notes "${{ env.RELEASE_NOTES }}" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NOTES: ${{ env.RELEASE_NOTES }} + VERSION: ${{ steps.version.outputs.version }} + run: | + gh release create "$VERSION" \ + --title "$VERSION" \ + --notes "$NOTES" readme-changelog: name: Publish changelog to Readme @@ -101,9 +107,12 @@ jobs: - name: Extract release data id: release_data + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_VERSION: ${{ needs.release.outputs.version }} run: | - echo "title=${{ needs.release.outputs.version }}" >> $GITHUB_OUTPUT - body=$(gh release view ${{ needs.release.outputs.version }} --json body --jq .body) + echo "title=$RELEASE_VERSION" >> $GITHUB_OUTPUT + body=$(gh release view "$RELEASE_VERSION" --json body --jq .body) body_escaped=$(echo "$body" \ | sed 's/&/\&/g' \ | sed 's/> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish changelog to Readme env: README_API_KEY: ${{ secrets.README_API_KEY }} + RELEASE_TITLE: ${{ steps.release_data.outputs.title }} + RELEASE_BODY: ${{ steps.release_data.outputs.body }} run: | - jq -n --arg title "BitPay Utils cryptography pack v${{ steps.release_data.outputs.title }}" \ - --arg body "${{ steps.release_data.outputs.body }}" \ + jq -n --arg title "BitPay Utils cryptography pack v$RELEASE_TITLE" \ + --arg body "$RELEASE_BODY" \ '{ title: $title, content: {