From 83269e264cb0593da6c79045778b8ba670f7af77 Mon Sep 17 00:00:00 2001 From: Cameron G Date: Thu, 2 Jul 2026 14:30:00 -0400 Subject: [PATCH] ci: pass changed files through env in release-notes reST check The Check reStructuredText code formatting step in release_notes.yml uses shell: python and builds its file list by interpolating the tj-actions/changed-files output directly into a Python source literal: files = "${{ steps.changed-files.outputs.all_changed_files }}".split() At runtime the workflow expression is expanded by the Actions runner before Python starts, so the contents of that output become part of the Python source of the step. Because git allows filenames to contain a double-quote character as well as newlines and backslashes, a pull_request contributor can add a release-note file whose path breaks out of the string literal. GitHub renders the workflow using the head ref's copy of the file, so an attacker does not need write access to main to reach this code path. The trigger is pull_request (not pull_request_target), so GITHUB_TOKEN is read-only for this repo and repository secrets are not attached to the job. The direct blast radius is limited to code execution in the release-notes job itself, but that job still runs on the deepset-owned runner pool and shares the ephemeral filesystem with the checked-out source tree, so injecting Python here is undesirable regardless. The fix follows GitHub's documented secure workflows pattern: expose the changed-files output as an environment variable and read it from Python via os.environ, so the untrusted value never becomes part of the source string that the interpreter parses. Same class of hardening as #11733 (persist-credentials: false) and #11777 (further dependency pinning + CodeQL), extended one step further into the workflow body. --- .github/workflows/release_notes.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_notes.yml b/.github/workflows/release_notes.yml index a5b99c20541..09e2ccbbb02 100644 --- a/.github/workflows/release_notes.yml +++ b/.github/workflows/release_notes.yml @@ -62,8 +62,12 @@ jobs: - name: Check reStructuredText code formatting if: steps.changed-files.outputs.any_changed == 'true' && !contains( github.event.pull_request.labels.*.name, 'ignore-for-release-notes') shell: python + env: + CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} run: | - files = "${{ steps.changed-files.outputs.all_changed_files }}".split() + import os + + files = os.environ["CHANGED_FILES"].split() errors = [] for filepath in files: