Guide 145 checklist

How to review CI credentials and token scopes

Review CI credential scope and trust conditions across triggers, artifacts and workflow edits, keeping untrusted data separate from privileged execution.

For each CI job, write down what can start it, whose code it runs, which credentials it receives, and the smallest action and resource set those credentials need. Review the Git host token and cloud or registry credentials separately. A read-only pull-request check should not inherit release write access merely because both jobs live in one workflow file. A short-lived token is safer than a stored long-lived key for many workloads, but a broadly trusted job can still misuse it. The result of the review should be a job-by-job access table, a list of high-risk trigger paths, and an observed allowed/denied check after any change.

The exact answer depends on CI provider and event type. This guide uses GitHub Actions and AWS as a worked example. GitHub creates a job-specific GITHUB_TOKEN for workflows, while a job may separately obtain cloud credentials through OIDC or read a stored secret. Do not assume restricting GITHUB_TOKEN also narrows the AWS role, package-registry token or self-hosted runner's ambient permissions.

Illustrative review: PR check and production release

Illustrative workflow, not a customer configuration or an executed exploit: Northstar Billing has one workflow with two jobs. review runs on pull requests, checks source and comments on the PR. release runs after changes reach the protected main branch and deploys to AWS. Both jobs currently have contents: write and pull-requests: write; a long-lived AWS access key is stored as a repository secret. The YAML does not show whether a fork's pull request can reach the release job, and the reviewer must not infer that it can.

The first artifact is a privilege table:

Job Trigger and code Needed GitHub permission Needed external authority Initial concern
review PR code may be untrusted contents: read; PR comment write only if the design truly requires it No AWS deployment access Workflow-level writes may be inherited needlessly
release Protected branch release revision Repository read, release or deployment permission if used One AWS deployment role for defined resources Long-lived key has no job or branch claim

This table is a proposed access requirement, not proof of current effective permissions. The reviewer still needs repository settings, workflow permissions, environment rules, the role trust policy and an actual run record. GitHub's workflow syntax documents job and workflow permissions: when specific token permissions are set, unspecified ones become none. It also explains how defaults and fork pull-request events affect final permissions. Record the final effective scope for the real event, not just what a snippet appears to say.

1. Inventory every credential delivery route

Input: workflow files, reusable workflows, repository and organization settings, environment secrets, cloud trust policies, runner configuration. Owner: platform engineer with security reviewer. Action: list every token or role a job can receive. Output: one row per job and credential. Check: the row explains both the credential's issuer and where its scope is enforced.

Include GITHUB_TOKEN, personal access tokens, GitHub App installation tokens, package-registry tokens, cloud keys, OIDC-derived roles, signing keys and runner instance credentials. A job may never reference a secret by name yet still reach credentials through a shared runner, a reusable workflow or a cloud metadata service. Conversely, a configured secret may be withheld on a particular fork event. Follow the real event and runner path before declaring access present or absent.

For each credential record its owner, intended job, issuance condition, maximum lifetime, allowed resources, permissions, rotation or revocation method, and logs that show use. Do not copy the secret value into the inventory. If the owner is unknown, classify the credential as unowned until someone accountable confirms its purpose.

2. Cross the trigger and code trust boundary

Review every on: event, branch filter, environment approval and reusable-workflow caller. Ask who can cause the event and which revision of code or workflow configuration executes. GitHub warns that pull_request_target runs with elevated base-repository trust; checking out and executing an untrusted pull request inside that privileged job creates a serious path to its secrets or write token. The same reasoning applies to downloaded artifacts, scripts, dependencies and configuration from less trusted jobs. A checkout step alone is not the harmful action; executing the untrusted content with privileged credentials completes the risk.

Northstar's review job needs to inspect PR code. The safest design is to keep that job unprivileged and separate any comment-writing operation from execution of untrusted PR content. If a privileged follow-up job consumes a result file from the PR job, treat that file as untrusted data and validate it before use. Do not let a PR-controlled branch, artifact name or command line become an unchecked shell command. GitHub's secure-use guidance covers script injection, secret handling and third-party action trust.

3. Set GitHub token permissions per job

Write the smallest permissions map for each job, starting with no write grants. GitHub's workflow syntax allows read, write or none per permission and permissions: {} to disable them all. If review only checks source, contents: read may suffice. If a separate job posts a PR comment, grant pull-requests: write to that job alone and ensure its input cannot control the command or destination. The release job should get only the GitHub permissions used for the release artifact; its AWS role is reviewed separately.

Inspect every third-party action and reusable workflow that receives the token. GitHub recommends pinning actions to a full-length commit SHA so a mutable tag cannot silently change the code that runs. Verify the commit belongs to the expected repository, and review what the action does with credentials. A pinned action can still be malicious or have a dangerous design; pinning controls change, not intent.

4. Replace cloud keys with a narrow job identity where possible

