| Recipe | GitHub Actions CI dependencies |
| Target audience (the chef) | Project maintainers and developers who need practical, concrete steps to efficiently secure CI dependencies within their GitHub Actions workflows |
| Scope (ingredients) | Dependencies within the GitHub Actions, Github Action runner image, Choosing CI dependencies, Responding to vulnerabilities in CI dependencies |
| Out of scope (do not add) | Writing your own custom actions, Other CI tools, Other types of dependencies (code, test, container, etc), Transitive dependencies, SBOMs – provenance |
TLDR:
🗹 Source the Best Ingredients: Evaluate before using / Trust the source: Prefer actions from trusted organisations (or GitHub org itself)
🗹 Measure precisely: Limit permissions and access to the minimum necessary for the action
🗹 Protect your tools: Pin your dependencies and runner images
🗹 Keep it fresh: Automatically update your dependencies and images
🗹 Choose your kitchen: Evaluate self hosted runners vs GitHub hosted runners
Utensils: Tools for cooking up a release
As a quick summary, here are specific tools mentioned in this post, along with their capabilities:
| Audit Actions | Pin Action SHAs | Update Action SHAs | Audit workflows | |
| zizmor | ✅ | ✅ | ✅ | |
| frizbee | ✅ | |||
| pinact | ✅ | |||
| ratchet | ✅ | |||
| pin-github-action | ✅ | |||
| scorecard | ⚠️ (check only) | ✅ | ||
| Dependabot | ✅ | |||
| Renovate | ✅ | |||
A word of caution in the kitchen
Running a third-party action is equivalent to cloning its code and executing it inside your own permission space. A tainted dependency can spoil the entire dish by exposing build secrets, tampering with the recipe (modifying code), or hijacking your serving infrastructure (package publishing), all without a noticeable change in the workflow or original ingredients.
It’s just like cooking your favorite meal in a messy kitchen, with leaky plumbing, slippery floors and dirty kitchenware: A disaster is just lurking around the corner.
The solarwinds attack is probably one of the most (in)famous in the series of supply chain attacks on CI dependencies. More recently the tj-actions/changed-files GitHub action was also compromised, not to mention hackerbot-claw exploiting github actions for trivy, datadog and others.
The Recipe: Step-by-step instructions
Evaluate before using (source your ingredients)
Prefer actions directly provided by GitHub, or those from verified organisations. In the GitHub Marketplace, Actions with the “Verified” badge indicate that GitHub has verified the creator of the action as a partner organization.
When choosing actions, particularly from unverified organizations, favor those with regular and recent updates and an active community that addresses issues regularly. Adopters, project longevity, stars, contributors and forks are quick ways to compare actions, but can be manipulated. Adopters and longevity are the best metrics. If badges are published, these can give you an indicator if last builds or tests are passing, results of code coverage, static code analysis, etc. It can be difficult to test Actions, so effort put in here is a strong sign of good practices.
Secrets (a note on seasoning): Take extra caution when actions are asking for more permissions. Note that when you allow access to secrets this injects all secrets into the workflow, so ONLY enable secrets when necessary on a trusted action. Remember that actions already have access to the secrets within the build environment. Is that enough? Too much? (See Access control below).
Static Aanalysis (taste testing): Run your workflow through a static analysis tool, such as zizmor:
- Install zizmor on your favorite platform or even as a container image
- Run zizmor against workflows, actions, dependabot configurations or all of the above:
- Offline, for a specific tag: zizmor –collect=all myorg/myrepo@v1
- Online, fetching remote repositories as necessary, and performing all audits that require online access: zizmor –gh-token $(gh auth token) myorg/myrepo
- Even better you can automatically monitor your repository by integrating zizmorcore/zizmor-action within your GitHub Actions
For more, go to zizmor documentation.
Enforce API settings (the pantry gatekeeper): To enforce your review process, configure your GitHub API settings to allow only “organization-only actions” or “explicitly named actions”. If set at the organization level, these permissions will overwrite all downstream permissions. This will allow a trusted set of people to review all actions, but it does introduce additional review overhead when adding a new Action to your pipelines.
Pin your dependencies and runner images (protect your tools)
Unlike mutable tags (e.g., @v1) and can be overwritten by maintainers or attackers, a digest is a unique, unchangeable identifier for a specific version of the action’s code or a container image. When you use a mutable tag reference, anyone with upstream access to update tags could change your ingredients – while you’re in the middle of cooking!
Anchoring tools: You can use pinning tools like frizbee / pin-github-action / ratchet / pinact (among others) to replace all tag references in your workflow yaml file by digests.
Inspection check: Scorecard or zizmor will flag if you haven’t done this with the pinned dependency check, which includes pinning CI dependencies.
Automatically update your dependencies (keeping it fresh)
Like perishable goods, your actions can go bad over time. Just like any other code, new vulnerabilities may affect actions you reference in your workflows. You need to regularly update your Actions in order to benefit from the latest security updates.
The kitchen assistant: To automate this process, enable Dependabot or Renovate for GitHub Actions. Grouping these updates in these automated tools can help reduce the maintenance burden, but ensure that security updates are prioritized (for this to work you must enable Dependency graph and Dependabot alerts in Github). These bots also ensure that the upgrade comes from the proper upstream repository, so it comes from the existing project maintainers.
Setting the schedule: As an example, to configure Dependabot to update the CI workflow dependencies:
- Add
dependabot.ymlunder the .github folder. If you already have Dependabot configured for other ecosystems, you can use the existing file of your repository.
The following configuration will send weekly updates as separate PRs for each action that needs updates.
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
These tools can update pinned Actions as well as tags, so they work well with the previous advice. Here is an example of the ingredient change Dependabot will automatically prepare:
- uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # v0.34.1
+ uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
Access control (measure precisely):
GitHub Actions execute within your organization and repository, so it’s important to note what they have access to. Just like you monitor access to knives in your kitchen, applying the principle of least privilege is a good practice for CI as well. All actions have access to a GITHUB_TOKEN with permissions based on repository, organization, and workflow-level settings. By default, actions will have the broadest permissions available unless restricted. Secure Use for GitHub Actions is a good place to start.
Scorecard will flag if your project does not set (limit) workflow-level settings with the Token Permissions check.
A few pointers:
- Keep tools safe: Restrict the ability of GitHub Actions to create or approve pull requests
- Vetting new chefs: Require approval for workflow runs coming from new contributor forks
- Mind the shared space: As a reminder, the Github token is available to most workflows, and earlier workflows can generally poison the environment for later workflows.
- Secure the service entrance: Remember that pull_request_target runs workflows in the context of the base repository and thus has read and write permissions to the Github token by default.
Self hosted runners vs GitHub Runners (choose your kitchen)
GitHub allows you to run on hosted ephemeral runners (like a commercial kitchen space) or using your own runner infrastructure (your own kitchen). If you are using self-hosted runners, you are also responsible for updating the base software image used by the runner, as well as the actions running in the workflows. You will also need to ensure a secure environment in the self hosted runner. For a more in depth comparison, read this.