Skip to content

A GitHub token does not authenticate requests to github.com web pages

This is a finding

Something I found or learned, or a discovery that cost time once: cheap by design, and true of a moment rather than in general.

An Authorization header sent to a github.com web page is ignored. A repository you can read in the browser, whilst signed in, answers a token-bearing request with the same 404 it gives an anonymous stranger, and the token changes nothing about the response. Tokens authenticate the REST API1, and separately the raw content host, not the website.

The trap is that 404 rather than 401 is what a private resource returns, deliberately, so that its existence is not disclosed. That means the response to a request you thought was authenticated is byte-identical to the response for a repository that was deleted, renamed, or never existed. Nothing in it says "your credential was not used".

Evidence

Verified empirically on 2026-09-18, against a repository in an organisation the token's account can read:

$ curl -o /dev/null -w '%{http_code}\n' https://github.com/ORG                       # no token
404
$ curl -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" \
    https://github.com/ORG                                                           # token
404
$ curl -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" \
    https://api.github.com/orgs/ORG
200

The first two responses being identical is the whole finding. The API accepts the same token on the same host name suffix, as does raw.githubusercontent.com, which returns the file body for a private path given the same header.

The API also discriminates correctly rather than merely answering, which matters if you intend to check anything with it. A path that exists returns 200 and a path that does not returns 404, on the same repository and with the same credential, so a negative answer is informative:

$ curl ... "https://api.github.com/repos/ORG/REPO/contents/README.md?ref=BRANCH"
200
$ curl ... "https://api.github.com/repos/ORG/REPO/contents/NOPE.md?ref=BRANCH"
404

Why it matters

Anything that follows a github.com URL on your behalf inherits this. A link checker, a documentation build, a script that verifies references: adding a token to it is a change that looks like it should work, produces no error, and leaves every private link reported as broken. The time is lost afterwards, on the assumption that the token is at fault, checking scopes and single sign-on authorisation on a credential that was never sent anywhere that would have read it. This is the half of linkspector ignores the httpHeaders in its config that would still bite after the bug in that tool is fixed.

Two things follow. First, check the API rather than the page. The ordinary web URLs map onto it mechanically, which is a rewrite a link checker can be configured to do:

Web URL API equivalent
github.com/ORG/REPO and most paths under it api.github.com/repos/ORG/REPO
github.com/ORG/REPO/{blob,edit,tree}/REF/PATH api.github.com/repos/ORG/REPO/contents/PATH?ref=REF

The mapping is not total. A pull request, a release tag or a discussion needs its own endpoint, so a rewrite that silently passes what it does not recognise is worse than one that refuses it.

Second, probe before you conclude. One request to api.github.com/orgs/ORG separates the two cases the 404 conflates, because a credential problem surfaces there as 401 Bad credentials whilst a working credential returns the organisation. Run it before reporting anything as missing, and the difference between "this link is dead" and "I am not who I think I am" stops costing an afternoon.

The following pages link to this page:


  1. GitHub: Authenticating to the REST API, last modified 2026-09-18 ↩