Safeguard
Developer Tools•

Can an issue or pull request title run code in your workflow?

Yes: placing github.event values directly in a run step is script injection. See how a pull request title runs a command and how an env variable stops it.

Safeguard Security Team
4 min read

Yes. If a workflow puts a value like a pull request title directly into a run step, the text is substituted into the shell script before it runs, so a crafted title can execute commands on the runner. Pass the value through an environment variable instead.

How the injection works

GitHub's documentation explains that expressions inside the dollar and double brace syntax are evaluated and then substituted with their values. The result becomes part of the script. Consider this vulnerable step:

- name: Check PR title
  run: |
    title="${{ github.event.pull_request.title }}"
    if [[ $title =~ ^octocat ]]; then
      echo "ok"
    else
      echo "bad title"
      exit 1
    fi

The documentation shows that a title such as a"; ls $GITHUB_WORKSPACE" closes the quoted assignment early, so the attacker's command runs. In a workflow with secrets or write permissions, the command could be far worse than listing files.

Which values are risky

The docs list context values that should be treated as potentially untrusted, including those ending in body, head_ref, label, message, name, ref and title. Examples are the issue title and the pull request body. The warning also covers less obvious inputs such as branch names and email addresses.

Source of inputWho controls it
Pull request title and bodyAnyone who can open a pull request
Issue title and bodyAnyone who can open an issue
Branch name (head_ref)The branch creator, including forks
Commit messageThe commit author

The fix: an intermediate environment variable

GitHub's hardening guide recommends setting the value of the expression to an intermediate environment variable for inline scripts, so untrusted input does not alter the script. Its preferred approach is a JavaScript action that takes the value as an argument.

- name: Check PR title
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: |
    if [[ "$PR_TITLE" =~ ^octocat ]]; then
      echo "ok"
    else
      exit 1
    fi

The shell now reads the title as data from a variable, not as part of the program text. Always quote the variable.

Why this matters more for maintainers

Open source repositories accept input from strangers by design. Issue triage bots, labelers and title checkers all handle that input. Combine this risk with a privileged trigger and it gets worse. See pull_request_target risks for open source maintainers and least privilege GITHUB_TOKEN for a release workflow.

Audit steps

  1. Search workflows for the dollar brace pattern inside run blocks.
  2. For each, ask whether the value comes from a user.
  3. Move it into an env variable, or into an action input.
  4. Quote it in the shell.
  5. Add the workflows folder to CODEOWNERS.

Try it yourself

Click through it at your own pace, or open it full screen.

Click through a gated pull request and see which checks run before a merge. Workflow changes are exactly the kind of change to gate.

Safeguard can gate pull requests with policies; the free scan at gold.safeguard.sh/scan covers your dependencies.

Quick check

Is a branch name really attacker controlled?

Yes, for pull requests from forks the branch name is chosen by the contributor, and the docs list head_ref among risky values.

Is quoting the variable enough?

Quoting inside the shell helps, but the main fix is to keep the value out of the script text by using an env variable.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.