# how to scope GitHub personal access tokens safely

## TL;DR
Use fine-grained personal access tokens scoped to exactly the repos and permissions the task needs, with the shortest expiration you can tolerate. Never reuse one token across unrelated jobs, and never paste a token into chat, tickets, or code. When a token leaks or its job ends, revoke it and mint a fresh one instead of widening the old one.

## The query
```text
how to scope GitHub personal access tokens safely
```

## Use this when
- you need a token for CI, a script, or an integration and want least privilege
- someone asks "which permissions does this token need" or "is my PAT too broad"
- you are replacing a classic PAT with all-repo access
- you are auditing tokens after a leak or a team change

## Not for this skill when
- you are setting up a GitHub App (apps have their own permission model)
- you need server-to-server auth at scale (consider an app installation token)
- you are debugging an API 403 (check the token scope, but the fix may be repo settings)

## Steps
1. Create a fine-grained token under your GitHub settings, scoped to only the repositories it needs (never "all repositories" unless the job truly needs it).
   Expected output: the token creation screen lists exactly the repos you selected.
2. Grant only the permissions the job uses: for a release script that pushes tags, that is Contents read and write on those repos and nothing else.
   Expected output: the permission list is short and matches the documented needs of the task.
3. Set an expiration: 30 to 90 days for interactive use, shorter for one-off jobs.
   Expected output: GitHub shows the expiry date on the token list so stale tokens are visible.
4. Store it in a secrets manager or the CI system's secret store, referenced by name in workflows. Never hardcode it in a file or echo it in logs.
   Expected output: the workflow references the secret by name and the value never appears in logs.
5. Test with a read-only call first, then the write the job needs, and confirm nothing else is reachable (try listing repos outside the scope).
   Expected output: in-scope calls succeed, out-of-scope calls return 404 or 403.
6. Revoke on a schedule: delete tokens when the job ends and rotate the long-lived ones.
   Expected output: the token list shows only active, needed tokens with fresh expiries.

### Variant: classic PATs
Classic tokens cant be scoped per repo, so keep them read-only where possible and prefer fine-grained for anything new. Treat every classic token as high blast radius.

### Variant: tokens for local scripts
Mint a token with a 7-day expiry for a one-off local script, scoped to the single repo. Delete it when the script is done instead of letting it linger.

### Variant: org-level policy
Org owners can restrict which token types members may create and require SSO. Turn that on so personal tokens inherit the org's baseline.

## Why this happens
A token is a password with an API attached. Broad tokens turn one leaked string into write access across every repo you touch, and tokens pasted into logs or tickets get scraped fast. Scoping shrinks the blast radius to the one job the token exists for.

## Edge cases and pitfalls
- Fine-grained tokens need explicit repo selection. New repos are not auto-included, so jobs break silently after a repo is created; document the re-scope step.
- Some older integrations only accept classic tokens. Isolate those on a machine account with minimal repo access.
- Expiration breaks automation at 2am. Put rotation on a calendar or automate it.
- A leaked token must be revoked before you investigate. Revoke first, then check audit logs for what it touched.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_uIZFtthYP2uZwi_tIIP7qA
