Secure the pipeline
Minimize token permissions
Give GITHUBTOKEN no more repository access than each workflow or job requires.
Every workflow receives a GITHUB_TOKEN. It is temporary, but it is still a credential. Broad write access turns a script injection or compromised action into a repository takeover.
The default permissions for new repositories changed over time. Older repos may still grant contents: write to every workflow. Check repository settings under Actions > General and align the default with your least-privilege YAML.
My default: start with almost no permission, then add back only what breaks.
Set restrictive defaults at the top
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
Pull request tests usually need read access to code, nothing else.
Grant write only where needed
Publishing releases or commenting on pull requests needs explicit scope on that job:
release:
needs: test
permissions:
contents: write
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/create-release.sh
Other jobs never see contents: write.
Prove it by breaking on purpose
Set permissions: {} or contents: read only. Run the workflow. When a step fails with “Resource not accessible by integration”, read the docs for that action and add the smallest permission that fixes it.
Do not copy permissions: write-all from an old template because the token expires at the end of the job. An attacker does not need long-lived access. One write call to main is enough.
Some actions need pull-requests: write to comment or packages: write to publish. Grant those on the job that calls the action, not on the whole workflow.
Document required permissions in the runbook next to each job name. Future you will forget why id-token: write exists.
Try this on your own project: list every job and the minimum permission it needs. Compare that list to your current YAML.
Lesson completed