CVE-2023-51664 (TypeScript, published 2023-12-27T17:15Z)
Description: value: tj-actions/changed-files is a Github action to retrieve all files and directories. Prior to 41.0.0, the `tj-actions/changed-files` workflow allows for command injection in changed filenames, allowing an attacker to execute arbitrary code and potentially leak secrets. This issue may lead to arbitrary command execution in the GitHub Runner. This vulnerability has been addressed in version 41.0.0. Users are advised to upgrade.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-77 \u2014 Improper Neutralization of Special Elements used in a Command ('Command Injection')
The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
Repository: https://github.com/tj-actions/changed-files
Files changed:
README.md
action.yml
src/changedFilesOutput.ts
src/inputs.ts
src/main.ts
src/utils.ts
Commit message: Merge pull request from GHSA-mcph-m25j-8j63

* feat: add `safe_output` input enabled by default

* fix: migrate README to safe uses of interpolation

* fix: README `uses` typo

* fix: README examples to account for newlines

* fix: README examples missing `safe_output`

* fix: remove sanitization of `'`

* fix: also sanitize `|&;`

Vulnerable code:
          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
          for file in ${{ steps.changed-markdown-files.outputs.all_changed_files }}; do
          echo "List all the files that have changed: ${{ steps.changed-files-yaml.outputs.test_all_changed_files }}"
          echo "List all the files that have changed: ${{ steps.changed-files-yaml.outputs.doc_all_changed_files }}"
          echo "List all the files that have changed: ${{ steps.changed-files-specific.outputs.all_changed_files }}"
          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
        for file in ${{ steps.changed-files.outputs.added_files }}; do
     uses: ./
     uses: ./
        for file in ${{ steps.changed-files-specific.outputs.deleted_files }}; do
        for file in ${{ steps.changed-files-specific.outputs.deleted_files }}; do
          echo "List all the files that have changed: ${{ steps.changed-files-specific.outputs.all_changed_files }}"
        for file in ${{ steps.changed-files-for-dir1.outputs.added_files }}; do
      uses: ./
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
    shouldEscape: inputs.escapeJson
      json: inputs.json
    shouldEscape: inputs.escapeJson
  shouldEscape = false

Fixed code:
        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.
        env:
          ALL_CHANGED_FILES: |-
            ${{ steps.changed-files.outputs.all_changed_files }}
          for file in "$ALL_CHANGED_FILES"; do
          safe_output: false # true by default, set to false because we are using an environment variable to store the output and avoid command injection.
        env:
          ALL_CHANGED_FILES: |-
            ${{ steps.changed-markdown-files.outputs.all_changed_files }}
          for file in "$ALL_CHANGED_FILES"; do
          safe_output: false # true by default, set to false because we are using an environment variable to store the output and avoid command injection.
        env:
          TEST_ALL_CHANGED_FILES: |-
            ${{ steps.changed-files-yaml.outputs.test_all_changed_files }}
          echo "List all the files that have changed: $TEST_ALL_CHANGED_FILES"
        env:
          DOC_ALL_CHANGED_FILES: |-
            ${{ steps.changed-files-yaml.outputs.doc_all_changed_files }}
          echo "List all the files that have changed: $DOC_ALL_CHANGED_FILES"
          safe_output: false # true by default, set to false because we are using an environment variable to store the output and avoid command injection.
        env:
          ALL_CHANGED_FILES: |-
            ${{ steps.changed-files-specific.outputs.all_changed_files }}
          echo "List all the files that have changed: $ALL_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.
        env:
          ALL_CHANGED_FILES: |-
            ${{ steps.changed-files.outputs.all_changed_files }}
          for file in "$ALL_CHANGED_FILES"; do
        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.
        env:
          ALL_CHANGED_FILES: |-
            ${{ steps.changed-files.outputs.all_changed_files }}
          for file in "$ALL_CHANGED_FILES"; do
      with:
        safe_output: false
      env:
        ADDED_FILES: |-
          ${{ steps.changed-files.outputs.added_files }}
        for file in "$ADDED_FILES"; do
      with:
        safe_output: false
     uses: tj-actions/changed-files@v40
       safe_output: false
     uses: tj-actions/changed-files@v40
       safe_output: false
        safe_output: false
      env:
        DELETED_FILES: |-
          ${{ steps.changed-files-specific.outputs.deleted_files }}
        for file in "$DELETED_FILES"; do
      env:
        DELETED_FILES: |-
          ${{ steps.changed-files-specific.outputs.deleted_files }}
        for file in "$DELETED_FILES"; do
          safe_output: false
        env:
          ALL_CHANGED_FILES: |-
            ${{ steps.changed-files-specific.outputs.all_changed_files }}
          echo "List all the files that have changed: $ALL_CHANGED_FILES"
        safe_output: false
      env:
        ADDED_FILES: |-
          ${{ steps.changed-files-for-dir1.outputs.added_files }}
        for file in "$ADDED_FILES"; do
      uses: tj-actions/changed-files@v40
  safe_output:
    description: "Apply sanitization to output filenames before being set as output."
    required: false
    default: "true"
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
  safeOutput: boolean
  const safeOutput = core.getBooleanInput('safe_output', {required: false})
    safeOutput,
      json: inputs.json,
      safeOutput: inputs.safeOutput
    shouldEscape: inputs.escapeJson,
    safeOutput: inputs.safeOutput
  shouldEscape = false,
  safeOutput = false
  safeOutput?: boolean
  // if safeOutput is true, escape special characters for bash shell
  if (safeOutput) {
    cleanedValue = cleanedValue.replace(/[$()`|&;]/g, '\\$&')
  }