Github read-only API Token

How to create a Github read-only API token

Ah, so you’re maintaining some kind of app which relies on github’s API and you want to grant it an access token in order to view a repository’s issues, code, etc.

But you don’t want that token to be an almighty read/write one, do you?

That is a serious security vulnerability if someone succeeds in acquiring your token.

Fear not, I’ve got you covered. In this article, I’ll show you how to create a read-only api token for your apps.

The short answer

You cannot. Github doesn’t have support for this, although it’s something which has been requested since 2015 and many users have complained since then.

But don’t worry. There is a feasible workaround and there are some what appear to be workarounds, which can end up compromising your application & all repositories on Github.

The use-case

The way I discovered the workaround is by stumbling upon this bump myself. What I wanted to achieve is a suite of CI tests for todocheck, which verify that there are no regressions when integrating the tool with Github.

To achieve this, I don’t want to simply mock the HTTP responses which the Github API returns. I want to actually verify the integration works, even if at some point, the API suddenly changes.

Hence, I’ve created a suite of test fixtures in a Github organisation which I use in my tests to verify the integration. To access the repositories in it, I’ve issued an API token, following the standard procedure, and added it as a secret to my repository.

This secret is then used in my github workflow and passed as an environment variable to todocheck.

The problem

Well, everything looks nice and dandy so far, especially given that I’ve followed the approach described by Github for using secrets in my workflows.

The only issue is that all my repositories are now compromised. This secret is passed as an environment variable to the tests ran from the given pull request and all someone has to do is modify the tests’ source code to use the secret in a malicious way.

Now, Github dilligently tries to stop one from doing that by hiding the secret if it’s printed out:

This might be enough to convince you that you’re safe & secure, but unfortunately, you’re wrong.

Although Github hides the secret when printed to the terminal, someone could still use it inside the application. One can use this to make http calls to github’s api to e.g. delete all your repositories.

You don’t need to know what the secret is to use it. 😿

Here’s what doesn’t work

Now that you’ve identified that there is an issue, you might want to start researching how to fix it.

What’s very disturbing is that all the advice found on the web, except the one in this article 😉, doesn’t really solve the problem for this use case.

Here’s what I encountered along the way, tried and failed.

Running the workflow from the master branch

In Github’s official blog post, they suggest using pull_request_target to make your secrets safe.

The idea is that the untrusted pull request will run the workflow from the repository’s master branch, not the one in the untrusted PR.

This sounds like a feasible solution, especially given that Github recommends it, but again, doesn’t work for this use case.

Although the workflow is run from the master branch, the code is run from the pull request. Hence, one can use the source code of the tests to, again, maliciously use the secret.

Running the code from the master branch

Alternatively, in the same article, they suggest using workflow_run to not only use the workflow from the master branch, but also the source code from it.

However, if you just run the tests from the master branch, then the CI tests on any pull requests will succeed. So you need to pass in the build artifact from the untrusted PR to the code from the master branch.

In fact, I did attempt to do this and believed for a while that this will solve the issue.

This, again, won’t work because although a malicious actor cannot modify the test suite, they can modify the build from their PR to use the secret as needed.

As you can see, Stackoverflow advice and even Github’s official advice might lead you to compromise yourself pretty badly.

Here’s what works

I didn’t discover the best advice for addressing this in any public github documentation or blog post. Not even a stackoverflow answer.

I found it in a small remark made by someone on a very long thread about the issue. The guy’s remark didn’t even aim to suggest a workaround but to express his frustration:

I had to scroll A LOT to find this one.

The solution was to create a new dummy Github account and give it read-only access to the repositories I want to integrate with.

Next, I generated a new API token from this account and used that in my workflows.

Now, even if someone uses this API token in a malicious PR for todocheck, I don’t care. The API token doesn’t have write access to any of my repositories.

To save you some time, you can invite a read-only contributor to a (private) repository from:

Settings -> Manage Access -> Invite teams or people


And there you go.

How to achieve Github’s missing and so-much demanded feature.

Hopefully, this helped you solve a similar nasty use-case in your own project.

What’s more, it prevented you from making an ill-informed “security fix” which leaves you confident that everything is nice and dandy. But actually, you might’ve compromised all your Github projects.

If you’ve stumbled upon any other solutions, working or not, let me know in the comment section below. I’ll be sure to keep the article updated for future reference!

Site Footer