Where credentials live (repo, environment, org), least-privilege GITHUB_TOKEN permissions, and keyless cloud login with OIDC instead of long-lived keys.
| Level | Scope | Use for |
|---|---|---|
| Organization | Selected / all repos | Shared keys (e.g. DOTENV_PRIVATE_KEY, registry tokens) |
| Repository | One repo | Repo-specific tokens |
| Environment | One job with environment: production | Prod-only secrets, protected by approval rules |
secrets.X is encrypted and masked in logs. vars.X is plain config (region, URLs). Precedence: environment > repository > organization.
Every run gets an automatic GITHUB_TOKEN (${{ secrets.GITHUB_TOKEN }}), valid only for this repo and this run. Grant only what the job needs.
GitHub issues a short-lived signed token per run; AWS/GCP/Azure trust it and return temporary credentials. Nothing to rotate or leak.
Cloud side: create an OIDC identity provider for token.actions.githubusercontent.com
Pattern: commit encrypted .env.production (dotenvx), keep only DOTENV_PRIVATE_KEY as an org secret, decrypt at runtime. One source of truth for all env vars.
Secrets are not passed to workflows triggered from forks, and are empty when misspelled (no error). Never echo a secret or write it to an artifact.