CVE-2023-51664: Improper Neutralization of Special Elements used in a Command ('Command Injection') (CWE-77)
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.
CWE description: 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.
CVSS v3 base score: 9.8
Published: 2023-12-27T17:15Z
Repository: https://github.com/tj-actions/changed-files
Language: TypeScript
Files changed: README.md, action.yml, src/changedFilesOutput.ts, src/inputs.ts, src/main.ts, src/utils.ts
Commit date: 2023-12-22 14:07:32 -0700
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 `|&;`
Diff: diff --git a/README.md b/README.md
index d32e01a..431aefb 100644
--- a/README.md
+++ b/README.md
@@ -121,42 +121,52 @@ jobs:
 
       # Example 1
       - name: Get changed files
         id: changed-files
         uses: tj-actions/changed-files@v40
+        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.
         
         # To compare changes between the current commit and the last pushed remote commit set `since_last_remote_commit: true`. e.g
         # with:
         #   since_last_remote_commit: true 
 
       - name: List all changed files
+        env:
+          ALL_CHANGED_FILES: |-
+            ${{ steps.changed-files.outputs.all_changed_files }}
         run: |
-          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
+          for file in "$ALL_CHANGED_FILES"; do
             echo "$file was changed"
           done
 
       # Example 2
       - name: Get all changed markdown files
         id: changed-markdown-files
         uses: tj-actions/changed-files@v40
         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.
           # Avoid using single or double quotes for multiline patterns
           files: |
              **.md
 
       - name: List all changed files markdown files
         if: steps.changed-markdown-files.outputs.any_changed == 'true'
+        env:
+          ALL_CHANGED_FILES: |-
+            ${{ steps.changed-markdown-files.outputs.all_changed_files }}
         run: |
