A CI workflow runs code from a repository event in a runner with an identity and a token. Its permissions are part of the threat model: a test job that only reads source does not need permission to write packages or mint an identity token. OpenID Connect can exchange a short-lived GitHub-issued identity token for cloud credentials when the cloud trust policy accepts the job's claims.
GitHub Actions: narrow tokens and cloud trust
Operational decision
A billing service has one unprivileged test job and a separate deployment job protected by an environment. Set a minimal default permission and grant id-token: write only to the deployment job that needs to request an OIDC token. The cloud provider must also restrict the accepted repository, branch or environment, and audience; id-token: write by itself grants no cloud role. Avoid running untrusted pull-request code in a privileged context. Pin third-party actions to reviewed commit SHAs in a real workflow and review updates deliberately. The fragment below shows only job permission boundaries, not a complete workflow. Environment review and branch restrictions are configured in repository settings and may depend on the plan and repository visibility.
name: billing-ci
on: [push]
permissions: {contents: read}
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: make test
deploy:
needs: test
environment: production
runs-on: ubuntu-latest
permissions: {contents: read, id-token: write}
steps:
- run: ./release/promote-verified-digest.shCost and verification
Splitting jobs increases runner startup time but keeps deployment authority away from test code. A token limited in GitHub can still be dangerous if the cloud trust policy accepts too many claims. The fragment deliberately omits checkout and OIDC exchange steps; copying it alone will not deploy. Audit who can change workflow files, which branches may reach production, and whether environment protection actually runs. Never print credentials or identity tokens in job logs.
Common Mistakes
- Do not use id-token: write as a substitute for a restrictive cloud trust policy.
- Do not run untrusted pull-request code with deployment credentials.
- Do not assume an environment name creates approval rules automatically.
Connected lessons
- DevOps Tutorial
- Continuous integration: test the merge candidate
- Immutable artifacts and release provenance
- Secrets and configuration across the delivery path
