Third-Party GitHub Actions: Effects of an Opt-Out Permission Model

Aug 16, 2023
9 minutes
73 views

GitHub Actions, for good reason, is the go-to choice for countless developers. Despite its convenience, though, potential security risks accompany third-party actions. In this post, we share how the world’s most popular repositories fail to manage their build permissions and walk you through the why and the how of proper permissions management in GitHub Actions.

Third-Party GitHub Actions: Effects of an Opt-Out Permission Model

GitHub Actions has gained immense popularity in recent years as a powerful and flexible automation platform for software development workflows. With its intuitive user interface, extensive library of prebuilt actions and seamless integration with GitHub repositories, it has become a go-to choice for many developers and organizations seeking to streamline their CI/CD pipelines, automate routine tasks, and improve collaboration across teams.

But convenience and accelerated development aside, using third-party actions comes with security risks. GitHub cautions organizations by plainly stating, "There is significant risk in sourcing actions from third-party repositories on GitHub." The statement underscores the importance of ensuring the security of workflows on the platform.

In this blog post, we cover the all-important reasons AppSec engineers need to properly manage permissions in GitHub Actions — while also showing how the vast majority of GitHub organizations fail to correctly handle these permissions.

Action’s Permissions

When a workflow runs, GitHub issues a short-lived GITHUB_TOKEN for the workflow to interact with the repository. This token has since become a target for attackers who succeed in executing code in a workflow, as it provides them with the same level of permission as the token. Command injection in the CI, compromised dependencies or actions — all allow attackers to access the token and use it against the repository.

Tokens in each workflow are set by the repository administrators and maintainers with read or write access to a list of scopes. If not specifically set in the workflow configuration file, the default setting of the repository will determine permissions. In February 2023, applicable to new organizations, GitHub changed the default permissions granted to this token from “write-all”, granting write permissions on all available scopes, to read-only for the contents, packages and metadata scopes.

Developers who want to limit the permissions to the minimum can explicitly grant permissions to each workflow or a job, which is a smart move. Know the caveats, though. GitHub Actions supports the "write-all" and “read-all” scopes, granting all permissions. Also, even if granular permissions are set, they should match against the permissions the action requires to fulfill its purpose.

Let’s review why it’s a bad practice to allow broader scopes than required.

Permissions Worth Taking a Closer Look

Granting higher permissions to workflows that aren’t required, like “write-all”, “read-all” or actions in figure 1, fuels post exploitation techniques and allows attackers to inflict serious damage to the organization.

Actions commonly granted overly permissive actions can result in damaging exploitation.
Figure 1: Actions commonly granted overly permissive actions can result in damaging exploitation.

Granting Tailored Permissions

GitHub allows setting granular permissions per a workflow’s job, and we can use an action inside a job, isolated from the rest of the workflow.

Let’s grant the third-party action the exact permissions it needs to operate.

Unlike the installation of GitHub apps where you authorize each permission used by the app beforehand, when making use of a GitHub action, there’s no permissions approval process. What’s more, there’s no central location, GitHub or otherwise, to find what permissions a third-party action requires. This makes it difficult for developers to maintain the important principle of least privilege.

From good will, the maintainers occasionally mention the recommended permissions, like in actions/stale. But this isn’t enforced or standardized by GitHub, and in general, it’s rare to see guidelines on required permissions provided by maintainers.

The Close Stale Issues action by GitHub provides recommended permissions.
Figure 2: The Close Stale Issues action by GitHub provides recommended permissions. [Source]

Creating Action Permissions Database

We’ve created PiPER (a portmanteau of pipeline and permissions), an internal tool that calculates the minimal required permissions for a given action. It works by performing static code analysis on the actions’ code, identifying any action made against the repository, and mapping usage to a permission. Achieving good coverage isn’t so straightforward, as the GitHub API can be accessed in various ways, such as interacting with it directly through HTTP or by using various libraries that wrap it. Additionally, both the REST API and the GraphQL endpoints can be used.

We used PiPER to analyze the top 1000 popular actions on the GitHub marketplace.

We've learned that about 50% of the actions don’t perform any interaction against the repository and, therefore, don’t require the GitHub token to fulfill its purpose.

Fifty percent of the analyzed actions require no permission.
Figure 3: Fifty percent of the analyzed actions require no permission.

In fact, 94% of actions require only 2-3 of the 13 available scopes — contents, issues and pull-requests being the popular ones.

For example, the rtCamp/action-slack-notify action doesn't have any interaction with the GitHub API, so the GitHub token accessible by this action shouldn’t be granted permissions against the repository. You can achieve this by explicitly declaring it inside the job/workflow, as follows:

