From 94388fd3f4a2e232be19dbdc5c5d8102cc055957 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:39:25 -0400 Subject: [PATCH] fix(release-notifier): harden inputs --- .github/workflows/__call-release-notifier.yml | 23 ++++++++++--------- tests/release-notifier-workflow.test.mjs | 21 +++++++++++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 tests/release-notifier-workflow.test.mjs diff --git a/.github/workflows/__call-release-notifier.yml b/.github/workflows/__call-release-notifier.yml index d9c31844..cce5205e 100644 --- a/.github/workflows/__call-release-notifier.yml +++ b/.github/workflows/__call-release-notifier.yml @@ -76,11 +76,17 @@ jobs: if: >- steps.check-label.outputs.hasTopic == 'true' && steps.check-release.outputs.isLatestRelease == 'true' + # Keep event data out of the generated shell script so Markdown is never evaluated as shell syntax. + env: + RELEASE_BODY: ${{ github.event.release.body }} + REPOSITORY: ${{ github.repository }} + REPOSITORY_NAME: ${{ github.event.repository.name }} + TAG_NAME: ${{ github.event.release.tag_name }} run: | # setup variables - tag_name="${{ github.event.release.tag_name }}" + tag_name="${TAG_NAME}" semver="${tag_name#v}" - repo_lower="$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]')" + repo_lower="$(printf '%s' "${REPOSITORY_NAME}" | tr '[:upper:]' '[:lower:]')" # extract year, month, and day year="${semver%%.*}" @@ -95,15 +101,15 @@ jobs: mkdir -p "$(dirname "${file_name}")" thumbnail_base_url="https://app.lizardbyte.dev/dashboard/github/openGraphImages" - thumbnail_url="${thumbnail_base_url}/${{ github.event.repository.name }}_624x312.png" + thumbnail_url="${thumbnail_base_url}/${REPOSITORY_NAME}_624x312.png" # create jekyll blog post { echo "---" echo "layout: release" - echo "title: ${{ github.event.repository.name }} ${tag_name} Released" + echo "title: ${REPOSITORY_NAME} ${tag_name} Released" echo "release-tag: ${tag_name}" - echo "gh-repo: ${{ github.repository }}" + echo "gh-repo: ${REPOSITORY}" echo "gh-badge: [follow, fork, star]" echo "tags: [release, ${repo_lower}]" echo "thumbnail-img: ${thumbnail_url}" @@ -114,12 +120,7 @@ jobs: echo "" } > "${file_name}" - release_body=$(cat <> "${file_name}" + printf '%s\n' "${RELEASE_BODY}" >> "${file_name}" - name: Create/Update Pull Request id: create-pr diff --git a/tests/release-notifier-workflow.test.mjs b/tests/release-notifier-workflow.test.mjs new file mode 100644 index 00000000..807a66ea --- /dev/null +++ b/tests/release-notifier-workflow.test.mjs @@ -0,0 +1,21 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import test from 'node:test'; + +const workflow = fs.readFileSync('.github/workflows/__call-release-notifier.yml', 'utf8'); +const createBlogStart = workflow.indexOf(' - name: Create blog post'); +const createBlogEnd = workflow.indexOf(' - name: Create/Update Pull Request', createBlogStart); +const createBlogStep = workflow.slice(createBlogStart, createBlogEnd); +const runScript = createBlogStep.slice(createBlogStep.indexOf(' run: |')); + +test('create blog post does not interpolate release data into its shell script', () => { + assert.notEqual(createBlogStart, -1); + assert.notEqual(createBlogEnd, -1); + + assert.match(createBlogStep, /RELEASE_BODY: \$\{\{ github\.event\.release\.body \}\}/); + assert.match(createBlogStep, /REPOSITORY: \$\{\{ github\.repository \}\}/); + assert.match(createBlogStep, /REPOSITORY_NAME: \$\{\{ github\.event\.repository\.name \}\}/); + assert.match(createBlogStep, /TAG_NAME: \$\{\{ github\.event\.release\.tag_name \}\}/); + assert.doesNotMatch(runScript, /\$\{\{\s*github\./); + assert.match(runScript, /printf '%s\\n' "\$\{RELEASE_BODY\}" >> "\$\{file_name\}"/); +});