Skip to content

Fix template injection in handwritten GitHub workflows - #21033

Draft
Ankit Jain (radical) wants to merge 1 commit into
microsoft:mainfrom
radical:template-injection-fixes
Draft

Ankit Jain (radical) wants to merge 1 commit into
microsoft:mainfrom
radical:template-injection-fixes

Conversation

@radical

Copy link
Copy Markdown
Member

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.
  • The following targeted test command — passed, 255 succeeded, 0 failed, 0 skipped:
dotnet test --project tests/Infrastructure.Tests/Infrastructure.Tests.csproj \
  --no-launch-profile /p:NuGetAudit=false -- \
  --filter-class '*.TemplateInjectionTests' \
  --filter-class '*.TestTriggerMapTests' \
  --filter-class '*.AgenticWorkflowTests' \
  --filter-class '*.ValidateAgenticWorkflowsTests' \
  --filter-class '*.BackportWorkflowTests' \
  --filter-class '*.ExtensionWorkflowTests' \
  --filter-class '*.LabelerPredictPullsWorkflowTests' \
  --filter-class '*.PrDocsCheckWorkflowTests' \
  --filter-class '*.LintHandwrittenWorkflowsScriptTests' \
  --filter-not-trait 'quarantined=true' \
  --filter-not-trait 'outerloop=true'
uv tool run --from 'zizmor==1.31.0' zizmor \
  --offline --format json .github/actions .github/workflows

Scanner 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

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

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
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 21033

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 21033"

@github-actions github-actions Bot added the area-engineering-systems infrastructure helix infra engineering repo stuff label Oct 15, 2026
@aspire-repo-bot
aspire-repo-bot Bot requested a balanced review from Copilot October 15, 2026 19:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-engineering-systems infrastructure helix infra engineering repo stuff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants