GitHub API Key Leak, May 16 2016

On May 16, 2016, we received a report that our Travis build was leaking credentials (specifically, an API key) for the GitHub account @letsencryptbot. We immediately removed that account’s access, changed its credentials, and began auditing our codebase for unauthorized commits.

Our investigation has concluded, and with GitHub’s help we’ve determined that the leaked credentials were never improperly used. No code was improperly modified, and no subscriber or other confidential information (aside from the leaked API key) was exposed.

As a result of our discussions about this incident we’ve decided not to continue granting privileges to @letsencryptbot, we’ve reviewed existing GitHub privilege grants, and we’ve put new policies in place for GitHub privilege review.

We’d like to thank Tobias Sachs for bringing this to our attention via our email address.

It is Let’s Encrypt’s policy to disclose information about incidents like this as soon as we’ve completed our intial investigation. We are committed to this practice in the interest of furthering transparency in our industry.

Further Technical Details

In, we introduced a tool, github-pr-status, that runs in Travis and comments on GitHub PRs with detailed error statuses. It used this command to extract a credentials file from an encrypted file:

openssl aes-256-cbc \
-K $encrypted_53b2630f0fb4_key -iv $encrypted_53b2630f0fb4_iv \
-in test/github-secret.json.enc -out /tmp/github-secret.json -d

Travis echoes the commands in .travis.yml, but without expanding the arguments. In, we refactored the before_install command into a shell script, That shell script sets the xtrace option, which echoes commands after expanding the arguments. That means that as of October 1, 2015, we were echoing the relevant secret in the Travis logs, allowing anyone to decrypt the copy of github-secret.json.enc in the Boulder repo and get an API key for the @letsencryptbot user on GitHub. The login password for @letsencryptbot was not leaked, only the API key.

However, the API key had repo:status and public_repo scope. The public_repo scope allows pushing to repos on which the authenticating user has write access. The @letsencryptbot account had write access to the Boulder repo, so there was a risk that someone could abuse the @letsencryptbot account to push unauthorized commits. We originally provided the public_repo permission to the account so that it could post comments on PRs with details of failing tests. See this discussion for more information about why it’s necessary to have public_repo permissions to post a comment: