-
-
Notifications
You must be signed in to change notification settings - Fork 7
fix: complete script injection hardening across all actions #152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -77,47 +77,47 @@ runs: | |||||
| DEPENDENCY_NAME: ${{ inputs.name }} | ||||||
| run: | | ||||||
| # Validate that inputs.name contains only safe characters | ||||||
| if ("$env:DEPENDENCY_NAME" -notmatch '^[a-zA-Z0-9_\./@\s-]+$') { | ||||||
| Write-Output "::error::Invalid dependency name: '$env:DEPENDENCY_NAME'. Only alphanumeric characters, spaces, and _-./@ are allowed." | ||||||
| if ($env:DEPENDENCY_NAME -notmatch '^[a-zA-Z0-9_\./@\s-]+$') { | ||||||
| Write-Output ('::error::Invalid dependency name: "' + $env:DEPENDENCY_NAME + '". Only alphanumeric characters, spaces, and _-./@ are allowed.') | ||||||
| exit 1 | ||||||
| } | ||||||
| Write-Output "✓ Dependency name '$env:DEPENDENCY_NAME' is valid" | ||||||
| Write-Output ('Dependency name "' + $env:DEPENDENCY_NAME + '" is valid') | ||||||
|
|
||||||
| - name: Validate dependency path | ||||||
| shell: pwsh | ||||||
| env: | ||||||
| DEPENDENCY_PATH: ${{ inputs.path }} | ||||||
| run: | | ||||||
| # Validate that inputs.path contains only safe characters (including # for CMake dependencies) | ||||||
| if ("$env:DEPENDENCY_PATH" -notmatch '^[a-zA-Z0-9_\./#-]+$') { | ||||||
| Write-Output "::error::Invalid dependency path: '$env:DEPENDENCY_PATH'. Only alphanumeric characters and _-./# are allowed." | ||||||
| if ($env:DEPENDENCY_PATH -notmatch '^[a-zA-Z0-9_\./#-]+$') { | ||||||
| Write-Output ('::error::Invalid dependency path: "' + $env:DEPENDENCY_PATH + '". Only alphanumeric characters and _-./# are allowed.') | ||||||
| exit 1 | ||||||
| } | ||||||
| Write-Output "✓ Dependency path '$env:DEPENDENCY_PATH' is valid" | ||||||
| Write-Output ('Dependency path "' + $env:DEPENDENCY_PATH + '" is valid') | ||||||
|
|
||||||
| - name: Validate changelog-entry | ||||||
| shell: pwsh | ||||||
| env: | ||||||
| CHANGELOG_ENTRY: ${{ inputs.changelog-entry }} | ||||||
| run: | | ||||||
| # Validate that inputs.changelog-entry is either 'true' or 'false' | ||||||
| if ("$env:CHANGELOG_ENTRY" -notin @('true', 'false')) { | ||||||
| Write-Output "::error::Invalid changelog-entry value: '$env:CHANGELOG_ENTRY'. Only 'true' or 'false' are allowed." | ||||||
| if ($env:CHANGELOG_ENTRY -notin @('true', 'false')) { | ||||||
| Write-Output ('::error::Invalid changelog-entry value: "' + $env:CHANGELOG_ENTRY + '". Only "true" or "false" are allowed.') | ||||||
| exit 1 | ||||||
| } | ||||||
| Write-Output "✓ Changelog-entry value '$env:CHANGELOG_ENTRY' is valid" | ||||||
| Write-Output ('Changelog-entry value "' + $env:CHANGELOG_ENTRY + '" is valid') | ||||||
|
|
||||||
| - name: Validate pr-strategy | ||||||
| shell: pwsh | ||||||
| env: | ||||||
| PR_STRATEGY: ${{ inputs.pr-strategy }} | ||||||
| run: | | ||||||
| # Validate that inputs.pr-strategy is either 'create' or 'update' | ||||||
| if ("$env:PR_STRATEGY" -notin @('create', 'update')) { | ||||||
| Write-Output "::error::Invalid pr-strategy value: '$env:PR_STRATEGY'. Only 'create' or 'update' are allowed." | ||||||
| if ($env:PR_STRATEGY -notin @('create', 'update')) { | ||||||
| Write-Output ('::error::Invalid pr-strategy value: "' + $env:PR_STRATEGY + '". Only "create" or "update" are allowed.') | ||||||
| exit 1 | ||||||
| } | ||||||
| Write-Output "✓ PR strategy value '$env:PR_STRATEGY' is valid" | ||||||
| Write-Output ('PR strategy value "' + $env:PR_STRATEGY + '" is valid') | ||||||
|
|
||||||
| - name: Validate post-update-script | ||||||
| if: ${{ inputs.post-update-script != '' }} | ||||||
|
|
@@ -126,11 +126,11 @@ runs: | |||||
| POST_UPDATE_SCRIPT: ${{ inputs.post-update-script }} | ||||||
| run: | | ||||||
| # Validate that inputs.post-update-script contains only safe characters | ||||||
| if ("$env:POST_UPDATE_SCRIPT" -notmatch '^[a-zA-Z0-9_\./#\s-]+$') { | ||||||
| Write-Output "::error::Invalid post-update-script path: '$env:POST_UPDATE_SCRIPT'. Only alphanumeric characters, spaces, and _-./# are allowed." | ||||||
| if ($env:POST_UPDATE_SCRIPT -notmatch '^[a-zA-Z0-9_\./#\s-]+$') { | ||||||
| Write-Output ('::error::Invalid post-update-script path: "' + $env:POST_UPDATE_SCRIPT + '". Only alphanumeric characters, spaces, and _-./# are allowed.') | ||||||
| exit 1 | ||||||
| } | ||||||
| Write-Output "✓ Post-update script path '$env:POST_UPDATE_SCRIPT' is valid" | ||||||
| Write-Output ('Post-update script path "' + $env:POST_UPDATE_SCRIPT + '" is valid') | ||||||
|
|
||||||
| - name: Validate authentication inputs | ||||||
| shell: pwsh | ||||||
|
|
@@ -288,30 +288,31 @@ runs: | |||||
| PR_STRATEGY: ${{ inputs.pr-strategy }} | ||||||
| DEPENDENCY_PATH: ${{ inputs.path }} | ||||||
| TARGET_BRANCH: ${{ inputs.target-branch }} | ||||||
| LATEST_TAG: ${{ steps.target.outputs.latestTag }} | ||||||
| run: | | ||||||
| if ([string]::IsNullOrEmpty($env:TARGET_BRANCH)) { | ||||||
| $mainBranch = $(git remote show origin | Select-String "HEAD branch: (.*)").Matches[0].Groups[1].Value | ||||||
| $prBranchPrefix = '' | ||||||
| } else { | ||||||
| $mainBranch = $env:TARGET_BRANCH | ||||||
| $prBranchPrefix = "$mainBranch-" | ||||||
| $prBranchPrefix = $mainBranch + '-' | ||||||
| } | ||||||
| $prBranch = switch ($env:PR_STRATEGY) | ||||||
| { | ||||||
| 'create' { "deps/$env:DEPENDENCY_PATH/${{ steps.target.outputs.latestTag }}" } | ||||||
| 'update' { "deps/$env:DEPENDENCY_PATH" } | ||||||
| default { throw "Unkown PR strategy '$env:PR_STRATEGY'." } | ||||||
| 'create' { 'deps/' + $env:DEPENDENCY_PATH + '/' + $env:LATEST_TAG } | ||||||
| 'update' { 'deps/' + $env:DEPENDENCY_PATH } | ||||||
| default { throw ('Unknown PR strategy "' + $env:PR_STRATEGY + '".') } | ||||||
| } | ||||||
| $prBranch = $prBranchPrefix + $prBranch | ||||||
| "baseBranch=$mainBranch" | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| "prBranch=$prBranch" | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| ('baseBranch=' + $mainBranch) | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| ('prBranch=' + $prBranch) | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| $nonBotCommits = ${{ github.action_path }}/scripts/nonbot-commits.ps1 ` | ||||||
| -RepoUrl "$(git config --get remote.origin.url)" -PrBranch $prBranch -MainBranch $mainBranch | ||||||
| -RepoUrl $(git config --get remote.origin.url) -PrBranch $prBranch -MainBranch $mainBranch | ||||||
| $changed = $nonBotCommits.Length -gt 0 ? 'true' : 'false' | ||||||
| "changed=$changed" | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| if ("$changed" -eq "true") | ||||||
| ('changed=' + $changed) | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| if ($changed -eq 'true') | ||||||
| { | ||||||
| Write-Output "::warning::Target branch '$prBranch' has been changed manually - skipping updater to avoid overwriting these changes." | ||||||
| Write-Output ('::warning::Target branch "' + $prBranch + '" has been changed manually - skipping updater to avoid overwriting these changes.') | ||||||
| } | ||||||
|
|
||||||
| - name: Parse the existing PR URL | ||||||
|
|
@@ -321,19 +322,22 @@ runs: | |||||
| working-directory: caller-repo | ||||||
| env: | ||||||
| GH_TOKEN: ${{ inputs.api-token || github.token }} | ||||||
| BASE_BRANCH: ${{ steps.root.outputs.baseBranch }} | ||||||
| PR_BRANCH: ${{ steps.root.outputs.prBranch }} | ||||||
| run: | | ||||||
| $urls = @(gh api 'repos/${{ github.repository }}/pulls?base=${{ steps.root.outputs.baseBranch }}&head=${{ github.repository_owner }}:${{ steps.root.outputs.prBranch }}' --jq '.[].html_url') | ||||||
| $apiUrl = 'repos/${{ github.repository }}/pulls?base=' + $env:BASE_BRANCH + '&head=${{ github.repository_owner }}:' + $env:PR_BRANCH | ||||||
| $urls = @(gh api $apiUrl --jq '.[].html_url') | ||||||
| if ($urls.Length -eq 0) | ||||||
| { | ||||||
| "url=" | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| 'url=' | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| } | ||||||
| elseif ($urls.Length -eq 1) | ||||||
| { | ||||||
| "url=$($urls[0])" | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| ('url=' + $urls[0]) | Tee-Object $env:GITHUB_OUTPUT -Append | ||||||
| } | ||||||
| else | ||||||
| { | ||||||
| throw "Unexpected number of PRs matched ($($urls.Length)): $urls" | ||||||
| throw ('Unexpected number of PRs matched (' + $urls.Length + '): ' + $urls) | ||||||
|
||||||
| throw ('Unexpected number of PRs matched (' + $urls.Length + '): ' + $urls) | |
| throw ('Unexpected number of PRs matched (' + $urls.Length + '): ' + ($urls -join ', ')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GitHub API endpoint string is built by concatenating unencoded query parameter values ($env:BASE_BRANCH and $env:PR_BRANCH). Because DEPENDENCY_PATH validation allows characters like '#', the resulting branch name can contain '#', which will be treated as a URL fragment delimiter and truncate the request. Prefer passing query params via
gh api -f base=... -f head=...(lets gh handle encoding) or explicitly URL-encode these values before concatenation.