GitHub Action tj-actions/verify-changed-files is vulnerable to command injection in output filenames
Description
Command injection in tj-actions/verify-changed-files due to unsanitized filenames can allow arbitrary code execution on GitHub runners.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Command injection in tj-actions/verify-changed-files due to unsanitized filenames can allow arbitrary code execution on GitHub runners.
The tj-actions/verify-changed-files GitHub Action lists changed files during a workflow execution. The vulnerability arises because filenames containing special characters (e.g., ;) are returned as-is in the changed_files output. If a workflow uses this output directly in a run block without proper escaping, an attacker can inject arbitrary commands [1].
An attacker can create a pull request with a file that has a malicious name, such as ; echo "compromised"; .txt. When the action runs, the output includes this filename. If the workflow then executes echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}" without using an environment variable, the injected command will execute on the GitHub runner [2]. The attack requires that the workflow triggers on events like pull_request and that the output is used unsafely.
Successful exploitation allows an attacker to run arbitrary commands with the permissions of the runner, potentially stealing secrets such as GITHUB_TOKEN or other environment variables. This could lead to compromise of the entire repository or other resources accessible to the token [1].
The vulnerability is patched in versions v17 and v17.0.0 by enabling safe_output by default, which escapes special characters for bash environments. The patch also adds escaping logic for characters like $, (, ), ` `, |, &, and ;` [3][4]. Users should update to the latest version and follow best practices by using environment variables to capture the output instead of direct substitution.
- NVD - CVE-2023-52137
- GitHub - tj-actions/verify-changed-files: :octocat: Github action to verify file changes that occur during the workflow execution.
- Merge pull request from GHSA-ghm2-rq8q-wrhc · tj-actions/verify-changed-files@498d3f3
- chore: update entrypoint.sh (#357) · tj-actions/verify-changed-files@592e305
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
tj-actions/verify-changed-filesGitHub Actions | < 17 | 17 |
Affected products
2- Range: < 17.0.0
Patches
2592e305da041chore: update entrypoint.sh (#357)
1 file changed · +15 −17
entrypoint.sh+15 −17 modified@@ -39,17 +39,26 @@ fi CHANGED_FILES="" -# Function to concatenate non-empty strings with a separator +# Function to concatenate non-empty file names with a separator concatenate() { local separator=$1 shift local result="" - for str in "$@"; do - if [[ -n $str ]]; then + for filename in "$@"; do + if [[ "$INPUT_SAFE_OUTPUT" == "true" ]]; then + filename=${filename//$/\\$} # Replace $ with \$ + filename=${filename//\(/\\\(} # Replace ( with \( + filename=${filename//\)/\\\)} # Replace ) with \) + filename=${filename//\`/\\\`} # Replace ` with \` + filename=${filename//|/\\|} # Replace | with \| + filename=${filename//&/\\&} # Replace & with \& + filename=${filename//;/\\;} # Replace ; with \; + fi + if [[ -n $filename ]]; then if [[ -n $result ]]; then - result+="$separator$str" + result+="$separator$filename" else - result="$str" + result="$filename" fi fi done @@ -62,20 +71,10 @@ CHANGED_FILES=$(concatenate "|" "$TRACKED_FILES" "$UNTRACKED_OR_IGNORED_FILES" " CHANGED_FILES=$(echo "$CHANGED_FILES" | awk '{gsub(/\|/,"\n"); print $0;}' | sort -u | awk -v d="|" '{s=(NR==1?s:s d)$0}END{print s}') if [[ -n "$CHANGED_FILES" ]]; then - echo "Found uncommited changes" + echo "Found uncommitted changes" CHANGED_FILES=$(echo "$CHANGED_FILES" | awk '{gsub(/\|/,"\n"); print $0;}' | awk -v d="$INPUT_SEPARATOR" '{s=(NR==1?s:s d)$0}END{print s}') - if [[ "$INPUT_SAFE_OUTPUT" == "true" ]]; then - CHANGED_FILES=${CHANGED_FILES//$/\\$} # Replace $ with \$ - CHANGED_FILES=${CHANGED_FILES//\(/\\\(}} # Replace ( with \( - CHANGED_FILES=${CHANGED_FILES//\)/\\\)}} # Replace ) with \) - CHANGED_FILES=${CHANGED_FILES//\`/\\\`} # Replace ` with \` - CHANGED_FILES=${CHANGED_FILES//|/\\|} # Replace | with \| - CHANGED_FILES=${CHANGED_FILES//&/\\&} # Replace & with \& - CHANGED_FILES=${CHANGED_FILES//;/\\;} # Replace ; with \; - fi - echo "files_changed=true" >> "$GITHUB_OUTPUT" echo "changed_files=$CHANGED_FILES" >> "$GITHUB_OUTPUT" @@ -85,7 +84,6 @@ if [[ -n "$CHANGED_FILES" ]]; then fi exit 1 fi - else echo "No changes found." echo "files_changed=false" >> "$GITHUB_OUTPUT"
498d3f316f50Merge pull request from GHSA-ghm2-rq8q-wrhc
3 files changed · +27 −2
action.yml+5 −0 modified@@ -25,6 +25,10 @@ inputs: description: 'Message to display when files have changed and the `fail-if-changed` input is set to `true`.' default: "Files have changed." required: false + safe_output: + description: "Apply sanitization to output filenames before being set as output." + required: false + default: "true" outputs: files_changed: @@ -61,6 +65,7 @@ runs: INPUT_MATCH_GITIGNORE_FILES: ${{ inputs.match-gitignore-files }} INPUT_FAIL_IF_CHANGED: ${{ inputs.fail-if-changed }} INPUT_FAIL_MSG: ${{ inputs.fail-message }} + INPUT_SAFE_OUTPUT: ${{ inputs.safe_output }} branding: icon: file-text
entrypoint.sh+10 −0 modified@@ -66,6 +66,16 @@ if [[ -n "$CHANGED_FILES" ]]; then CHANGED_FILES=$(echo "$CHANGED_FILES" | awk '{gsub(/\|/,"\n"); print $0;}' | awk -v d="$INPUT_SEPARATOR" '{s=(NR==1?s:s d)$0}END{print s}') + if [[ "$INPUT_SAFE_OUTPUT" == "true" ]]; then + CHANGED_FILES=${CHANGED_FILES//$/\\$} # Replace $ with \$ + CHANGED_FILES=${CHANGED_FILES//\(/\\\(}} # Replace ( with \( + CHANGED_FILES=${CHANGED_FILES//\)/\\\)}} # Replace ) with \) + CHANGED_FILES=${CHANGED_FILES//\`/\\\`} # Replace ` with \` + CHANGED_FILES=${CHANGED_FILES//|/\\|} # Replace | with \| + CHANGED_FILES=${CHANGED_FILES//&/\\&} # Replace & with \& + CHANGED_FILES=${CHANGED_FILES//;/\\;} # Replace ; with \; + fi + echo "files_changed=true" >> "$GITHUB_OUTPUT" echo "changed_files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
README.md+12 −2 modified@@ -60,6 +60,7 @@ Verify that certain files or directories did or did not change during the workfl uses: tj-actions/verify-changed-files@v16 id: verify-changed-files with: + safe_output: false # true by default, set to false because we are using an environment variable to store the output and avoid command injection. files: | *.txt test_directory @@ -69,8 +70,11 @@ Verify that certain files or directories did or did not change during the workfl - name: Run step only when any of the above files change. if: steps.verify-changed-files.outputs.files_changed == 'true' + env: + FILES_CHANGED: |- + ${{ steps.verify-changed-files.outputs.changed_files }} run: | - echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}" + echo "Changed files: $FILES_CHANGED" # Outputs: "Changed files: new.txt test_directory/new.txt" ``` @@ -82,6 +86,7 @@ Verify that certain files or directories did or did not change during the workfl uses: tj-actions/verify-changed-files@v16 id: verify-changed-files with: + safe_output: false files: | new.txt test_directory @@ -99,10 +104,15 @@ Verify that certain files or directories did or did not change during the workfl - name: Verify Changed files uses: tj-actions/verify-changed-files@v16 id: verify-changed-files + with: + safe_output: false - name: List all changed files tracked and untracked files + env: + FILES_CHANGED: |- + ${{ steps.verify-changed-files.outputs.changed_files }} run: | - echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}" + echo "Changed files: $FILES_CHANGED" ``` If you feel generous and want to show some extra appreciation:
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-ghm2-rq8q-wrhcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-52137ghsaADVISORY
- github.com/tj-actions/verify-changed-files/commit/498d3f316f501aa72485060e8c96fde7b2014f12ghsax_refsource_MISCWEB
- github.com/tj-actions/verify-changed-files/commit/592e305da041c09a009afa4a43c97d889bed65c3ghsax_refsource_MISCWEB
- github.com/tj-actions/verify-changed-files/security/advisories/GHSA-ghm2-rq8q-wrhcghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.