|
| 1 | +# Case Study: GitHub OAuth Token Invalidation and Prevention Strategies (Issue #26) |
| 2 | + |
| 3 | +## Executive Summary |
| 4 | + |
| 5 | +This case study investigates the GitHub OAuth token invalidation issue experienced when using `gh-setup-git-identity` across multiple environments. The investigation reveals that this behavior is a documented GitHub security feature, not a bug. When more than 10 OAuth tokens exist for the same user/application/scope combination, GitHub automatically revokes the oldest tokens. |
| 6 | + |
| 7 | +This document explores the root causes, reconstructs the timeline of events, and proposes solutions including potential modifications to `gh-setup-git-identity` to mitigate token accumulation. |
| 8 | + |
| 9 | +## Issue Description |
| 10 | + |
| 11 | +**Issue URL:** https://github.com/link-foundation/gh-setup-git-identity/issues/26 |
| 12 | + |
| 13 | +**Related Analysis:** https://github.com/link-assistant/hive-mind/pull/1022 (Issue #1021) |
| 14 | + |
| 15 | +**Reported Behavior:** |
| 16 | +> At the same time I was executing `gh-setup-git-identity` command on the remote server. And for some reason executing `gh-setup-git-identity` remotely did destroy GitHub Auth session locally in docker. That is strange. |
| 17 | +
|
| 18 | +When running `gh-setup-git-identity` on multiple machines, authentication sessions on other machines become invalid. This creates a "musical chairs" effect where the most recent authentication causes the oldest one to fail. |
| 19 | + |
| 20 | +## Root Cause Analysis |
| 21 | + |
| 22 | +### Primary Root Cause: GitHub's OAuth Token Limits |
| 23 | + |
| 24 | +From the [official GitHub documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/token-expiration-and-revocation): |
| 25 | + |
| 26 | +> "There is a limit of ten tokens that are issued per user/application/scope combination, and a rate limit of ten tokens created per hour. If an application creates more than ten tokens for the same user and the same scopes, the oldest tokens with the same user/application/scope combination are revoked." |
| 27 | +
|
| 28 | +### Secondary Cause: gh auth login Token Accumulation |
| 29 | + |
| 30 | +When `gh auth login` is executed (which `gh-setup-git-identity` uses internally), it: |
| 31 | + |
| 32 | +1. Creates a new OAuth token through GitHub's device code flow |
| 33 | +2. Stores the new token in the local credentials store |
| 34 | +3. **Does NOT revoke the previous token** on GitHub's side |
| 35 | + |
| 36 | +From [GitHub CLI Issue #9233](https://github.com/cli/cli/issues/9233): |
| 37 | +> "Whenever an already logged-in user runs a gh auth login or gh auth refresh, a new OAuth app token is generated and replaces the previous token in the credentials store. The problem is that the old token is not revoked during this process." |
| 38 | +
|
| 39 | +This is currently a known limitation and marked as "blocked" because the GitHub platform doesn't provide an API for the CLI to revoke its own tokens. |
| 40 | + |
| 41 | +### How These Combine to Cause the Issue |
| 42 | + |
| 43 | +``` |
| 44 | +Timeline of Token Accumulation: |
| 45 | +
|
| 46 | +Machine A (Docker) Machine B (Server) Machine C (Local) |
| 47 | + | | | |
| 48 | + v v v |
| 49 | + Token #1 Token #2 Token #3 |
| 50 | + | | | |
| 51 | + v v v |
| 52 | + Token #4 Token #5 Token #6 |
| 53 | + | | | |
| 54 | + v v v |
| 55 | + Token #7 Token #8 Token #9 |
| 56 | + | | | |
| 57 | + | v | |
| 58 | + | Token #10 | |
| 59 | + | | | |
| 60 | + | v | |
| 61 | + | gh-setup-git-identity | |
| 62 | + | (creates Token #11) | |
| 63 | + | | | |
| 64 | + v v v |
| 65 | + Token #1 Token #11 Token #3 |
| 66 | + REVOKED! (newest - valid) (still valid) |
| 67 | + | | | |
| 68 | + X | | |
| 69 | + Machine A | | |
| 70 | + auth FAILS! | | |
| 71 | +``` |
| 72 | + |
| 73 | +## Timeline of Events |
| 74 | + |
| 75 | +Based on the log file from https://gist.github.com/konard/a430347f7f9aff41b7e0c64a7289712a: |
| 76 | + |
| 77 | +| Timestamp (UTC) | Event | Token State | |
| 78 | +|-----------------|-------|-------------| |
| 79 | +| 09:05:36 | `solve` command started in Docker | Token valid | |
| 80 | +| 09:05:42 | GitHub auth check skipped (`--no-tool-check`) | Token valid | |
| 81 | +| 09:05:55 | Git push successful to fork | Token valid | |
| 82 | +| 09:06:02 | PR created successfully | Token valid | |
| 83 | +| ~09:06-09:12 | (Unknown) Remote server runs `gh-setup-git-identity` | **New token created** | |
| 84 | +| 09:12:59 | Git push fails: "Invalid username or token" | **Token revoked** | |
| 85 | +| 09:13:06 | `gh auth status` confirms token invalid | Token invalid | |
| 86 | + |
| 87 | +The token was valid at 09:05:55 (successful push) but became invalid by 09:12:59 (approximately 7 minutes later), indicating an external event caused the revocation. |
| 88 | + |
| 89 | +## Evidence from Logs |
| 90 | + |
| 91 | +### Successful Authentication (09:05:55) |
| 92 | +``` |
| 93 | +[2025-12-28T09:05:55.246Z] [INFO] Push exit code: 0 |
| 94 | +[2025-12-28T09:05:55.246Z] [INFO] Push output: remote: |
| 95 | +remote: Create a pull request for 'issue-133-434c3df37b90' on GitHub by visiting: |
| 96 | +``` |
| 97 | + |
| 98 | +### Authentication Failure (09:12:59) |
| 99 | +``` |
| 100 | +Exit code 128 |
| 101 | +remote: Invalid username or token. Password authentication is not supported for Git operations. |
| 102 | +fatal: Authentication failed for 'https://github.com/konard/andchir-install_scripts.git/' |
| 103 | +``` |
| 104 | + |
| 105 | +### gh auth status Confirmation (09:13:06) |
| 106 | +``` |
| 107 | +github.com |
| 108 | + X Failed to log in to github.com account konard (/home/hive/.config/gh/hosts.yml) |
| 109 | + - Active account: true |
| 110 | + - The token in /home/hive/.config/gh/hosts.yml is invalid. |
| 111 | + - To re-authenticate, run: gh auth login -h github.com |
| 112 | +``` |
| 113 | + |
| 114 | +## Proposed Solutions |
| 115 | + |
| 116 | +### Question from Issue #26 |
| 117 | + |
| 118 | +> Can we maybe use our `gh-setup-git-identity` tool to actually revoke all 10 tokens? |
| 119 | +
|
| 120 | +**Answer: Not directly through code modification.** |
| 121 | + |
| 122 | +The GitHub CLI itself cannot revoke OAuth tokens programmatically because GitHub's platform doesn't expose an API endpoint for OAuth apps to revoke their own tokens. This is documented in [GitHub CLI Issue #9233](https://github.com/cli/cli/issues/9233). |
| 123 | + |
| 124 | +However, there are several alternative approaches: |
| 125 | + |
| 126 | +### Solution 1: Use Personal Access Tokens (PATs) Instead of OAuth Flow |
| 127 | + |
| 128 | +**Recommended for multi-environment setups** |
| 129 | + |
| 130 | +PATs don't count toward the 10-token OAuth limit and can be shared across environments. |
| 131 | + |
| 132 | +```bash |
| 133 | +# Create a PAT at GitHub Settings > Developer settings > Personal access tokens |
| 134 | +# Then authenticate: |
| 135 | +echo "ghp_your_token_here" | gh-setup-git-identity --with-token |
| 136 | +``` |
| 137 | + |
| 138 | +**Pros:** |
| 139 | +- No token accumulation |
| 140 | +- Same token works across all environments |
| 141 | +- Explicit control over token lifecycle |
| 142 | + |
| 143 | +**Cons:** |
| 144 | +- Requires manual token creation |
| 145 | +- Token management responsibility shifts to user |
| 146 | + |
| 147 | +### Solution 2: Revoke Tokens Manually via GitHub Settings |
| 148 | + |
| 149 | +Users can manually clean up old tokens: |
| 150 | + |
| 151 | +1. Go to **GitHub Settings** > **Applications** > **Authorized OAuth Apps** |
| 152 | +2. Find "GitHub CLI" entry |
| 153 | +3. Click "Revoke" to remove old authorizations |
| 154 | +4. Re-authenticate with `gh-setup-git-identity` |
| 155 | + |
| 156 | +### Solution 3: Use GH_TOKEN/GITHUB_TOKEN Environment Variable |
| 157 | + |
| 158 | +For automated/CI environments: |
| 159 | + |
| 160 | +```bash |
| 161 | +export GH_TOKEN="ghp_your_personal_access_token" |
| 162 | +# or |
| 163 | +export GITHUB_TOKEN="ghp_your_personal_access_token" |
| 164 | + |
| 165 | +# gh-setup-git-identity will use this token |
| 166 | +gh-setup-git-identity |
| 167 | +``` |
| 168 | + |
| 169 | +### Solution 4: Add Warning Documentation to gh-setup-git-identity |
| 170 | + |
| 171 | +Add documentation warning users about the 10-token limit when using multiple environments. |
| 172 | + |
| 173 | +### Solution 5: Implement Token Reuse Check (Code Change) |
| 174 | + |
| 175 | +Modify `gh-setup-git-identity` to: |
| 176 | +1. Check if current token is valid before triggering new authentication |
| 177 | +2. Skip `gh auth login` if already authenticated (currently implemented but can be enhanced) |
| 178 | +3. Warn users when they attempt to re-authenticate unnecessarily |
| 179 | + |
| 180 | +### Solution 6: Add Token Revocation Guidance in CLI Output |
| 181 | + |
| 182 | +When authentication is triggered, output a message like: |
| 183 | + |
| 184 | +``` |
| 185 | +Note: GitHub limits OAuth tokens to 10 per user/application/scope. |
| 186 | +If you use multiple environments, consider using Personal Access Tokens instead. |
| 187 | +To revoke old tokens: https://github.com/settings/applications |
| 188 | +``` |
| 189 | + |
| 190 | +## Recommendations |
| 191 | + |
| 192 | +### For Individual Users |
| 193 | + |
| 194 | +1. **For multi-environment setups**: Use Personal Access Tokens (PATs) instead of OAuth device flow |
| 195 | +2. **Regularly audit tokens**: Visit GitHub Settings > Applications > Authorized OAuth Apps |
| 196 | +3. **Share tokens across environments**: Use `GH_TOKEN` environment variable with a single PAT |
| 197 | + |
| 198 | +### For gh-setup-git-identity Development |
| 199 | + |
| 200 | +1. **Add documentation** about GitHub's 10-token limit in README.md |
| 201 | +2. **Add CLI warning** when triggering authentication about token limits |
| 202 | +3. **Enhance `--with-token` mode** documentation for multi-environment usage |
| 203 | +4. **Consider adding `--revoke-first` flag** that prompts users to revoke via GitHub settings before re-authenticating |
| 204 | + |
| 205 | +## Conclusion |
| 206 | + |
| 207 | +The token invalidation issue is expected behavior due to GitHub's OAuth token management: |
| 208 | + |
| 209 | +1. **GitHub limits OAuth tokens to 10 per user/application/scope combination** |
| 210 | +2. **When the limit is exceeded, the oldest tokens are automatically revoked** |
| 211 | +3. **`gh auth login` creates new tokens without revoking old ones** |
| 212 | +4. **This is a platform limitation, not a bug in `gh-setup-git-identity`** |
| 213 | + |
| 214 | +The recommended mitigation is to use Personal Access Tokens (PATs) for multi-environment setups, which provides better control over token lifecycle and avoids the 10-token OAuth limit. |
| 215 | + |
| 216 | +## References |
| 217 | + |
| 218 | +- [GitHub Token Expiration and Revocation Documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/token-expiration-and-revocation) |
| 219 | +- [GitHub CLI gh auth login Manual](https://cli.github.com/manual/gh_auth_login) |
| 220 | +- [GitHub CLI Issue #9233: Invalidate previous OAuth token](https://github.com/cli/cli/issues/9233) |
| 221 | +- [GitHub CLI Issue #5924: Support for short-lived OAuth tokens](https://github.com/cli/cli/issues/5924) |
| 222 | +- [Original Issue Analysis (hive-mind #1021)](https://github.com/link-assistant/hive-mind/pull/1022) |
| 223 | +- [Original Issue Gist Log](https://gist.github.com/konard/a430347f7f9aff41b7e0c64a7289712a) |
| 224 | + |
| 225 | +## Appendix: Related Files |
| 226 | + |
| 227 | +- `timeline.md` - Detailed event timeline reconstruction |
| 228 | +- `solutions.md` - Comprehensive solution comparison matrix |
| 229 | +- `raw-logs/` - Redacted log excerpts for reference |
0 commit comments