Security Advisory: Encrypted Environment Variables

We’ve had a feature for a while that allows you to encrypt environment variables in your .travis.yml as a way to include credentials that can be used in your builds without making them readable by everyone with access to your .travis.yml.

Originally these variables weren’t available in pull request builds, since anyone could submit a pull request against the repository and print out the variables. Later we changed this a bit to allow encrypted environment variables in pull requests from branches on the same repository. If someone has access to push to branches they already have access to create builds that can see the encrypted environment variables.

About 2 months ago, on April 11th, 2016, we were alerted about an issue with the way we determined if a pull request was coming from the same repository or a fork. Due to the way GitHub stores forks internally, there was a way to make a pull request with commits from a fork, but make it look like it came from the main repository.

This then meant that you could fork a repository, make a commit that reveal the encrypted environment variables, submit a pull request and thereby get access to encrypted environment variables.

As of April 14th, 2016, we had a patch deployed to production that no longer made this exploit possible. We then ran a query against our database to find pull request that had used this exploit, but found no evidence that it had been used to gain access to encrypted environment variables.

We would like to thank ChALkeR for responsibly disclosing this to us and for the help in getting this resolved.

Technical details

In order to explain how this exploit worked, we’re going to use two example repositories: henrikhodne/test-project-1 and travis-repos/test-project-1. henrikhodne/test-project-1 is a fork of travis-repos/test-project-1. It’s also useful to know some pull request terms to understand this: The base of the pull request is the “target” of the pull request, where you want it to be merged into. This would often be master on an upstream repository, but could be any branch. The head of the pull request is the “source” of the pull request. Usually this would be a feature branch, but it could be anything that’s “commitish”, which we’ll get back to.

When you fork a repository on GitHub, instead of copying the entire repository they save space by sharing the commit data between the repositories. This has the perhaps unexpected side effect of making commits made to forks available through the upstream repository as well, although you wouldn’t get them as part of a clone. For example, I pushed a commit 6e940c3 to henrikhodne/test-project-1, which you can see at https://github.com/henrikhodne/test-project-1/commit/6e940c3. But you can also go to https://github.com/travis-repos/test-project-1/commit/6e940c3 and see the same commit.

Since the head of a pull request can be anything commitish (which is a term used often in Git documentation to mean anything that can resolve to a commit reference, including commit SHAs (6e940c3), branch names (new-cool-feature) and more advanced things like master@{yesterday}), you can then create a pull request on travis-repos/test-project-1 that tries to merge travis-repos/test-project-1@6e940c3 into travis-repos/test-project-1@master. If you look at the pull request with the GitHub API (which is what Travis CI uses), the API reports the “head commit” as being a part of the main repository, which then causes Travis CI to include encrypted environment variables.

We’ve now worked around this issue by only allowing access to encrypted environment variables to pull requests where the head reference is also on the list of known branches for the repository, so commit SHAs and other non-branch references would no longer get access to encrypted environment variables.