In the evolving landscape of software development, Continuous Integration and Continuous Deployment (CI/CD) tools like GitHub Actions have become indispensable. However, with great power comes great responsibility, and it's crucial to be aware of potential security pitfalls. One such vulnerability is treating untrusted inputs, such as branch names or pull request (PR) titles, as static parameters.
Spoiler alert, they should NOT be trusted
I enjoy puzzles, so here goes one:
steps:
- name: Generate summary
run: |
echo "Pull Request for [${{ github.event.pull_request.title }}](https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}) has been updated 🎉" >> $GITHUB_STEP_SUMMARY
echo "Image tagged **v${{ needs.determine_app_version.outputs.app_version }}** has been built and pushed to the registry." >> $GITHUB_STEP_SUMMARY
This will generate a workflow summary like:
Can you spot the vulnerability?
The Problem
When a developer clicks on the Revert PR button in GitHub, the new PR title is Revert "<OLD TITLE>"
. This breaks the Generate summary step.
Try again if you haven’t spotted the vulnerability
The vulnerability lies in how github.event.pull_request.title
input is handled. GitHub Actions expressions dynamically generate code. Unfortunately, if the PR title contains any special characters or unexpected input, it can cause the script to fail or behave unexpectedly.
The Impact
When developers click on Revert PR button, the PR title is Revert "<OLD TITLE>"
, the echo command in the Generate summary step generates the bash command:
echo "Pull Request for [Revert "<OLD TITLE>"](https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}) has been updated 🎉" >> $GITHUB_STEP_SUMMARY
The presence of the double quotes within the PR title closes the echo early. However, this allows for bad actors to craft a title that could be used take full control of the runner
The Solution
To prevent this issue, one approach is to use env as an input “encoder” and avoid directly using GitHub Actions ${{ }}
expressions. For example:
env:
TITLE: ${{ github.event.pull_request.title }}
steps:
- name: Generate summary
run: |
echo "Pull Request for [$TITLE](https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}) has been updated 🎉" >> $GITHUB_STEP_SUMMARY
By using the env
context to store untrusted inputs, you avoid potential script-breaking issues caused by special characters pre-rendered by Github Actions.
Leave a comment if you can think on a different way to solve this problem!