A GitHub workflow that creates build attestations needs id-token: write, contents: read and attestations: write, and also packages: write for container images. Each grants a specific capability, so set them at the job level and keep every other job at read only.
The permissions block is short, but it is a good place to apply least privilege.
The block from GitHub's documentation
For binaries, the documented workflow uses:
permissions:
id-token: write
contents: read
attestations: write
For container images, add packages: write. The documentation uses the actions/attest action, with subject-path for binaries, and subject-name, subject-digest and push-to-registry: true for images. SBOM attestations add sbom-path.
What each permission is for
| Permission | Plain meaning |
|---|---|
| id-token: write | Lets the job request an OpenID Connect token that identifies the workflow run |
| contents: read | Lets the job read the repository |
| attestations: write | Lets the job store the attestation |
| packages: write | Lets the job push the image and its attestation to the registry |
The identity token is the interesting one. It is how a job can prove who it is without a stored secret, which is the same idea the Sigstore overview describes for keyless signing, where a certificate authority checks an identity token. Check GitHub's own documentation for how its attestations use that token, since this post only relies on what the page lists.
Applying least privilege
Workflow permissions can be set at the top of the file or per job. Prefer per job.
- Set a read only default for the whole workflow.
- Give the attestation job the block above, and nothing more.
- Keep jobs that run untrusted or third party steps separate from the job that holds
id-token: write. - Pin actions you use to a full commit SHA where your policy allows it.
Splitting jobs matters because any step in a job with id-token: write can request that token. A step you did not review, running in the same job, could use it.
Common mistakes
- Granting
write-allat workflow level because it "fixes" a permissions error. - Adding
packages: writeto binary only workflows. - Running pull request builds from forks in the same workflow that attests releases.
- Forgetting that attestations describe a build, not the safety of what was built.
Verification is the other half
The documentation shows consumers verifying with gh attestation verify. An attestation nobody verifies is a record, not a control. Decide who checks and where, for example in a deploy job.
For container image workflows, also consider where scanning happens. Safeguard policies and gates can block pull requests that add risky dependencies, and the free scan at https://gold.safeguard.sh/scan is a quick check of what you ship.
Quick check
Can I put these permissions on the whole workflow?
You can, but per job is safer, because every step in a job with id-token write can request the token.
Do I need packages write for a binary?
The documentation adds packages write for container images, not for the binary example.
Sources
Last reviewed: 19 October 2026.