-          for file in ${{ steps.changed-markdown-files.outputs.all_changed_files }}; do
+          for file in "$ALL_CHANGED_FILES"; do
             echo "$file was changed"
           done
 
       # Example 3
       - name: Get all test, doc and src files that have changed
         id: changed-files-yaml
         uses: tj-actions/changed-files@v40
         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_yaml: |
             doc:
               - '**.md'
               - docs/**
               - README.md
@@ -168,33 +178,43 @@ jobs:
           # Optionally set `files_yaml_from_source_file` to read the YAML from a file. e.g `files_yaml_from_source_file: .github/changed-files.yml`
 
       - name: Run step if test file(s) change
         # NOTE: Ensure all outputs are prefixed by the same key used above e.g. `test_(...)` | `doc_(...)` | `src_(...)` when trying to access the `any_changed` output.
         if: steps.changed-files-yaml.outputs.test_any_changed == 'true'  
+        env:
+          TEST_ALL_CHANGED_FILES: |-
+            ${{ steps.changed-files-yaml.outputs.test_all_changed_files }}
         run: |
           echo "One or more test file(s) has changed."
-          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: $TEST_ALL_CHANGED_FILES"
       
       - name: Run step if doc file(s) change
         if: steps.changed-files-yaml.outputs.doc_any_changed == 'true'
+        env:
+          DOC_ALL_CHANGED_FILES: |-
+            ${{ steps.changed-files-yaml.outputs.doc_all_changed_files }}
         run: |
           echo "One or more doc file(s) has changed."
-          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: $DOC_ALL_CHANGED_FILES"
 
       # Example 3
       - name: Get changed files in the docs folder
         id: changed-files-specific
         uses: tj-actions/changed-files@v40
         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: docs/*.{js,html}  # Alternatively using: `docs/**`
           files_ignore: docs/static.js
 
       - name: Run step if any file(s) in the docs folder change
         if: steps.changed-files-specific.outputs.any_changed == 'true'
+        env:
+          ALL_CHANGED_FILES: |-
+            ${{ steps.changed-files-specific.outputs.all_changed_files }}
         run: |
           echo "One or more files in the docs folder has changed."
-          echo "List all the files that have changed: ${{ steps.changed-files-specific.outputs.all_changed_files }}"
+          echo "List all the files that have changed: $ALL_CHANGED_FILES"
 ```
 
 #### Using Github's API :octocat:
 
 ```yaml
@@ -222,14 +242,19 @@ jobs:
 
     steps:
       - name: Get changed files
         id: changed-files
         uses: tj-actions/changed-files@v40
+        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.
 
       - name: List all changed files
+        env:
+          ALL_CHANGED_FILES: |-
+            ${{ steps.changed-files.outputs.all_changed_files }}
         run: |
-          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
+          for file in "$ALL_CHANGED_FILES"; do
             echo "$file was changed"
           done
 ```
 
 ### On [`push`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push) ⬆️
@@ -263,16 +288,21 @@ jobs:
           fetch-depth: 0  # OR "2" -> To retrieve the preceding commit.
 
       - name: Get changed files
         id: changed-files
         uses: tj-actions/changed-files@v40
+        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.
 
       # NOTE: `since_last_remote_commit: true` is implied by default and falls back to the previous local commit.
 
       - name: List all changed files
+        env:
+          ALL_CHANGED_FILES: |-
+            ${{ steps.changed-files.outputs.all_changed_files }}
         run: |
-          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
+          for file in "$ALL_CHANGED_FILES"; do
             echo "$file was changed"
           done
       ...
 ```
 
@@ -713,14 +743,19 @@ See [inputs](#inputs) for more information.
 ```yaml
 ...
     - name: Get changed files
       id: changed-files
       uses: tj-actions/changed-files@v40
+      with:
+        safe_output: false
 
     - name: List all added files
+      env:
+        ADDED_FILES: |-
+          ${{ steps.changed-files.outputs.added_files }}
       run: |
-        for file in ${{ steps.changed-files.outputs.added_files }}; do
+        for file in "$ADDED_FILES"; do
           echo "$file was added"
         done
 ...
 ```
 
@@ -734,10 +769,12 @@ See [outputs](#outputs) for a list of all available outputs.
 ```yaml
 ...
     - name: Get changed files
       id: changed-files
       uses: tj-actions/changed-files@v40
+      with:
+        safe_output: false
 
     - name: Run a step if my-file.txt was modified
       if: contains(steps.changed-files.outputs.modified_files, 'my-file.txt')
       run: |
         echo "my-file.txt file has been modified."
@@ -754,12 +791,13 @@ See [outputs](#outputs) for a list of all available outputs.
 ```yaml
 ...
 
    - name: Get changed files and write the outputs to a Txt file
      id: changed-files-write-output-files-txt
-     uses: ./
+     uses: tj-actions/changed-files@v40
      with:
+       safe_output: false
        write_output_files: true
 
    - name: Verify the contents of the .github/outputs/added_files.txt file
      run: |
        cat .github/outputs/added_files.txt
@@ -773,12 +811,13 @@ See [outputs](#outputs) for a list of all available outputs.
 
 ```yaml
 ...
    - name: Get changed files and write the outputs to a JSON file
      id: changed-files-write-output-files-json
-     uses: ./
+     uses: tj-actions/changed-files@v40
      with:
+       safe_output: false
        json: true
        write_output_files: true
 
    - name: Verify the contents of the .github/outputs/added_files.json file
      run: |
@@ -818,10 +857,11 @@ See [inputs](#inputs) for more information.
 ...
     - name: Get changed files
       id: changed-files-specific
       uses: tj-actions/changed-files@v40
       with:
+        safe_output: false
         files: |
           my-file.txt
           *.sh
           *.png
           !*.md
@@ -838,19 +878,25 @@ See [inputs](#inputs) for more information.
       run: |
         echo "Only files listed above have changed."
 
     - name: Run step if any of the listed files above is deleted
       if: steps.changed-files-specific.outputs.any_deleted == 'true'
+      env:
+        DELETED_FILES: |-
+          ${{ steps.changed-files-specific.outputs.deleted_files }}
       run: |
-        for file in ${{ steps.changed-files-specific.outputs.deleted_files }}; do
+        for file in "$DELETED_FILES"; do
           echo "$file was deleted"
         done
 
     - name: Run step if all listed files above have been deleted
       if: steps.changed-files-specific.outputs.only_deleted == 'true'
+      env:
+        DELETED_FILES: |-
+          ${{ steps.changed-files-specific.outputs.deleted_files }}
       run: |
-        for file in ${{ steps.changed-files-specific.outputs.deleted_files }}; do
+        for file in "$DELETED_FILES"; do
           echo "$file was deleted"
         done
 ...
 ```
 
@@ -956,18 +1002,22 @@ jobs:
 
       - name: Get changed files in the .github folder
         id: changed-files-specific
         uses: tj-actions/changed-files@v40
         with:
+          safe_output: false
           base_sha: ${{ steps.get-base-sha.outputs.base_sha }}
           files: .github/**
 
       - name: Run step if any file(s) in the .github folder change
         if: steps.changed-files-specific.outputs.any_changed == 'true'
+        env:
+          ALL_CHANGED_FILES: |-
+            ${{ steps.changed-files-specific.outputs.all_changed_files }}
         run: |
           echo "One or more files in the .github folder has changed."
-          echo "List all the files that have changed: ${{ steps.changed-files-specific.outputs.all_changed_files }}"
+          echo "List all the files that have changed: $ALL_CHANGED_FILES"
 ...
 ```
 
 See [inputs](#inputs) for more information.
 
@@ -986,15 +1036,19 @@ See [inputs](#inputs) for more information.
 
     - name: Run changed-files with defaults in dir1
       id: changed-files-for-dir1
       uses: tj-actions/changed-files@v40
       with:
+        safe_output: false
         path: dir1
 
     - name: List all added files in dir1
+      env:
+        ADDED_FILES: |-
+          ${{ steps.changed-files-for-dir1.outputs.added_files }}
       run: |
-        for file in ${{ steps.changed-files-for-dir1.outputs.added_files }}; do
+        for file in "$ADDED_FILES"; do
           echo "$file was added"
         done
 ...
 ```
 
@@ -1013,11 +1067,11 @@ See [inputs](#inputs) for more information.
       with:
         quotepath: "false"
 
     - name: Run changed-files with quotepath disabled for a specified list of file(s)
       id: changed-files-quotepath-specific
-      uses: ./
+      uses: tj-actions/changed-files@v40
       with:
         files: test/test-è.txt
         quotepath: "false"
 ...
 ```
diff --git a/action.yml b/action.yml
index ecb6781..7038657 100644
--- a/action.yml
+++ b/action.yml
@@ -132,10 +132,14 @@ inputs:
     default: "false"
   escape_json:
     description: "Escape JSON output."
     required: false
     default: "true"
+  safe_output:
+    description: "Apply sanitization to output filenames before being set as output."
+    required: false
+    default: "true"
   fetch_depth:
     description: "Depth of additional branch history fetched. NOTE: This can be adjusted to resolve errors with insufficient history."
     required: false
     default: "50"
   skip_initial_fetch:
diff --git a/src/changedFilesOutput.ts b/src/changedFilesOutput.ts
index 2d4cc2d..89ce660 100644
--- a/src/changedFilesOutput.ts
+++ b/src/changedFilesOutput.ts
@@ -41,11 +41,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('added_files', outputPrefix),
     value: addedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
   await setOutput({
     key: getOutputKey('added_files_count', outputPrefix),
     value: addedFiles.count,
     writeOutputFiles: inputs.writeOutputFiles,
@@ -62,11 +63,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('copied_files', outputPrefix),
     value: copiedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('copied_files_count', outputPrefix),
     value: copiedFiles.count,
@@ -84,11 +86,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('modified_files', outputPrefix),
     value: modifiedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('modified_files_count', outputPrefix),
     value: modifiedFiles.count,
@@ -106,11 +109,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('renamed_files', outputPrefix),
     value: renamedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('renamed_files_count', outputPrefix),
     value: renamedFiles.count,
@@ -128,11 +132,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('type_changed_files', outputPrefix),
     value: typeChangedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('type_changed_files_count', outputPrefix),
     value: typeChangedFiles.count,
@@ -150,11 +155,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('unmerged_files', outputPrefix),
     value: unmergedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('unmerged_files_count', outputPrefix),
     value: unmergedFiles.count,
@@ -172,11 +178,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('unknown_files', outputPrefix),
     value: unknownFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('unknown_files_count', outputPrefix),
     value: unknownFiles.count,
@@ -197,11 +204,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('all_changed_and_modified_files', outputPrefix),
     value: allChangedAndModifiedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('all_changed_and_modified_files_count', outputPrefix),
     value: allChangedAndModifiedFiles.count,
@@ -224,11 +232,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('all_changed_files', outputPrefix),
     value: allChangedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('all_changed_files_count', outputPrefix),
     value: allChangedFiles.count,
@@ -312,11 +321,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('all_modified_files', outputPrefix),
     value: allModifiedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('all_modified_files_count', outputPrefix),
     value: allModifiedFiles.count,
@@ -417,11 +427,12 @@ export const setOutputsAndGetModifiedAndChangedFilesStatus = async ({
     key: getOutputKey('deleted_files', outputPrefix),
     value: deletedFiles.paths,
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 
   await setOutput({
     key: getOutputKey('deleted_files_count', outputPrefix),
     value: deletedFiles.count,
diff --git a/src/inputs.ts b/src/inputs.ts
index 5f5f468..ac492d6 100644
--- a/src/inputs.ts
+++ b/src/inputs.ts
@@ -32,10 +32,11 @@ export type Inputs = {
   dirNamesIncludeFiles: string
   dirNamesIncludeFilesSeparator: string
   dirNamesDeletedFilesIncludeOnlyDeletedDirs: boolean
   json: boolean
   escapeJson: boolean
+  safeOutput: boolean
   fetchDepth?: number
   fetchSubmoduleHistory: boolean
   sinceLastRemoteCommit: boolean
   writeOutputFiles: boolean
   outputDir: string
@@ -152,10 +153,11 @@ export const getInputs = (): Inputs => {
       trimWhitespace: false
     }
   )
   const json = core.getBooleanInput('json', {required: false})
   const escapeJson = core.getBooleanInput('escape_json', {required: false})
+  const safeOutput = core.getBooleanInput('safe_output', {required: false})
   const fetchDepth = core.getInput('fetch_depth', {required: false})
   const sinceLastRemoteCommit = core.getBooleanInput(
     'since_last_remote_commit',
     {required: false}
   )
@@ -270,10 +272,11 @@ export const getInputs = (): Inputs => {
     dirNamesExcludeCurrentDir,
     dirNamesIncludeFiles,
     dirNamesIncludeFilesSeparator,
     json,
     escapeJson,
+    safeOutput,
     writeOutputFiles,
     outputDir,
     outputRenamedFilesAsDeletedAndAdded,
     token,
     apiUrl,
diff --git a/src/main.ts b/src/main.ts
index b3cf279..85dc7e5 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -171,11 +171,12 @@ const getChangedFilesFromLocalGitHistory = async ({
     await setOutput({
       key: 'all_old_new_renamed_files',
       value: allOldNewRenamedFiles.paths,
       writeOutputFiles: inputs.writeOutputFiles,
       outputDir: inputs.outputDir,
-      json: inputs.json
+      json: inputs.json,
+      safeOutput: inputs.safeOutput
     })
     await setOutput({
       key: 'all_old_new_renamed_files_count',
       value: allOldNewRenamedFiles.count,
       writeOutputFiles: inputs.writeOutputFiles,
diff --git a/src/utils.ts b/src/utils.ts
index 81a0457..46e9b4f 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1322,36 +1322,44 @@ export const setArrayOutput = async ({
     key: outputPrefix ? getOutputKey(key, outputPrefix) : key,
     value: inputs.json ? value : value.join(inputs.separator),
     writeOutputFiles: inputs.writeOutputFiles,
     outputDir: inputs.outputDir,
     json: inputs.json,
-    shouldEscape: inputs.escapeJson
+    shouldEscape: inputs.escapeJson,
+    safeOutput: inputs.safeOutput
   })
 }
 
 export const setOutput = async ({
   key,
   value,
   writeOutputFiles,
   outputDir,
   json = false,
-  shouldEscape = false
+  shouldEscape = false,
+  safeOutput = false
 }: {
   key: string
   value: string | string[] | boolean
   writeOutputFiles: boolean
   outputDir: string
   json?: boolean
   shouldEscape?: boolean
+  safeOutput?: boolean
 }): Promise<void> => {
   let cleanedValue
   if (json) {
     cleanedValue = jsonOutput({value, shouldEscape})
   } else {
     cleanedValue = value.toString().trim()
   }
 
+  // if safeOutput is true, escape special characters for bash shell
+  if (safeOutput) {
+    cleanedValue = cleanedValue.replace(/[$()`|&;]/g, '\\$&')
+  }
+
   core.setOutput(key, cleanedValue)
 
   if (writeOutputFiles) {
     const extension = json ? 'json' : 'txt'
     const outputFilePath = path.join(outputDir, `${key}.${extension}`)

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, '\\$&')
  }