For the Northstar release job, GitHub's AWS OIDC guide describes exchanging a workflow identity token for AWS access without storing a long-lived AWS secret. That removes one persistent credential from the repository's secret store. The AWS role must still trust only the intended GitHub organization, repository and release context. AWS's OIDC role guide warns against an unconstrained GitHub sub claim; otherwise repositories outside your control may be able to assume the role.

Write two independent restrictions: who may assume the role in its trust policy, and what the role may do in its permissions and relevant resource policies. Check branch or environment claims against the actual workflow event. GitHub environment protection can add a human approval step when appropriate. A role that trusts only Northstar's repository but grants broad account administration is still overprivileged. A narrow role trusted by every repository is also overexposed. AWS policy evaluation explains why the role policy is only one part of effective access.

If a long-lived credential cannot be removed now, scope it to the smallest resource set, keep it out of PR jobs, set a rotation owner and plan migration. A leaked existing key needs incident response, not just a future OIDC ticket; use the Git leak response guide and verify the old key's actual revocation.

5. Inspect what crosses from the PR job to the privileged job

Northstar's proposed split has three distinct authorities:

untrusted PR -> read-only analysis -> result data
                                     |
                                     v
trusted comment job -> fixed-format comment on API-resolved PR
protected release + approved environment -> OIDC -> scoped AWS role

The result data is still untrusted. In this synthetic contract, the comment job accepts only a small versioned object such as {"schema":1,"finding_count":3}. It checks type, bounds and size. Repository, PR number and associated run are derived and cross-checked through trusted event/API metadata, not accepted as destination instructions from the result file. A supplied command, URL, path or unknown field is rejected. For example, finding_count must be an integer between 0 and the agreed maximum; a string, negative number, nested object or oversized payload fails validation. The job renders a fixed message such as “Review completed: 3 items require assessment” and does not interpolate artifact text into a shell.

This contract also requires provenance: the consumer verifies the expected repository, triggering workflow/run and relevant PR association before selecting the artifact. Valid JSON from the wrong run is not the right review result. The consumer never checks out or executes the PR's scripts with its comment token. Exact trigger and artifact-selection behavior must be checked against the chosen GitHub event; the diagram is not a complete workflow.

6. Match the OIDC subject to the release configuration

A branch-only release and an environment-based release may have different sub values. GitHub's AWS OIDC guide specifies an environment form when a job uses an environment and recommends environment deployment protection rules. Do not widen trust to repo:ORG/REPO:* merely to make a mismatched release start working.

For a deployment configured with the standard repository-name environment subject, the illustrative expected value is repo:northstar/billing:environment:production, with the intended AWS audience. The protected-branch condition then needs to be enforced through the environment's deployment rules and reviewed workflow controls; that subject string alone does not encode refs/heads/main. GitHub also supports subject-format variations, so verify the actual configured claim format and current documentation rather than copying this literal into a real trust policy.

Event path Intended credential outcome Decisive check
Ordinary/fork PR analysis Repository read only; no cloud authority Effective event permissions and runner ambient access
Trusted comment follow-up Comment write only; no source execution Run provenance, strict result schema, trusted destination
Main release to production environment OIDC request and scoped release role Actual subject/audience plus environment branch rules
Manual dispatch from another ref Cannot bypass production branch/environment rule Manual trigger follows same protected release boundary

No workflow was run for this example. The review closeout must attach actual event-specific evidence before claiming these outcomes. Merge approval, role-assumption success and final deployment permission are distinct checks; the legacy key's revocation remains a separate incident acceptance item.

Treat workflow edits as changes to the credential boundary

Keep the access table beside the workflow's maintenance record. A new manual trigger, reusable caller, artifact consumer or runner can change who reaches an otherwise unchanged token. When Northstar adds a release-retry job, review whether it uses the same protected environment and trusted artifact association. Copying the original permissions map is insufficient if the new job accepts a different ref or downloads an unverified artifact.

The reviewer can keep this narrow: identify the new entry, compare its code and data trust with the reviewed release path, and record which credential assumptions carry forward. If the path cannot obtain production authority, document that evidence. If it can, apply the release conditions to the new path before relying on the older workflow review.

Copy this review checklist

  1. Name every event, caller, code revision and runner type for the job.
  2. Inventory GitHub, cloud, registry, signing and runner credentials separately.
  3. Map every credential to a named action, resource, environment and owner.
  4. Keep untrusted code execution out of privileged jobs. Accept result data only through a bounded schema, verified run association and fixed rendering path.
  5. Restrict GITHUB_TOKEN per job; inspect third-party actions and reusable workflows.
  6. Scope OIDC trust claims and the assumed role's permissions independently.
  7. Confirm normal success and a forbidden case on the actual event path.
  8. Record unresolved secret copies, rare events, log gaps and follow-up owner.

Start with the job that can publish a release or reach production. Its credentials have the highest potential effect, and its access table is usually small enough to review today. For an exposed old key, follow secret rotation verification. To inspect the AWS role behind the workflow in more detail, use the application IAM review.