Safeguard
Security Guides

GitHub Token Security (2026 Guide)

GitHub tokens are keys to your source, your CI, and often your cloud. This guide covers PATs, fine-grained tokens, GitHub App and Actions tokens — and how to scope, store, and rotate them after the CircleCI and Heroku token thefts.

Priya Mehta
Security Researcher
6 min read

A GitHub token is rarely just access to code. Because so much automation authenticates to GitHub, a stolen token often unlocks CI pipelines, package registries, deployment keys, and — through those — production cloud accounts. Two incidents make the blast radius concrete. In April 2022, attackers used OAuth tokens that had been issued to Heroku and Travis CI to download data from dozens of organizations' private GitHub repositories; the tokens were legitimate, so nothing looked anomalous until GitHub spotted the pattern. In January 2023, CircleCI disclosed that malware on an engineer's laptop had stolen a session token and forced every customer to rotate all secrets, many of which were GitHub personal access tokens. The common thread is that a token is a bearer credential: whoever holds it is you, until you revoke it. This guide covers the four token types GitHub issues and how to run each one safely.

The four token types and how they leak

GitHub issues classic personal access tokens (PATs), fine-grained PATs, GitHub App installation tokens, and the automatic GITHUB_TOKEN inside Actions. Classic PATs are the most dangerous because their scopes are coarse — the repo scope grants access to every repository the user can see, public and private, with no per-repo limit and, by default, no expiry. Fine-grained PATs, which reached general availability in 2025, fix this by binding a token to specific repositories with specific permissions and a mandatory expiration date. Tokens leak the same way all secrets do: committed to git, printed in CI logs, pasted into issues or chat, or lifted from a developer machine by infostealer malware — the vector behind both the CircleCI and the 2024 Snowflake credential-theft waves. Because a valid token produces valid API calls, leaks are frequently discovered through scanning or after abuse, not from anything obviously wrong in the logs.

Best practices, with commands

Use fine-grained PATs and always set an expiration. Never use a classic PAT with the broad repo scope when a fine-grained token scoped to a single repository will do. When you create one, restrict it to the exact repositories and permissions needed, and pick the shortest workable lifetime.

Store tokens in a secrets manager, never in .git-credentials in plaintext. For local git, use a credential helper backed by the OS keychain instead of a flat file:

# Use the OS keychain / credential manager instead of plaintext storage
git config --global credential.helper manager   # Windows
git config --global credential.helper osxkeychain # macOS
# Authenticate through the gh CLI so the token lives in the keychain, not a dotfile
gh auth login

Prefer the automatic GITHUB_TOKEN in Actions and give it read-only defaults. The built-in token is scoped to the running repository and expires when the job ends, which is far safer than a PAT stored as a secret. Lock permissions down explicitly at the top of every workflow:

# .github/workflows/ci.yml — least privilege for the built-in token
permissions:
  contents: read
  pull-requests: write

Verify and revoke suspected leaks fast. Confirm a token is dead rather than assuming a revoke worked, and list what a token can reach before trusting it:

# Check a token's scopes without exposing it in the URL
curl -sS -I -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user \
  | grep -i x-oauth-scopes
# After revoking, confirm it now returns 401
curl -sS -o /dev/null -w "%{http_code}\n" \
  -H "Authorization: Bearer $OLD_TOKEN" https://api.github.com/user

Turn on push protection. GitHub secret scanning with push protection blocks commits that contain recognizable tokens before they land, and it has been enabled by default on public repositories since 2024 — enable it on private repositories too.

GitHub token hardening checklist

ControlWhy it matters
Fine-grained PATs over classic repo-scope tokensLimits a leak to specific repos, not the whole account
Mandatory expiration on every tokenCaps the window a leaked token stays valid
Built-in GITHUB_TOKEN for Actions where possibleEphemeral, repo-scoped, nothing static to store
Explicit permissions: block per workflowPrevents write access the job never needed
OS keychain credential helper, not plaintextStops laptop file-read from harvesting tokens
Push protection on public and private reposBlocks the leaking commit before it merges
Regular token inventory and revocationKills forgotten, over-scoped tokens

How Safeguard's secret scanning helps

Token sprawl is invisible until something scans for it. Safeguard's secret scanning recognizes GitHub PATs, App tokens, and OAuth credentials across your repositories and full commit history, reporting the introducing commit and author so revocation and history-purge happen together. Griffin AI validates whether a detected token is still live and classifies its scope, so a broad classic PAT is prioritized over an expired fine-grained one. Developers stop the leak at the source by running the Safeguard CLI in pre-commit and CI, and because tokens, dependency risk, and misconfigurations surface in the same software composition analysis view, you triage from one prioritized list. When a hardcoded token can be replaced with a managed reference, auto-fix opens the pull request to make the change.

Bring continuous secret and dependency scanning to every repository — get started free or read the documentation.

Frequently Asked Questions

What is the difference between a classic and a fine-grained personal access token?

A classic PAT uses coarse scopes like repo, which grants access to every repository the user can see and, by default, never expires. A fine-grained PAT binds the token to specific repositories with specific permissions and requires an expiration date. Fine-grained tokens, generally available since 2025, dramatically shrink the blast radius of a leak and should be your default choice.

Should CI use a personal access token or the built-in GITHUB_TOKEN?

Prefer the built-in GITHUB_TOKEN whenever the job only needs to act on its own repository. It is scoped to that repository, expires when the job finishes, and never has to be stored as a secret. Reach for a fine-grained PAT or a GitHub App token only when a workflow genuinely needs cross-repository access, and scope it as narrowly as possible.

How were legitimate tokens abused in the Heroku and Travis incident?

In April 2022, attackers obtained OAuth tokens that GitHub had issued to Heroku and Travis CI integrations and used them to clone private repositories from many organizations. Because the tokens were valid and the API calls looked normal, the abuse was not obvious from logs alone. It is the clearest argument for narrow scopes and short lifetimes — a token that can reach less, and expires sooner, is worth less when stolen.

Does removing a token from the latest commit make it safe?

No. The token remains readable in git history, so you must rewrite history to purge it and, more importantly, revoke and reissue the token. A committed token should be treated as compromised regardless of cleanup, because automated scanners test credential-shaped strings in public commits within minutes of a push.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.