Use GitHub Actions securely

How to use GitHub Actions securely

GitHub is one of the most popular source control platforms available. It relies on Git concepts, and millions of developers use it. GitHub Actions embrace all aspects of what source control needs, such as branching, pull requests, feature flags, and versioning. It also integrates nicely into third-party continuous integration and continuous development (CI/CD) pipelines or deployment tools like Azure DevOps, Jenkins, GitLab, and Octopus Deploy.

GitHub released GitHub Actions in September 2018. It quickly became the number one CI/CD pipeline engine for organizations. Like the well-known DevOps tools, GitHub Actions works with pipelines called workflows, where each pipeline is a collection of tasks—which GitHub calls events. 

Using GitHub Actions securely

A combined source control solution with CI/CD pipelines is a powerful capability, as organizations can manage their application lifecycle processes from within a single solution. But at the same time, organizations should watch out for potential security risks and threats if the solution’s implementation doesn’t have security as the core focus.

Securing your software development and application release chain entails several different mechanisms and scenarios to keep under control. This article focuses on five ways to use GitHub Actions securely:

  1. Use trusted code
  2. Self-hosted runners versus public-hosted runners
  3. Understand the pull_request trigger and the vulnerabilities it creates
  4. Understand the risks coming with script injections and how to prevent them
  5. Efficiently managing secrets and authentication (OpenID Connect)

GitHub secure code scanning (enforcement using trusted code)

One of the most vital features of GitHub source control is the built-in secure code scanning tool, based on CodeQL and Dependabot. Code scanning is available for public and private repositories in a GitHub Enterprise Advanced Security licensed scenario. Code scanning looks for security risks, vulnerabilities, outdated packages, and code threats and generates an alert for each of its detections in order of importance.

From within your GitHub repositories, navigate to the Security tab. Here, you have four different features available:

  • Security policies, where you define how users should report security vulnerabilities
  • Security advisories for viewing or validating security recommendations
  • Dependabot alerts, providing notifications when detecting a vulnerability 
  • Code scanning alerts, providing a list of common vulnerabilities and coding errors
GitHub Security Tab

Opening the Code Scanning alerts section provides the following view:

Code scanning alerts

Integrating CodeQL security scanning relies on a GitHub Action workflow, codesql-analysis.yml, which you add with the steps described below.
From your GitHub repository, navigate to Actions and click New workflow. In the Search Workflows field, search for “security.” Notice that CodeQL Analysis is one of the results in the list.

get started with GitHub Actions

Click Configure, which provides a sample workflow YAML file like the example screenshot below. The file name doesn’t matter as long as it implements these same default steps:

github actions workflow permissions

Click the green Start Commit button on the right to run the actual GitHub Action workflow. It should take only a few minutes.

GitHub Actions: Running a workflow

After the CodeQL workflow is complete, security alerts appear on the Security tab in the Code scanning view:

GitHub Actions: Code Scanning

Self-hosted runners versus public-hosted runners

GitHub-hosted runners are the virtual machines executing the Actions workflows and events. Once a workflow is complete, the GitHub-hosted runner completely resets, guaranteeing the machine is completely clean. It’s impossible to gain access to any information that was running or stored on that machine.

Self-hosted runners are virtual machines that you set up yourself, often to have more control of what’s running inside that virtual machine. You can deploy these runners in your on-premises network or public cloud scenarios. While you might consider them more secure since your security teams run in an environment you control, they’re also vulnerable to security threats. Often, self-hosted runners are not as fleeting as the GitHub-hosted ones. Without continuous updates, information stored on the server—and the tools and packages running on it—become a security risk. 
Next, ensure you don’t use self-hosted runners for public repositories. Actions are available to anyone in a public repository, which might expose a security breach in your self-hosted runners, compromising the rest of your network. You should use the latest versions of the supported operating systems where possible (e.g., Ubuntu Linux 20.04, Windows Server 2022, or macOS Big Sur 11, identified as <OS>-latest):

name: Sample workflow
on:
  push:
	branches: [ main ]
 
jobs:
  Run-npm-on-Ubuntu:
	name: Execute Node NPM on an Ubuntu machine
	runs-on: ubuntu-latest
	steps:
  	- uses: actions/checkout@v3
  	- uses: actions/setup-node@v3
    	with:
      	node-version: '12'

Understand the pull_request trigger, especially the vulnerabilities it creates

Branching and pull requests are common practices in source control. They enable developers to introduce fixes and features into the application source code environment. Typically, you’d require manual approval because of the peer review concept of DevOps, where someone is watching over the code in the branch before accepting a merge into the main branch.

However, there might be situations where organizations might decide to allow GitHub Actions to create and approve such pull requests. The setting to do that is available in your GitHub Organizations’ settings:

From your GitHub profile, select Organizations. Go to Actions > General. Select Workflow permissions.

Here, see the Allow GitHub Actions to create and approve pull requests.

While there may be benefits from enabling this setting, it could be risky to allow GitHub Actions to trigger this Action. If there’s a malicious request coming in to trigger an Action from a pull request, it might break the security of your pipeline, the source code, or something worse. The setting to prevent pull requests from triggering Actions for approvals became available at the beginning of 2022 and was extended in May 2022 to avoid the use of pull requests. This also emphasizes the focus of security within GitHub environments out of GitHub itself.

Understand the risks of script injections and how to protect yourself

Script injections are a vital part of how GitHub runs Actions. Think of these as additional tasks that run as part of your Actions’ workflow to perform “something.” However, these script injections also expose a huge security risk for your environment. Malicious script injections can harm your source code, pipelines, or target environments. GitHub Docs says the following:

When creating workflows, custom Actions, and composite Actions, you should always consider whether your code might execute untrusted input from attackers. This can occur when attackers add malicious commands and scripts to a context. When your workflow runs, those strings might appear as code to execute on the runner.

In general, when using braces ({{ }}) in a GitHub Action, they pick up variable values, which can be system properties or custom ones you define. Typically, workflow execution replaces these values. Using the if statement as part of the run event allows you to perform a specific task when the outcome of the value is true or false.

For example:

name: Check for incidents
on:
  issues:
	type: [ opened ]
 
jobs:
  incident-checks:
	name: Execute Node NPM on an Ubuntu machine
	runs-on: ubuntu-latest
	steps:
  	- run: !
  	  if [[ “${{ github.event.issue.title}}” == *” issue”$* ]]
    	then
      	<action step here, for example curl>

In the sample YAML workflow snippet above, an Action workflow checks for any GitHub Issue containing the word “issue” in its title. For each, it could trigger a certain task. Typically, this would be a curl action to pull up the URL to the GitHub Issue item. However, more vulnerable, harmful actions could easily replace this, such as running an ls command to access data available on the GitHub runner server. Or worse, one might execute a software installation by executing apt get install (or similar).

Efficiently managing secrets and authentication (OpenID Connect)

It should be no surprise that storing secrets as plain text within your source code or as part of your GitHub Actions workflows is a mistake. Luckily, GitHub provides built-in secret detection for many common solutions and cloud platforms, as outlined in the GitHub docs. The security engine is looking for “security patterns,” identifying secrets and connection strings for Adobe, Amazon AWS, Microsoft Azure, and many other platforms.

This protects your secrets and secret strings as GitHub won’t allow you to store these strings inside a GitHub repository. However, applications need secrets to interact with one another or allow your GitHub Actions to communicate with the target environment when running a deployment. While it’s still possible to create secret variables for this authentication, think of service principles when using Azure or a service ID when targeting Amazon AWS. However, you should rely on OpenID Connect to authenticate your Actions with a target cloud environment, as shown in the image below.

Authenticating Actions with OpenID Connect

Perform security hardening

GitHub uses an instrument that attempts to redact secrets that appear in run logs to help prevent accidental disclosure. The redaction instrument looks for exact matches of configured secrets with standard value encodings, such as Base64. However, this redaction isn’t guaranteed because there are multiple ways you can transform a secret value. As a result, there are specific proactive steps and good practices you should follow to help ensure you redact your secrets and limit other risks associated with secrets:

  • Never use structured data as a secret
  • Register all secrets used within workflows
  • Audit the handling of secrets
  • Use credentials that are minimally scoped
  • Audit and rotate registered secrets
  • Consider requiring review for access to secrets

Final thoughts on GitHub Actions security

Security is becoming increasingly important in your organization’s application and software lifecycle. From source code to performing pipeline actions and publishing your workloads to target environments, security should be part of each cycle in the CI/CD process. 

GitHub is a popular platform for managing source code and running pipelines called workflows. Operated by GitHub Actions, it’s possible to integrate several security protection mechanisms as part of your software chain management.
Integrating security as part of your GitHub Actions is vital in overall application lifecycle management. To learn where Mattermost, a provider of powerful developer collaboration tools, can help you optimize this process, sign up here for a free trial of our solutions.

This blog post was created as part of the Mattermost Community Writing Program and is published under the CC BY-NC-SA 4.0 license. To learn more about the Mattermost Community Writing Program, check this out.

Read more about:

GitHub security

Peter De Tender is an IT expert with over 24 years of experience. He's currently a public speaker, technical writer, book author, and publisher. Peter started shifting to cloud technologies in 2012 and quickly jumped onto the Azure platform as a cloud solution architect and trainer. In 2019, Peter became an Azure trainer within Microsoft. He was an Azure MVP for five years and a Microsoft Certified Trainer (MCT) for more than 12 years.