Disable permissions for all the available scopes.
Figure 4: Disable permissions for all the available scopes.

How Major Projects Manage Permissions

We began to wonder how popular open-source projects manage their workflows’ permissions. Do they limit each action to the minimum required? Do they manage cases where several actions are used in one workflow and each requires a different level of permissions?

To answer these questions, we created a list of the top starred 2000 public projects on GitHub using GitHub actions. As workflows triggered by pull requests originating from forks are set with read-only permissions by default, we analyzed build logs triggered by “push” events, extracted the third-party actions in use in each workflow, and calculated their effective permissions against the repository.

We now have the following:

  1. Database of actions and their required permissions, created by PiPER
  2. List of workflows used by the top 2,000 GitHub public repositories using GitHub Actions, as well as the third-party actions they use and their permissions granted through the GitHub token.

We learned that the top 2,000 repositories are configured with approximately 6,000 workflows, all of which use 1,045 unique actions. Key observations we made include:

1. A staggering 93% of the 2,000 GitHub repositories that use GitHub Actions have at least one overly permissive workflow.

93% of the analyzed repositories have at least one workflow with excessive permissions, as calculated by comparing granted permissions to required permissions using PiPER.
Figure 5: 93% of the analyzed repositories have at least one workflow with excessive permissions, as calculated by comparing granted permissions to required permissions using PiPER.

2. The vast majority of the open-source projects, 80%, doesn’t specify permissions in their workflow yaml, leaving the problematic default permissions in effect. That’s a lot of projects! But, most of them probably set minimal default permissions in the repository level, right? Well, far from it — 86% of the projects that didn’t specify permissions in their workflows were found overly permissive.

3. What happens when the repository maintainers do set granular permissions? In 36% of the open-source projects that invested time limiting the permissions of their GitHub token, their workflows were still overly permissive. It happens because permissions granted don’t match the actions’ requirements, or they’re specified for the entire workflow and not tailored per each job’s requirements.

Major Projects Using Low-Reputation Actions

Additionally, we encountered many cases where low-reputation actions with a low number of stars and forks maintained by private users — not having a GitHub “Verified Creator” badge, GitHub own vetting process — are effectively granted write-all permissions. As we can see in the following examples, first by taking a peak in the build logs, this is how we can tell the effective permissions of a job is “write-all”:

The effective permissions of a job are printed to the build logs when it starts.
Figure 6: The effective permissions of a job are printed to the build logs when it starts.

A few examples of low-reputation actions used by workflows of highly popular GitHub repositories — unpinned and granted with highly excessive permissions to the repository through the GitHub token — include re-actors/alls-green used by Microsoft and FastAPI, bwoodsend/setup-winlibs-action used by Cloudflare, and ljharb/actions used by Airbnb. This is a serious security risk. Any action compromised could potentially be used to attack your GitHub organization all the way to the production environment.

Remediation

GitHub Actions is a powerful tool that can help developers automate their workflows. But it’s important to stay on top of the security risks associated with using this tool.

Attackers could leverage a malicious action to compromise a repository. To mitigate this risk, use job isolation, running each job in a separate container. Then, if one job is compromised, the others won’t be affected.

Another important security measure is to use tailored permissions, meaning to grant only the permissions necessary for an action to complete its task. For example, if an action needs to access pull requests, it should only be granted the pull-request:read permission.

Implementing job isolation and splitting third-party actions into separate jobs opens up the option to grant tailored permissions, which reduces granted permissions and mitigates the security risks associated with using GitHub Actions. It’s important to remember, though, that no security measure is perfect. You must remain vigilant and continuously evaluate the security of workflows to protect against potential security breaches.

Workflow with excessive permissions
Figure 7: Workflow with excessive permissions
Breaking down the workflow to separate jobs with granular permissions for each job
Figure 8: Breaking down the workflow to separate jobs with granular permissions for each job

Additional Tips for Securing GitHub Actions Workflows

  • Use actions listed on the GitHub Marketplace that have the "verified creator" badge. Prefer known vendors you can trust over unknown actions maintained by private developers. Conduct a vetting process that includes reviewing the code of the third-party action and set the permissions accordingly.
  • Pin actions to full commit hashes. This ensures that you always use a known-good version of an action, even if an attacker compromises an action and makes changes to the code.

By following these practices, you can help to protect your organization from the security risks associated with GitHub Actions workflow permissions.

Learn More

If you’re interested in learning practical tips to get started with CI/CD security, read the CI/CD Security Checklist. You’ll find six best practices to help you embrace CI/CD security and harden your pipelines over time.


Subscribe to Cloud Native Security Blogs!

Sign up to receive must-read articles, Playbooks of the Week, new feature announcements, and more.