Use the narrowest credential that lets the catalog read what it needs, owned by something that will not disappear when a person leaves. Backstage sends the configured token with every call to that host, so whatever the token can do, a bug or a plugin can do. Prefer fine-grained tokens over classic ones where possible.
What the configuration does
The Backstage GitHub integration is configured in app-config.yaml with a list of providers. Each entry can have a host, a token, API and raw base URLs. The documentation states that if a token is supplied it is passed along with all calls to that provider, both API and raw, and that without one the access is anonymous. A public GitHub provider is added automatically at startup, so you only list it when you want to supply a token.
An example shape, with the secret coming from the environment:
integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}
Putting the value in an environment variable keeps it out of the file. That is the baseline, not the finish line.
Why scope matters here
The same token is used by every plugin and action that talks to GitHub through the integration. If it can write to repositories because the scaffolder publishes through it, then catalog reads and a community plugin's calls also run with write ability. Splitting duties is worth considering: one read oriented credential for catalog discovery, and a separate, tightly scoped credential for publishing.
GitHub's own documentation has points that bear directly on this choice:
| Property | Fine-grained token | Classic token |
|---|---|---|
| Resource owner | One user or organization | Broad |
| Permissions | Specific permissions per resource | Coarser scopes |
| Tied to a user | Yes | Yes |
| GitHub recommendation | Preferred where possible | Use when fine-grained will not work |
Both types are tied to the user who generated them and become inactive if that user loses access to the resource. For a service credential that is a risk of its own: a person leaves, the token stops, and the portal quietly loses data. It is a good argument for a dedicated service identity.
Steps
- Write down what the integration needs: read catalog files, discover repositories, publish new repositories. Each need gets its own row.
- Create the narrowest credential for each need and store it in your secret manager, not in a repository.
- Give each credential an owner who is not a single individual, and a rotation date on the calendar.
- Limit it to the organizations and repositories in scope.
- Turn on logging for the token's use where your host provides it, and alert on use from unexpected addresses.
- After any plugin change, re-check what calls it makes with the credential.
- Rotate immediately if the portal host is ever suspected compromised, and rebuild trust from there.
Common mistakes
Reusing a personal token created for a quick test. Granting write on every repository because it made the first demo work. Letting the same token serve CI, the portal and a script. Each of those turns one leak into a broad incident.
Where Safeguard fits
Safeguard's SBOM and dependency scanning and its policies and gates can check changes to a portal repository before merge. Try the free scan at gold.safeguard.sh/scan.
Quick check
Does Backstage use the token for every request to that host?
The documentation says a supplied token is passed with all calls to that provider, API and raw alike.
What happens to a personal token when its owner loses access?
GitHub's docs say both token types become inactive if the user loses access to the resource, which is why service credentials should not hang on one person.
Sources
Last reviewed: 19 October 2026.