From 2fa8e16206c53d71420ed01ac697d87eed0cc6e5 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Aug 2025 12:36:42 +0200 Subject: [PATCH 01/11] fix(updater): Prevent script injection vulnerabilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add input validation and use environment variables instead of direct interpolation to prevent potential script injection attacks through user-controlled workflow inputs. - Add validate-inputs job to check for safe characters in inputs.name and inputs.path - Move all environment variable declarations to job level for better organization - Replace direct interpolation in PR titles and PowerShell scripts with env variables - Ensure all user inputs are properly sanitized before use 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/updater.yml | 52 +++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 7b70200c..ba28d988 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -72,6 +72,23 @@ jobs: with: access_token: ${{ github.token }} + validate-inputs: + runs-on: ubuntu-latest + steps: + - name: Validate inputs + run: | + # Validate that inputs.name contains only safe characters + if [[ ! "${{ inputs.name }}" =~ ^[a-zA-Z0-9_\-\./@ ]+$ ]]; then + echo "::error::Invalid dependency name: '${{ inputs.name }}'. Only alphanumeric characters, spaces, and _-./@ are allowed." + exit 1 + fi + + # Validate that inputs.path contains only safe characters + if [[ ! "${{ inputs.path }}" =~ ^[a-zA-Z0-9_\-\./]+$ ]]; then + echo "::error::Invalid dependency path: '${{ inputs.path }}'. Only alphanumeric characters and _-./ are allowed." + exit 1 + fi + # What we need to accomplish: # * update to the latest tag # * create a PR @@ -90,6 +107,7 @@ jobs: # We do different approach on subsequent runs because otherwise we would spam users' mailboxes # with notifications about pushes to existing PRs. This way there is actually no push if not needed. update: + needs: validate-inputs runs-on: ${{ inputs.runs-on }} # Map the job outputs to step outputs outputs: @@ -102,6 +120,12 @@ jobs: defaults: run: shell: pwsh + env: + DEPENDENCY_NAME: ${{ inputs.name }} + DEPENDENCY_PATH: ${{ inputs.path }} + DEPENDENCY_PATTERN: ${{ inputs.pattern }} + CHANGELOG_SECTION: ${{ inputs.changelog-section }} + PR_STRATEGY: ${{ inputs.pr-strategy }} steps: - uses: actions/checkout@v4 with: @@ -121,18 +145,18 @@ jobs: - name: Update to the latest version id: target - run: ${{ runner.temp }}/ghwf/updater/scripts/update-dependency.ps1 -Path '${{ inputs.path }}' -Pattern '${{ inputs.pattern }}' + run: ${{ runner.temp }}/ghwf/updater/scripts/update-dependency.ps1 -Path "$env:DEPENDENCY_PATH" -Pattern "$env:DEPENDENCY_PATTERN" - name: Get the base repo info if: steps.target.outputs.latestTag != steps.target.outputs.originalTag id: root run: | $mainBranch = $(git remote show origin | Select-String "HEAD branch: (.*)").Matches[0].Groups[1].Value - $prBranch = switch ('${{ inputs.pr-strategy }}') + $prBranch = switch ($env:PR_STRATEGY) { - 'create' { 'deps/${{ inputs.path }}/${{ steps.target.outputs.latestTag }}' } - 'update' { 'deps/${{ inputs.path }}' } - default { throw "Unkown PR strategy '${{ inputs.pr-strategy }}'." } + 'create' { "deps/$env:DEPENDENCY_PATH/${{ steps.target.outputs.latestTag }}" } + 'update' { "deps/$env:DEPENDENCY_PATH" } + default { throw "Unkown PR strategy '$env:PR_STRATEGY'." } } "baseBranch=$mainBranch" | Tee-Object $env:GITHUB_OUTPUT -Append "prBranch=$prBranch" | Tee-Object $env:GITHUB_OUTPUT -Append @@ -185,11 +209,11 @@ jobs: with: base: ${{ steps.root.outputs.baseBranch }} branch: ${{ steps.root.outputs.prBranch }} - commit-message: 'chore: update ${{ inputs.path }} to ${{ steps.target.outputs.latestTag }}' + commit-message: 'chore: update ${{ env.DEPENDENCY_PATH }} to ${{ steps.target.outputs.latestTag }}' author: 'GitHub ' - title: 'chore(deps): update ${{ inputs.name }} to ${{ steps.target.outputs.latestTagNice }}' + title: 'chore(deps): update ${{ env.DEPENDENCY_NAME }} to ${{ steps.target.outputs.latestTagNice }}' body: | - Bumps ${{ inputs.path }} from ${{ steps.target.outputs.originalTag }} to ${{ steps.target.outputs.latestTag }}. + Bumps ${{ env.DEPENDENCY_PATH }} from ${{ steps.target.outputs.originalTag }} to ${{ steps.target.outputs.latestTag }}. Auto-generated by a [dependency updater](https://github.com/getsentry/github-workflows/blob/main/.github/workflows/updater.yml). ${{ env.TARGET_CHANGELOG }} @@ -223,19 +247,19 @@ jobs: - name: 'After new PR: redo the update' if: ${{ ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.existing-pr.outputs.url == '') && ( steps.root.outputs.changed == 'false') }} - run: ${{ runner.temp }}/ghwf/updater/scripts/update-dependency.ps1 -Path '${{ inputs.path }}' -Tag '${{ steps.target.outputs.latestTag }}' + run: ${{ runner.temp }}/ghwf/updater/scripts/update-dependency.ps1 -Path "$env:DEPENDENCY_PATH" -Tag '${{ steps.target.outputs.latestTag }}' - name: Update Changelog if: ${{ inputs.changelog-entry && ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.root.outputs.changed == 'false') }} run: | ${{ runner.temp }}/ghwf/updater/scripts/update-changelog.ps1 ` - -Name '${{ inputs.name }}' ` + -Name "$env:DEPENDENCY_NAME" ` -PR '${{ steps.pr.outputs.url }}' ` -RepoUrl '${{ steps.target.outputs.url }}' ` -MainBranch '${{ steps.target.outputs.mainBranch }}' ` -OldTag '${{ steps.target.outputs.originalTag }}' ` -NewTag '${{ steps.target.outputs.latestTag }}' ` - -Section '${{ inputs.changelog-section }}' + -Section "$env:CHANGELOG_SECTION" - run: git --no-pager diff if: ${{ ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.root.outputs.changed == 'false') }} @@ -247,11 +271,11 @@ jobs: with: base: ${{ steps.root.outputs.baseBranch }} branch: ${{ steps.root.outputs.prBranch }} - commit-message: 'chore: update ${{ inputs.path }} to ${{ steps.target.outputs.latestTag }}' + commit-message: 'chore: update ${{ env.DEPENDENCY_PATH }} to ${{ steps.target.outputs.latestTag }}' author: 'GitHub ' - title: 'chore(deps): update ${{ inputs.name }} to ${{ steps.target.outputs.latestTagNice }}' + title: 'chore(deps): update ${{ env.DEPENDENCY_NAME }} to ${{ steps.target.outputs.latestTagNice }}' body: | - Bumps ${{ inputs.path }} from ${{ steps.target.outputs.originalTag }} to ${{ steps.target.outputs.latestTag }}. + Bumps ${{ env.DEPENDENCY_PATH }} from ${{ steps.target.outputs.originalTag }} to ${{ steps.target.outputs.latestTag }}. Auto-generated by a [dependency updater](https://github.com/getsentry/github-workflows/blob/main/.github/workflows/updater.yml). ${{ env.TARGET_CHANGELOG }} From 3841d242c149b7fd8d07f934d4d282b37f28d38b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Aug 2025 12:44:02 +0200 Subject: [PATCH 02/11] refactor: Split input validation into separate steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the single validation step into two distinct steps for better clarity and more granular error reporting: - Validate dependency name - Validate dependency path Each step now also logs a success message when validation passes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/updater.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index ba28d988..44d3e839 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -75,19 +75,23 @@ jobs: validate-inputs: runs-on: ubuntu-latest steps: - - name: Validate inputs + - name: Validate dependency name run: | # Validate that inputs.name contains only safe characters if [[ ! "${{ inputs.name }}" =~ ^[a-zA-Z0-9_\-\./@ ]+$ ]]; then echo "::error::Invalid dependency name: '${{ inputs.name }}'. Only alphanumeric characters, spaces, and _-./@ are allowed." exit 1 fi - + echo "✓ Dependency name '${{ inputs.name }}' is valid" + + - name: Validate dependency path + run: | # Validate that inputs.path contains only safe characters if [[ ! "${{ inputs.path }}" =~ ^[a-zA-Z0-9_\-\./]+$ ]]; then echo "::error::Invalid dependency path: '${{ inputs.path }}'. Only alphanumeric characters and _-./ are allowed." exit 1 fi + echo "✓ Dependency path '${{ inputs.path }}' is valid" # What we need to accomplish: # * update to the latest tag From 71bc194fc4e20f0c5c42053a0fe9c5a918e71139 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Aug 2025 12:57:30 +0200 Subject: [PATCH 03/11] fix: Use [:space:] character class for spaces in regex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the regex pattern to properly match spaces in dependency names by using the [:space:] POSIX character class instead of a literal space in the regex pattern. This fixes CI failures for test cases that include spaces in the dependency name like "Workflow args test script". 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/updater.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 44d3e839..ef81cb15 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -78,7 +78,7 @@ jobs: - name: Validate dependency name run: | # Validate that inputs.name contains only safe characters - if [[ ! "${{ inputs.name }}" =~ ^[a-zA-Z0-9_\-\./@ ]+$ ]]; then + if [[ ! "${{ inputs.name }}" =~ ^[a-zA-Z0-9_\-\./@[:space:]]+$ ]]; then echo "::error::Invalid dependency name: '${{ inputs.name }}'. Only alphanumeric characters, spaces, and _-./@ are allowed." exit 1 fi From 4526fc82c29bda090d7a0e8eac3586c46a385a8b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Aug 2025 13:57:39 +0200 Subject: [PATCH 04/11] fix: Correct regex character class syntax for hyphens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move hyphens to the end of character classes in regex patterns to ensure they are treated as literal characters rather than ranges. This fixes validation failures for inputs containing hyphens like "WORKFLOW-TEST-DEPENDENCY-DO-NOT-MERGE". 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/updater.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index ef81cb15..6eaebabe 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -78,7 +78,7 @@ jobs: - name: Validate dependency name run: | # Validate that inputs.name contains only safe characters - if [[ ! "${{ inputs.name }}" =~ ^[a-zA-Z0-9_\-\./@[:space:]]+$ ]]; then + if [[ ! "${{ inputs.name }}" =~ ^[a-zA-Z0-9_\./@[:space:]-]+$ ]]; then echo "::error::Invalid dependency name: '${{ inputs.name }}'. Only alphanumeric characters, spaces, and _-./@ are allowed." exit 1 fi @@ -87,7 +87,7 @@ jobs: - name: Validate dependency path run: | # Validate that inputs.path contains only safe characters - if [[ ! "${{ inputs.path }}" =~ ^[a-zA-Z0-9_\-\./]+$ ]]; then + if [[ ! "${{ inputs.path }}" =~ ^[a-zA-Z0-9_\./-]+$ ]]; then echo "::error::Invalid dependency path: '${{ inputs.path }}'. Only alphanumeric characters and _-./ are allowed." exit 1 fi From 15af1183eb85b364d5925cff60e88fc7890ee165 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 15 Aug 2025 06:58:01 +0200 Subject: [PATCH 05/11] refactor: Use PowerShell for input validation steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the validation steps from Bash to PowerShell for consistency with the rest of the workflow which uses PowerShell as its default shell. - Use PowerShell's -notmatch operator instead of Bash regex - Use Write-Output instead of echo - Maintain the same validation logic and error messages 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/updater.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 6eaebabe..d7a13571 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -76,22 +76,24 @@ jobs: runs-on: ubuntu-latest steps: - name: Validate dependency name + shell: pwsh run: | # Validate that inputs.name contains only safe characters - if [[ ! "${{ inputs.name }}" =~ ^[a-zA-Z0-9_\./@[:space:]-]+$ ]]; then - echo "::error::Invalid dependency name: '${{ inputs.name }}'. Only alphanumeric characters, spaces, and _-./@ are allowed." + if ('${{ inputs.name }}' -notmatch '^[a-zA-Z0-9_\./@\s-]+$') { + Write-Output "::error::Invalid dependency name: '${{ inputs.name }}'. Only alphanumeric characters, spaces, and _-./@ are allowed." exit 1 - fi - echo "✓ Dependency name '${{ inputs.name }}' is valid" + } + Write-Output "✓ Dependency name '${{ inputs.name }}' is valid" - name: Validate dependency path + shell: pwsh run: | # Validate that inputs.path contains only safe characters - if [[ ! "${{ inputs.path }}" =~ ^[a-zA-Z0-9_\./-]+$ ]]; then - echo "::error::Invalid dependency path: '${{ inputs.path }}'. Only alphanumeric characters and _-./ are allowed." + if ('${{ inputs.path }}' -notmatch '^[a-zA-Z0-9_\./-]+$') { + Write-Output "::error::Invalid dependency path: '${{ inputs.path }}'. Only alphanumeric characters and _-./ are allowed." exit 1 - fi - echo "✓ Dependency path '${{ inputs.path }}' is valid" + } + Write-Output "✓ Dependency path '${{ inputs.path }}' is valid" # What we need to accomplish: # * update to the latest tag From fff8b7b823a3ba00ddc85ee5732209350b44653f Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 15 Aug 2025 07:00:15 +0200 Subject: [PATCH 06/11] docs: Add changelog entry for script injection security fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add entry to CHANGELOG.md documenting the security improvements to prevent script injection vulnerabilities in the updater workflow. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b470d77..83ed5ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Security + +- Updater - Prevent script injection vulnerabilities through workflow inputs ([#98](https://github.com/getsentry/github-workflows/pull/98)) + ## 2.13.1 ### Fixes From 9af03f8930734c0fa6e19e888d44ea961437a8a9 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:10:46 +0200 Subject: [PATCH 07/11] Apply suggestion from @jpnurmi Co-authored-by: J-P Nurmi --- .github/workflows/updater.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index d7a13571..1f2fa25e 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -84,7 +84,6 @@ jobs: exit 1 } Write-Output "✓ Dependency name '${{ inputs.name }}' is valid" - - name: Validate dependency path shell: pwsh run: | From 7bb1cd73e5b231480b56feea5f007ced1c415e3d Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:40:15 +0200 Subject: [PATCH 08/11] Apply suggestion from @vaind --- .github/workflows/updater.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 1f2fa25e..039123f0 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -150,7 +150,7 @@ jobs: - name: Update to the latest version id: target - run: ${{ runner.temp }}/ghwf/updater/scripts/update-dependency.ps1 -Path "$env:DEPENDENCY_PATH" -Pattern "$env:DEPENDENCY_PATTERN" + run: ${{ runner.temp }}/ghwf/updater/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Pattern $env:DEPENDENCY_PATTERN - name: Get the base repo info if: steps.target.outputs.latestTag != steps.target.outputs.originalTag From b67288f23338c88bea016e5ae362c508096ca16f Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:40:22 +0200 Subject: [PATCH 09/11] Apply suggestion from @vaind --- .github/workflows/updater.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 039123f0..4facae33 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -258,7 +258,7 @@ jobs: if: ${{ inputs.changelog-entry && ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.root.outputs.changed == 'false') }} run: | ${{ runner.temp }}/ghwf/updater/scripts/update-changelog.ps1 ` - -Name "$env:DEPENDENCY_NAME" ` + -Name $env:DEPENDENCY_NAME ` -PR '${{ steps.pr.outputs.url }}' ` -RepoUrl '${{ steps.target.outputs.url }}' ` -MainBranch '${{ steps.target.outputs.mainBranch }}' ` From 2feccc3944650b859c1b714ed6ace67cbaa33b98 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:40:29 +0200 Subject: [PATCH 10/11] Apply suggestion from @vaind --- .github/workflows/updater.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 4facae33..8eb5f737 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -264,7 +264,7 @@ jobs: -MainBranch '${{ steps.target.outputs.mainBranch }}' ` -OldTag '${{ steps.target.outputs.originalTag }}' ` -NewTag '${{ steps.target.outputs.latestTag }}' ` - -Section "$env:CHANGELOG_SECTION" + -Section $env:CHANGELOG_SECTION - run: git --no-pager diff if: ${{ ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.root.outputs.changed == 'false') }} From 97fbb71cad4a778942ab5d755bc73c710b28a33f Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:40:38 +0200 Subject: [PATCH 11/11] Apply suggestion from @vaind --- .github/workflows/updater.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 8eb5f737..b56434da 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -252,7 +252,7 @@ jobs: - name: 'After new PR: redo the update' if: ${{ ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.existing-pr.outputs.url == '') && ( steps.root.outputs.changed == 'false') }} - run: ${{ runner.temp }}/ghwf/updater/scripts/update-dependency.ps1 -Path "$env:DEPENDENCY_PATH" -Tag '${{ steps.target.outputs.latestTag }}' + run: ${{ runner.temp }}/ghwf/updater/scripts/update-dependency.ps1 -Path $env:DEPENDENCY_PATH -Tag '${{ steps.target.outputs.latestTag }}' - name: Update Changelog if: ${{ inputs.changelog-entry && ( steps.target.outputs.latestTag != steps.target.outputs.originalTag ) && ( steps.root.outputs.changed == 'false') }}