Repository navigation
Conversation
Currently, running `gh auth logout` does not revoke the underlying OAuth token (`gho_*`). This is dangerous as the token may have been compromised, has a 1-year TTL, and there is a reasonable expectation that logging out revokes the users current session. The currently documented solution of manually revoking the GitHub CLI app is rather manual and not the best experience. Add support to explicitly revoke the token to close this gap. This also revokes the old token when the user replaces their token via `gh auth login` or `gh auth refresh`. Fixes #9233
|
Thanks for your pull request! This is a large change (803 lines across 19 files) that doesn't reference a Large feature PRs require prior discussion in an issue before implementation — this helps the team assess whether the feature aligns with the project's direction before significant effort is invested. Please open an issue to discuss this feature first. This PR will be automatically closed in 2 days if requirements are not met. Full contribution requirements
|
There was a problem hiding this comment.
🟡 Changes recommended
Logout becomes network-dependent, while replacement flows can leave invalid credentials or lose failed revocations.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds OAuth token revocation and credential cleanup to authentication flows, addressing #9233 and #13111.
Changes:
- Revokes replaced or logged-out GitHub CLI OAuth tokens.
- Removes matching keyring and Git credential-helper entries.
- Prevents sensitive revocation requests from HTTP debug logging.
File summaries
| File | Description |
|---|---|
pkg/cmd/auth/shared/revoke.go |
Adds shared conditional revocation logic. |
pkg/cmd/auth/shared/revoke_test.go |
Tests revocation eligibility and errors. |
pkg/cmd/auth/shared/login_flow.go |
Revokes replaced tokens during login. |
pkg/cmd/auth/shared/login_flow_test.go |
Tests login revocation. |
pkg/cmd/auth/shared/gitcredentials/updater.go |
Adds username-aware credential rejection. |
pkg/cmd/auth/shared/gitcredentials/updater_test.go |
Tests selective credential removal. |
pkg/cmd/auth/refresh/refresh.go |
Revokes tokens replaced by refresh. |
pkg/cmd/auth/refresh/refresh_test.go |
Tests refresh revocation. |
pkg/cmd/auth/logout/logout.go |
Adds remote and local credential cleanup. |
pkg/cmd/auth/logout/logout_test.go |
Tests logout cleanup behavior. |
pkg/cmd/auth/login/login.go |
Wires revocation into login paths. |
pkg/cmd/auth/login/login_test.go |
Tests non-interactive replacement. |
internal/config/config.go |
Removes stale keyring credentials. |
internal/config/auth_config_test.go |
Tests keyring cleanup. |
internal/authflow/revoke.go |
Implements the revocation API request. |
internal/authflow/revoke_test.go |
Tests revocation responses. |
go.mod |
Promotes HTTP logging dependency. |
api/request_test.go |
Tests suppressed HTTP logging. |
api/client.go |
Adds per-request log suppression. |
Review details
- Files reviewed: 19/19 changed files
- Comments generated: 4
- Review effort level: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if len(oauthTokens) > 0 && !ghauth.IsEnterprise(hostname) { | ||
| httpClient, err := opts.PlainHttpClient() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| for _, token := range oauthTokens { | ||
| if err := opts.RevokeToken(httpClient, hostname, token); err != nil { | ||
| return fmt.Errorf("failed to revoke authentication token: %w", err) |
| // Reject removes credentials for a hostname and optional username from the git credential helper. | ||
| func (u *Updater) Reject(hostname, username string) error { |
| func RevokeOAuthTokenIfChanged( | ||
| httpClient *http.Client, | ||
| hostname, previousToken, newToken string, | ||
| revokeToken func(*http.Client, string, string) error, |
| return nil | ||
| } | ||
|
|
||
| return revokeToken(httpClient, hostname, previousToken) |
|
@BagToad: respectfully, it would be nice to get some kind of feedback. The two issues I referenced ([1][2]) don't have I am happy to make changes, but I don't know what the issue is—you haven't given me any clear feedback.
This isn't some drive-by thoughtless LLM prompt. I went over a few iterations to make sure the flows continued to work as expected. I hope my prior public contributions to other open source projects in the past make clear I'm legitimately trying to help. This experience of having a PR closed 10 minutes after opening with zero feedback seems to send a very clear message. |
|
Hi @sds,
It's not a matter of your intentions, which I'm sure are great, it's just that there are a lot of people (or LLMs as proxies to people!) who want to help contribute code to
The feedback is that we want people to discuss whether to do something and how to do it in on issues before contributors before time is spent unnecessarily. Ideally people discover this in our contributing guidelines before spending their own time. Based on your response here though, it's probably reasonable to update our bot response to explain a little more in detail why we mark things Next week, let's restart the discussion on #9233. In particular I want to make sure we're thinking about:
It's nice to have your code to refer to as a deeper dive into the solution. I appreciate you highlighting that API; I was aware of the API but had dismissed it based on it relating to app owners revoking a token, without realising that in this case, ownership is determined by having the client id and secret. |
|
Thanks for the detailed response—I appreciate it. I understand the intention behind Sounds good to discuss more comprehensively in #9233. Some quick responses (by no means exhaustive or intended as a final statement—merely additional thoughts) to carry over to that discussion:
Fair point. I don't have data on how the CLI is used by others, I can only speak for how I've seen it used myself and by others at my org, and I've not seen this. That said, I would say principle of least surprise still applies—when I log out, I expect my token to be revoked if the very tool I'm using generated it, regardless of where else I've decided to inject the token.
Yes, this was missed in my own review and I appreciated the automated review pointing it out. It of course should be addressed.
Relating to the prior point, this case will silently fail. Agree we should at least output some kind of warning that the token was not owned by the GitHub CLI app—this was an oversight. Relevant code
Yes, I realize I left this out in the PR description even though I had intentionally (attempted to) address it. The implementation specifically only does this with OAuth tokens—it does not attempt to revoke other kinds of tokens (relevant code). However, I did not manually verify this particular case. Thanks again for the response. Hope we can find a path forward. |
|
One additional point, tangentially related to this PR, but perhaps worthy of its own discussion outside of this repo. During an earlier implementation, I was using the new revocation API launched in March 2026. However, that API has an interesting behavior where if you provide an OAuth token to revoke, it will revoke ALL tokens associated with the GitHub CLI app (I did not test this with other apps, so perhaps it's only for the GitHub CLI app). Yes, you read that right. If you have two laptops, you can quickly verify by logging in on each of them separately via curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
https://api.github.com/credentials/revoke \
-d "{\"credentials\":[\"$(gh auth token)\"]}"If you run This led me to use the other API for token revocation for the above PR, which is in hindsight the safer approach given the revocation API doesn't check/enforce token ownership before revocation. |
Description
Currently, running
gh auth logoutdoes not revoke the underlying OAuth token (gho_*). This is dangerous as the token may have been compromised, has a 1-year TTL, and there is a reasonable expectation that logging out revokes the users current session. The currently documented solution of manually revoking the GitHub CLI app is rather manual and not the best experience.Add support to explicitly revoke the token to close this gap.
This also revokes the old token when the user replaces their token via
gh auth loginorgh auth refresh.How did you test this change?
There are tests included in the PR, but here is a step-by-step guide for how you can manually verify.
Note: this is written so you can test each claimed fix individually with the actual CLI. There are some redundant steps but it allows you to test any of these flows in isolation.
Case 1: Verify
gh auth logoutrevokes the old tokenBefore starting, clear your existing token using the latest
ghrelease:gh auth logout.Case 2: Verify
gh auth loginrevokes the old token if already authenticatedBefore starting, clear your existing token using the latest
ghrelease:gh auth logout.Case 3: Verify
gh auth refreshrevokes the old tokenBefore starting, clear your existing token using the latest
ghrelease:gh auth logout.Summary
I ran these tests myself locally, and they returned the expected results. If you run them with the currently
ghrelelase, any step where you test the old token (e.g.GH_TOKEN=$old_token ./bin/gh api user) will succeed instead of fail.Key points
I did not consider this a pure security issue given it has existed for so long, but I do see it as a usability issue, and addresses the principle of least surprise.
Notes for reviewers
Fixes #9233
Fixes #13111
Authorship and follow-up
Who wrote this:
Who answers review comments: