A repository-scoped machine token should be able to do one named job for one approved repository or explicit repository set, for a bounded time. Review both the source-provider permission and the receiving application's permission. A GitHub token that can read repository contents is not automatically allowed to read every security finding, start any scan or act for another customer account. Conversely, an application token restricted to one repository does not make a broad GitHub installation safe. Record the issuer, audience, repository IDs, actions, expiration, revocation path and responsible owner; then verify a permitted call and a forbidden call in an authorized environment. The answer changes for cross-repository jobs: grant the exact required set and document why it cannot be split, rather than silently using “all repositories.”
Machine credentials are not human sessions. They should not acquire a human's account administration, recovery or approval rights simply because a person created the integration. OWASP's Authorization Cheat Sheet calls for checking permission for each request, and its Secrets Management guidance covers expiration, rotation and actual revocation.
Illustrative case: one CI job, two repositories
Illustrative case; names and token values are invented. Riverstone Software has checkout-api and payroll-admin repositories. A CI job in checkout-api is supposed to request a source assessment for its commit and later read only the status of that job. The platform team installs a GitHub App with read-only repository contents. The installation currently covers both repositories because its owner selected “all repositories.” The job also holds an internal API token whose claim says repo=checkout-api and scan:create. Treat those as the initial configuration: status reads will fail until the separate job.status.read action is deliberately granted.
The internal claim is a useful input, but the resource server still has to validate its signature, audience, expiration, current account and repository grant, and the requested action. The provider installation is a separate exposure: even if the internal API rejects a payroll-admin job, a compromised GitHub installation token may be able to read that repository if the installation includes it. GitHub's installation guide distinguishes “all repositories” from “only select repositories,” and its installation-token documentation allows an issued token to be narrowed to named repositories and permissions.
Write the review result as a small scope ledger:
| Layer | Intended scope | Evidence to inspect | Riverstone decision |
|---|---|---|---|
| GitHub App installation | checkout-api only |
Installation repository selection and approved permissions | Change from all to selected repositories if no other approved job needs payroll access |
| GitHub installation token | Contents read for checkout-api |
Token request's repository IDs and permissions; returned expiry | Request exact repository and minimal permission |
| Internal machine delegation | Create one assessment for checkout-api and read that job's status |
Issuer, audience, account, repo, actions, expiry, live grant and revocation record | Reject payroll-admin, finding export and account-admin actions |
| Worker or downstream call | Bound job, repository and immutable revision | Signed work admission or equivalent plus rechecked persisted job parent | Reject a message that substitutes another repository ID |
No row is proof of a deployed control by itself. The ledger tells the reviewer what to verify, where a policy can expand, and who must repair it.
Calculate the authority for one operation
Treat the four layers as constraints on a particular action, rather than interchangeable token labels. In the synthetic example, use stable provider repository IDs: checkout 101, payroll 202. The installation includes both. An installation token requested specifically for 101 cannot inherit 202 merely because the installation is broader. GitHub documents repository and permission narrowing, and the one-hour token lifetime, in its installation-token guide.
installation repositories: {101, 202}
issued provider token repositories: {101}
internal delegation repositories: {101}
persisted job J-7 parent repository: 101
requested operation: job.status.read(J-7)
The provider token governs source-provider access. The internal delegation governs the application's status API; it must include job.status.read, not merely scan.create. The status handler also verifies that J-7 belongs to the same account and repository as the current delegation. These are different resource servers, so there is no universal token whose claim alone establishes all four facts.
The effective source-fetch scope is the intersection of provider installation access, issued token scope and the application's approved repository job. The effective internal status scope also requires the current application grant and J-7's persisted parent. A source read permission does not authorize finding detail, account administration or a second repository's job.
Follow two changes that look harmless
First, a platform administrator adds repository 303 to the installation. The already narrowed token and internal delegation remain intended for 101. The next token request should still explicitly request 101; omitting repository restrictions during rotation can accidentally widen a previously narrow credential. Review the mint request and returned scope, not just the installation settings.
Second, Riverstone revokes the internal delegation while the provider token remains unexpired. The application must deny a new status delivery and must prevent new source fetches admitted under that revoked job authority according to its stated policy. The provider token might still work directly against GitHub until its own revocation or expiry; application revocation does not remotely rewrite the provider's token. If the credential escaped, address both layers.
| Controlled request after each change | Expected application decision |
|---|---|
| Active delegation asks for J-7 status | Allow status projection only |
| Same token asks for payroll job J-8 | Deny persisted parent mismatch |
| Same token asks for J-7 finding detail | Deny missing action/section grant |
| Queue message says J-7 belongs to payroll | Reject conflict with authoritative job record |
| Revoked delegation asks for J-7 status | Deny despite unexpired token |
These are synthetic expectations, not verified provider or product results. If live revocation is unavailable, document the actual exposure window and decide whether that meets the integration's requirement.
For GitLab, inspect the project access token's role and scopes. Its project access-token documentation distinguishes read_repository from broader API permissions and documents its bot-user model. A project label does not imply read-only access. GitLab also documents an important visibility exception: a project token created by an internal user can access projects with Internal visibility. Include that reach when evaluating the actual instance; do not promise strict single-project confidentiality from the token name. Provider-specific behavior should be checked at the version and plan used by the integration.
Keep credential material out of the review record. Record token identifiers, repository IDs, scope requests, expiry and the authority that can revoke each layer. Also identify who holds the App signing key or other minting credential: narrowing a child token does not constrain an issuer that can mint a broader replacement. That makes an incident response possible without creating another secret-bearing report.
Check changes that quietly widen scope
Revisit the grant when a repository is added to an installation, a workflow moves from one project to a monorepo, a token is rotated, a permission is upgraded, or a team member who authorized it leaves. A new repository should not become readable because an old installation was set to “all.” Nor should a rotated internal token inherit an old broad scope by default. Trace the configuration and the actual issued token; they are different artifacts.
For Riverstone, perform one permitted status read for the checkout-api job and three denied calls with synthetic fixtures: a payroll-admin job request, a finding-detail read, and an account-admin action. Repeat after removing the granting user or repository permission. Verify the downstream worker also rejects a queued job whose persisted repository parent conflicts with the delegation. A provider console screenshot and a source-code check are useful evidence but do not alone establish that current runtime policy blocks each call.
For a platform that cannot check live grant changes on every request, state the maximum residual exposure until token expiry or cache refresh and treat immediate revocation as unmet. If the platform uses short-lived signed work admissions, document their expiry and cancellation fence. Do not promise retroactive confidentiality: a token holder may already have received source or findings before revocation. If the source-provider API cannot restrict an action as narrowly as desired, record that limitation and compensate with application controls and monitoring rather than calling the token least privilege without qualification.
Review who can obtain the next token
The scope ledger describes the credential in use, but Riverstone also needs to understand the process that creates its replacement. A CI job with a narrow provider token can still have broader effective authority if it can call a minting service that accepts arbitrary repository IDs. The minting service must validate the requested repository and action against the caller's approved delegation before issuing a child credential.
Trace the renewal request like any other protected operation. Which workload is authenticated? Which account and repository does its persisted job belong to? Is the requested permission needed for this job? Can a field in the build configuration substitute payroll's repository ID? The precise mechanism depends on the provider and deployment; the review outcome is an evidence-backed boundary around issuance, not an assumption that short lifetimes solve broad minting rights.
For the synthetic checkout job, the authorized request is for contents access to repository101 at the admitted revision. The resulting source credential should not be returned to an unrelated workflow, stored in broad logs, or reused for another job because both happen to run on the same worker. Those are handling decisions to inspect in the actual implementation.
Keep status access smaller than finding access
Riverstone deliberately adds job.status.read because the original scan:create grant did not cover it. Define the status projection at the same time. A CI job may need completion state and an opaque result reference. It does not necessarily need source excerpts, rejected candidates, or detailed vulnerability instructions. A status endpoint that embeds a full report defeats the narrower action design even when its permission name sounds limited.
The handler must check the job's persisted account and repository before constructing that projection. A guessed job ID is only a locator. If the API allows status for several jobs in one request, repeat the parent and action checks for each job rather than allowing the first matching checkout job to authorize the entire list.
Record the expected denied response without embedding the forbidden job's title or repository name. Internal diagnostics can retain the mismatch under the operational access policy. This gives the platform engineer a concrete result to inspect after changing token scopes: the permitted checkout status remains useful, while payroll and finding-detail requests release no protected content.
Assign one owner to each revocation step
If the integration is retired, identify who stops new internal admissions, revokes or disables the application delegation, handles provider credentials, and confirms that queued work follows the cancellation policy. These steps may belong to different systems. A disabled dashboard button is not evidence that a worker cannot renew its credential.
The review record should include responsible owners and nonsecret identifiers for those steps. It should also say which previously delivered source or reports remain outside revocation's reach. This makes the repository scope review actionable during both routine changes and a credential incident, without placing the credential itself in the evidence packet.
Next action: fill the four-row scope ledger for one production integration and demonstrate one allowed and one forbidden repository call. For account selection risks, continue with tenant ID spoofing in APIs; for stale grants, use the permission-change review.