Your GitHub OAuth App Has Been Accepting Wildcard Redirects the Whole Time. GitHub Just Told You, in a Blockquote.
GitHub shipped three OAuth changes on August 14. Two of them are the kind of thing you skim and feel good about: OAuth apps can now register up to 10 redirect URIs, and they can opt into eight-hour access tokens backed by six-month refresh tokens. The third is a blockquote near the bottom of the changelog, and it is the only one that describes your app as it exists right now.
Here is the sentence, verbatim:
Apps with only one redirect URI have wildcard matching enabled. This is a legacy behavior of GitHub that is now visible and controllable. Please review your apps and disable wildcard matching if you do not need it. This applies to all OAuth apps and any GitHub App that had a single redirect URI registered.
Read that as a disclosure rather than a feature note. Wildcard matching was not something you turned on. It was on. It has been on for every OAuth app with one callback URL, and per GitHub's docs the cutoff is apps registered before August 3, 2026. The changelog is the first time most people could see it, because it is the first time it became a setting rather than a behavior.
What wildcard matching actually permits
GitHub's own description: when wildcard matching is enabled, an authorization code, and therefore the user, can be sent to any URL that matches a subdomain or an additional path off of the registered redirect URI. The host excluding subdomains and the port must match exactly. The path must reference a subdirectory of the callback URL.
So if you registered https://app.example.com/auth/callback, the code can be delivered to a deeper path under /auth/callback, and to subdomains of your registered host.
GitHub's warning about the risk is one line: wildcard matching can be abused if the site being redirected to does not have strong control over its routes, and hosting user content is the example they give.
That example is doing a lot of work. Think about what typically lives on a subdomain of an app's domain, or under a path you do not audit as tightly as /auth:
- A docs site on a CMS somebody else administers
- A status page from a third-party vendor
- A marketing site with an open redirect in a campaign tracker
- A preview or branch deploy on
*.yourapp.com - Anything serving user-uploaded HTML
An authorization code arriving at any of those is a bad afternoon. The classic version of this bug is an open redirect somewhere in your own estate chained into an OAuth callback, and wildcard matching widens the target from one exact URL to a family of them.
Why this is worth ten minutes today rather than a shrug
I want to be honest about severity, because I think the temptation is to read "default-on wildcard redirects" and picture something worse than this is.
This is not a remote code execution. It is not exploitable on its own. It requires you to have something redirect-y or user-controlled inside the matching space, and plenty of small apps have exactly one domain, one path, and nothing else on it. If that is you, the practical risk today is close to zero.
What makes it worth attention anyway is that the risk is a function of your domain layout, which changes without anyone thinking about OAuth. You register the callback in year one, when app.example.com is the only thing you own. In year two you add a docs subdomain on a hosted platform. In year three you turn on preview deploys. Nobody involved in those decisions was thinking about an authorization code, because nobody knew the redirect was permissive. The setting was invisible.
That is the general shape of the problem, and it is why I would rather do the audit while it is on my mind than file it behind a maybe.
What I would actually do
Ten minutes, roughly:
- Open your OAuth app settings on GitHub. Do the same for any GitHub App you registered with a single callback URL. If you have apps under an organization as well as your personal account, both.
- Look at the wildcard checkbox on each redirect URI. If you do not need subdomain or subpath matching, uncheck it. Most single-tenant apps do not need it.
- Before unchecking, confirm nothing depends on it. Multi-tenant products that redirect to
customer.yourapp.comare the real use case for this feature, and turning it off will break their login. If that is you, keep it on for that URI specifically and now register the exact URIs you can enumerate, since you get up to 10. - While you are in there, look at the expiring-tokens option. Short-lived tokens are on by default for new applications; an app you registered years ago is issuing tokens that do not expire. Opting in is a real improvement, but check your auth SDK supports the refresh flow before you flip it, because GitHub explicitly notes you may need to disable it while you update the SDK.
Point four is the one I would not rush. An eight-hour access token with a six-month refresh token is better than a token that lives forever, but a token that lives forever is better than a login loop in production at 2am. Test the refresh path first. You can roll it out gradually by including the offline_access scope in your auth request rather than forcing it at the registration level.
The honest take
The security finding here is mild. The process finding is not.
A default that nobody chose was in place for years, could not be inspected, could not be changed, and became public in a bullet under two friendlier announcements. I do not read that as GitHub burying it; the changelog says plainly "please review your apps," which is more than a lot of vendors do. But the disclosure only landed because the feature work made it disclosable, and that is a bad dependency. If GitHub had never built configurable wildcard matching, nobody outside GitHub would know the behavior existed.
The transferable lesson is about how you read changelogs, and I say this as someone who skims them like everyone else. The interesting sentences are rarely in the headline features. They are in the migration notes, the legacy-behavior asides, and the paragraphs that begin with "existing apps." Those are where a vendor tells you what is true about the thing you already deployed, as opposed to what is new about the thing you have not.
Set a rule for yourself: when a platform you build on ships a new setting, ask what the value was before the setting existed. That question would have caught this one.
Author
Lukas
@lukcombinator