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: sarahhodne/test-project-1
and travis-repos/test-project-1
.
sarahhodne/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
sarahhodne/test-project-1
, which you can see at
https://github.com/sarahhodne/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.