ADR-1086: CI Workflow Least-Privilege Permissions Audit¶
- Status: Accepted
- Date: 2026-06-06
- Deciders: Lusoris
- Tags:
ci,security
Context¶
A permissions audit of all 28 .github/workflows/*.yml files surfaced two deviations from the least-privilege model the fork otherwise applies consistently:
-
upstream-watcher.yml: The workflow-level block was set topermissions: read-all, which grants every available read scope to any future job added to the file. The single existing job overrides this to{contents: read, issues: write}, so the broad default was never actually exercised, but it left an overly wide footprint. -
docker-publish-operator-node.yml: Two checkout steps (build-operatorat line 62,build-nodeat line 152) omittedpersist-credentials: false. Both jobs holdpackages: write(push to GHCR). Withoutpersist-credentials: false,actions/checkoutleaves theGITHUB_TOKENwithpackages: writescope stored in.git/configfor the lifetime of the runner. Every other checkout in the repository already sets this flag. The inconsistency was a latent scope-escalation risk: a rogue step appended to either job could push to GHCR without needing an explicitsecrets.*expression.
All other workflows were verified to have:
- Explicit top-level
permissions:blocks (none missing). - Per-job narrowing where write scopes are required.
persist-credentials: falseon checkout steps that co-exist with write scopes.scorecard.yml'spermissions: read-allis intentional and required by theossf/scorecard-actionto probe repository metadata; it is not changed.
Decision¶
-
Change the
upstream-watcher.ymlworkflow-level permission fromread-alltocontents: read. The existing job already opts intoissues: writeper-job; no job behaviour changes. -
Add
persist-credentials: falseto the two checkout steps indocker-publish-operator-node.yml(build-operatorandbuild-node).
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
Keep read-all on upstream-watcher | Zero risk since job overrides it | Violates least-privilege; a future job would silently inherit broad read access | Not chosen |
Remove persist-credentials: false pattern globally | Simpler YAML | Leaves tokens with write scope in .git/config across step boundaries | Not chosen |
Consequences¶
- Positive:
upstream-watcher.ymlnow starts from a least-privilege baseline; any future job added to the file starts fromcontents: readrather than read-all. - Positive:
docker-publish-operator-node.ymljobs drop the GITHUB_TOKEN from.git/configimmediately after checkout, consistent with every other checkout in the repository. - Neutral: No behaviour change in any workflow run. The
ossf/scorecardScorecard TokenPermissionsID check will report an improvement on the next weekly scan.
References¶
- OSSF Scorecard TokenPermissionsID check
- GitHub Actions documentation:
persist-credentials: falseinactions/checkout - ADR-0253 — Scorecard policy for this fork
- Audit scope:
.github/workflows/*.yml(28 files), 2026-06-06