You know what Git is: a distributed version control system used to track collaborative work. While Git is daily routine for engineering teams, you may be less familiar with its security concerns. A single mistake when it comes to Git security can be extremely costly for both your time and money.
In this article, you’ll learn some of Git’s key security risks and how to overcome them.
Carelessly Committed Secrets
Committed secrets are one of the most exploited attack surfaces. These secrets can be any sensitive data, but the most damaging typically involve unauthorized access to passwords and other security credentials.
I once worked for a large tech company that contributed to open-source projects. A previous employee there accidentally committed AWS credentials (an access key ID and secret key) to an open source Git repository.
The result? Bots constantly monitor commits pushed to public repositories, and one discovered the employee’s commit contained a substring that matched the pattern for AWS credentials. The bot immediately sucked up the credentials, used them to spin up a large number of EC2 instances, and proceeded to mine cryptocurrency until it was caught. In just a few hours, it racked up over $40,000 in AWS charges.
While bots like this can cause significant damage, humans with malicious intent can be even worse. What if you had sensitive financial or medical information stored in your AWS account, and the hacker used your credentials to quickly exfiltrate data? Committed secrets are a serious concern.
Fortunately, there are ways to protect yourself. Tools like git-secrets can scan your commits automatically, protecting against errors like the AWS credentials one from earlier. git-secrets also allows you to set up regular expression patterns to check for upon commit, rejecting it if a pattern is matched.
While git-secrets offers great protection against accidental committing of secrets, you should never store your secrets in plain text—regardless of where they are. Using an encryption mechanism like AWS Key Management Service provides more granular control over which environments can decrypt and use secrets, regardless of type.
Catching secrets before they’re committed is vital. Even if you delete sensitive files or hard-coded credentials, they still exist in Git’s history. The main purpose of Git is to collaborate on file versioning, so it’s unsurprisingly hard to purge this history as a result. While you could change the Git graph to purge historical versions of committed files, this isn’t recommended and should generally be avoided.
The Access Control Challenge
Access-control lists (ACLs) limit permissions to different types of computing resources. The distributed nature of Git means it doesn’t have native, granular access control.
This is where Git repo hosts come in. Most people don’t just use Git; they use it alongside a repo host like GitHub or GitLab. These repo hosts generally have multiple layers of configurable access, including:
- Making an entire repo public or private
- Managing groups of people with the same permissions
- Giving specific people outside an organization direct access to a specific repo
- Managing more granular user permissions (like reading, writing, and administrating)
There are many best practices related to user permissions, but you should implement these at minimum:
- Repos should be private whenever possible
- Users should be managed in groups to minimize errors in specific user settings and make adding or removing users easier
- Users should have the bare minimum of permissions necessary to do their jobs in order to prevent mistakes. If a user only needs to read code, they should not be given write permissions
If you’re given write permission to a Git repository, the repository owner trusts that you won’t commit anything malicious. Normal commits are not cryptographically signed as a result, making it easy to impersonate someone else if you have write permission. This is how easy it is to impersonate a different developer—let’s call him “Matt”:
git config --global user.email "email@example.com"
git config --global user.name "Matt"
Now when you commit, you’ll be committing as Matt! Git’s default setup trusts that you are who you say you are. Generally this setup is fine, but what if someone with malicious intent gets access to your credentials? They want to start committing harmful code, but they don’t want to be caught. In this system, it’s easy to frame someone else—like Matt!
Cryptographically signing your commits with a GPG key can solve this problem. Using GitHub as an example, you’d first generate a GPG key on your local machine. Then, you’d export the public key in armoured ASCII format, and copy the output to GitHub via User->Settings->SSH and GPG keys.
Once you’ve told your repo host your public GPG key, you can sign your commits by using the -S flag:
git commit -S -m "Commit message."
Now GitHub can verify that it was really your commit! You can also tell Git that you want to use a cryptographic signature every time by adding the appropriate flag in your global settings:
git config --global commit.gpgsign true
While Git is designed for distributed workflows, we’ve already discussed how using centralized repo hosts (e.g., GitHub) can improve permission handling. This is also true when it comes to enforcing Git workflows.
By protecting branches from unauthorized use, specific workflows can be enforced to prevent mistakes. On GitHub, you can set a branch protection rule on any branch. You can fine-tune the following settings from here:
- Require pull request reviews before merging
- Require status checks before merging
- Require conversation resolution before merging
- Require signed commits
- Require linear history
- Include administrators
- Restrict who can push to matching branches
- Allow force pushes
- Allow deletions
Here’s a concrete example workflow that you can enforce with branch protection rules:
- Create test and production branches that require pull request reviews and signed commits before merging.
- Require that pull request reviews are approved by specific senior developers.
- When creating a new feature, developers should create a new development branch. When development is complete, the developer should open a pull request to the test branch.
- Other developers should review the pull request. Once the review is complete, the feature branch can be merged to the test branch.
- Integration tests should be run on the test branch.
- Once all tests pass and management agrees on a release to production, a new pull request can be generated against the production branch. When management approves this request, an automated deployment pipeline is run to deploy all new tested features to production.
Securing the Broader Ecosystem
Git doesn’t exist in a vacuum. People and machines have to clone Git repositories to use them, and attackers can steal source code linked to your organization by accessing Git through trusted computers. Here are some systems that need to be secured to truly protect your Git repos:
- Developers’ computers. For optimal security, encrypt hard drives on development machines in the event they’re lost or stolen.
- Secure repo host (GitHub, GitLab, etc.) accounts with multi-factor authentication.
- Developers’ personal accounts should never be granted access to company resources.
- Developers should be given accounts managed by group permissions; this allows for easy access removal at the global level in the event they leave the organization.
- If you provide a service account to a repo backup service, make sure you limit permissions to read-only for backups. For restore functionality, an entirely new repo is generated.
- Continuous integration and continuous deployment (CI/CD) tools are often given service account permissions to clone repositories to run builds, test, and deploy your code. Make sure your CI/CD pipelines communicate via HTTPS, and only a minimal amount of developers have access to CI/CD servers.
Backups Mitigate Risk
Attackers can subvert and steal your Git data in many different ways. To protect this data, you should maintain multiple layers of security risk mitigation—Git backups can offer that.
By periodically snapshotting your Git repos and storing them securely, you can ensure that even subverted repos are restored at a good checkpoint—even if someone accidentally pushes secrets.
Rewind Backups for GitHub is a simple and secure provider of Git repo backups if you use GitHub, GitLab, or Bitbucket as your repository host. Rewind automatically makes backup copies of your repos daily, which can then be easily restored with the click of a button. Backups include more than just code: Rewind can secure all associated metadata, including issues, commits, milestones, wikis, and more. Start mitigating your risk today with a free trial of Rewind Backups for GitHub (Formerly BackHub).