Skip to content

fix: route untrusted workflow inputs through env to prevent shell injection - #149

Merged
joshua-temple merged 1 commit into
mainfrom
fix/workflow-input-injection
Jun 14, 2026
Merged

joshua-temple merged 1 commit into
mainfrom
fix/workflow-input-injection

Conversation

@joshua-temple

Copy link
Copy Markdown
Collaborator

Problem

Generated workflows interpolated untrusted GitHub Actions context directly into run: shell bodies. GitHub substitutes ${{ ... }} into the script text before the shell parses it, so a value containing a single quote, backtick, or $(...) breaks out of its argument. This is a correctness bug for legitimate values carrying shell metacharacters and, in the external-update receiver, an injection vector that lets a dispatch escalate from writing state to running arbitrary shell with that job's secrets.

Fix

Bind each untrusted value to a step-level env: variable and reference it as a quoted shell variable ("$VAR"), the pattern the hotfix workflow already uses.

Sites converted:

  • internal/generate/external.go - source_repo, deploy_name, environment, sha, version, artifacts
  • internal/generate/promote.go - mode input echoed in the validate step
  • internal/generate/pr_preview.go - pull_request base/head SHAs in the detect-changes and plan steps (attacker-influenceable on fork PRs)

Untrusted here means inputs.*, github.event.*, and github.event.inputs.*. Trusted contexts cascade controls (github.sha, github.ref, github.run_id, secrets.*, static literals) are left unchanged. Other generators (orchestrate, release, the hotfix path, the github-script-based notify-primary step) already route untrusted values through env:/with:/JS and were verified, not changed.

Verification

  • Unit tests assert the generated run: bodies contain no ${{ inputs. }} or ${{ github.event. }} interpolation and that values flow through env: and are consumed as quoted shell variables.
  • The multi-repo external-state e2e scenario now dispatches an artifacts payload containing a single quote, $(...), and a backtick, and asserts the recorded artifact value round-trips byte-for-byte through the receiver. Passes locally under act + gitea.
  • go build ./... && go test ./... green; e2e module builds and vets clean; golangci-lint run ./... clean on both modules.

…ection

Generated workflows interpolated workflow_dispatch inputs and
pull_request event fields directly into run: shell bodies. GitHub
substitutes ${{ ... }} into the script text before the shell parses it,
so a value containing a single quote, backtick, or $(...) breaks out of
its argument and executes as shell. This is both a correctness bug for
legitimate values carrying shell metacharacters and an injection vector
in the external-update receiver.

Bind each untrusted value to a step-level env: variable and reference it
as a quoted shell variable, matching the pattern the hotfix workflow
already uses.

Sites converted:
- external.go: source_repo, deploy_name, environment, sha, version, artifacts
- promote.go: mode input echoed in the validate step
- pr_preview.go: pull_request base/head SHAs in detect-changes and plan steps

Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
@joshua-temple
joshua-temple merged commit 53b7d31 into main Jun 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant