Repository navigation
Fix template injection in handwritten GitHub workflows - #21033
Ankit Jain (radical) wants to merge 1 commit into
Conversation
Workflow inputs and outputs were interpolated into executable shell and JavaScript source. Quotes or shell substitutions in those values could change the commands being run instead of remaining literal data. Pass values through environment variables and parse argument strings into literal arrays without evaluating shell syntax. Transport the expanded test matrix through files so it stays out of executable here-strings and per-variable OS size limits. Exercise argument parsing, enumeration, large-matrix transport, and CI routing behavior through standalone scripts. Zizmor 1.31.0 reports 127 fewer template-injection findings; the remaining 18 generated-workflow findings are deferred to microsoft#21031. Refs microsoft#21031 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc572840-cb86-4d09-829a-68af89cdce3e
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 21033Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 21033" |
There was a problem hiding this comment.
🟡 Changes recommended
Windows batch wrappers still reinterpret parsed arguments, and the extension version override reads an unset PowerShell variable.
3 open findings
What changed in this PR
Hardens Aspire’s handwritten GitHub workflows against template injection. Generated agentic workflows remain deferred to #21031.
Changes:
- Passes dynamic values through environment variables and parses argument strings as data.
- Moves large test matrices through files.
- Adds behavioral regression tests and CI selection rules.
| File | Description |
|---|---|
| tests/Infrastructure.Tests/WorkflowScripts/TemplateInjectionTests.cs | Tests argument parsing and matrix transport. |
| tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs | Verifies shared-script CI routing. |
| eng/github-ci/test-trigger-map.yml | Routes shared scripts to all consumers. |
| .github/workflows/write-test-matrix.ps1 | Copies matrices with an empty fallback. |
| .github/workflows/update-nix-cli-flake.yml | Passes release inputs through environment variables. |
| .github/workflows/tests.yml | Uses file-based matrix transport. |
| .github/workflows/specialized-test-runner.yml | Parses arguments and isolates dynamic values. |
| .github/workflows/run-tests.yml | Revises build and test argument forwarding. |
| .github/workflows/reproduce-flaky-tests.yml | Parses filters and isolates reproduction inputs. |
| .github/workflows/release-github-tasks.yml | Isolates release inputs from shell source. |
| .github/workflows/README.md | Documents input handling and matrix transport. |
| .github/workflows/polyglot-validation.yml | Quotes dynamic Docker arguments. |
| .github/workflows/parse-command-line.py | Adds literal argument parsing. |
| .github/workflows/labeler-predict-pulls.yml | Reads workflow names from the environment. |
| .github/workflows/homebrew-validate-release.yml | Isolates version and cask-path values. |
| .github/workflows/extension-unit-tests.yml | Moves version overrides into the environment. |
| .github/workflows/extension-e2e-tests.yml | Reads artifact URLs from the environment. |
| .github/workflows/enumerate-test-metadata.sh | Extracts metadata enumeration with argument arrays. |
| .github/workflows/deployment-tests.yml | Parses test arguments and isolates matrix values. |
| .github/workflows/build-packages.yml | Parses version override arguments. |
| .github/workflows/build-cli-native-archives.yml | Revises native build argument forwarding. |
| .github/workflows/backport.yml | Reads target branches through process.env. |
| .github/workflows/backmerge-release.yml | Isolates commit counts from executable source. |
| .github/actions/unlock-macos-keychain/action.yml | Quotes environment-backed keychain inputs. |
| .github/actions/enumerate-tests/action.yml | Parses build arguments and writes matrix files. |
| .github/actions/check-changed-files/action.yml | Reads the patterns path from the environment. |
🧠 Review effort: Balanced
| $versionArgsJson = & $python .github/workflows/parse-command-line.py VERSION_OVERRIDE_ARG | ||
| if ($LASTEXITCODE -ne 0) { throw 'Invalid versionOverrideArg.' } | ||
| $versionArgs = @($versionArgsJson | ConvertFrom-Json) | ||
| & $env:BUILD_SCRIPT ` |
| [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 | ||
| # Run tests | ||
| & ${{ env.DOTNET_SCRIPT }} test --project ${{ env.TEST_PROJECT_PATH }} ` | ||
| & $env:DOTNET_SCRIPT test --project $env:TEST_PROJECT_PATH ` |
| run: corepack yarn version --new-version "${{ inputs.extensionVersionOverride }}" --no-git-tag-version | ||
| env: | ||
| EXTENSION_VERSION_OVERRIDE: ${{ inputs.extensionVersionOverride }} | ||
| run: corepack yarn version --new-version "$EXTENSION_VERSION_OVERRIDE" --no-git-tag-version |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |


Description
Workflow inputs and outputs can become executable shell or JavaScript source when interpolated directly into GitHub Actions scripts. This change keeps those values as data in handwritten workflows and composite actions.
Values pass through environment variables, and argument strings are parsed into literal arrays without evaluating shell syntax. The expanded test matrix travels through files to avoid both executable here-strings and per-variable OS size limits.
Adds behavioral regression coverage for hostile literal arguments, malformed quoting, enumeration argument order, large-matrix transport, and CI routing. Tests execute standalone production scripts rather than extracting scripts from YAML.
Agentic workflows are excluded: no compiler upgrade, generated locks, action pins, or lint exceptions change. Follow-up #21031 tracks the 18 remaining generated findings and compiler blockers; this PR does not close it.
Fixes # (issue)
Validation
Run locally on macOS:
./restore.sh /p:NuGetAudit=false— passed.bash .github/workflows/lint-handwritten-workflows.sh— passed for 59 handwritten workflows.bash -n .github/workflows/enumerate-test-metadata.sh— passed.git diff --check— passed.uv tool run --from 'zizmor==1.31.0' zizmor \ --offline --format json .github/actions .github/workflowsScanner comparison against the unchanged base: 145 → 18 template-injection findings (127 removed). All 18 remaining findings are in six untouched agentic locks. The full scan exits 14 with 159 findings across categories; this is not a clean security audit.
Windows and live GitHub workflow execution have not been validated.
Security considerations
Multi-argument inputs support POSIX-style quoting on all platforms but deliberately do not evaluate shell substitutions, variables, or wildcards. Malformed quoting fails the step. Security review should confirm literal argument boundaries and cross-platform behavior; no formal security review or threat model has been completed.
Checklist
<remarks />and<code />elements on your triple slash comments?