You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up hardening to PR #408 (OSSF Token-Permissions). Consider replacing the top-level permissions: { contents: read } default across workflows with permissions: {} (deny-all) and relying exclusively on job-level permissions: blocks to grant scopes.
OSSF Scorecard scores both patterns the same on the Token-Permissions check, so this is a preference, not a requirement — tracked here as an explicit defense-in-depth refinement.
Motivation
Unambiguous intent: permissions: {} declares "the workflow grants nothing by default; every scope is an explicit job-level opt-in." This matches the per-job pattern the repo already uses across all 30 workflows.
Fail-loud on future changes: A new job added without a permissions: block would inherit nothing (and fail loudly on e.g. actions/checkout) rather than silently inheriting contents: read with unintended scope.
Strictly less privilege than contents: read at the top level.
Refactor required (6 workflows): push top-level write/read scopes down into the specific job(s) that need them before swapping. This is the bulk of the work.
Verify: no reusable-workflow callers or workflow templates rely on inherited top-level scopes.
Acceptance Criteria
All workflows under .github/workflows/ use permissions: {} at the top level
Top-level write scopes from the 6 combined-permissions workflows are moved into specific jobs
CI passes on dev after the change (no jobs lose required scopes)
OSSF Scorecard Token-Permissions sub-score unchanged (expected) or improved
Workflow author guidance in .github/copilot-instructions.md (or equivalent) notes permissions: {} as the convention
Summary
Follow-up hardening to PR #408 (OSSF Token-Permissions). Consider replacing the top-level
permissions: { contents: read }default across workflows withpermissions: {}(deny-all) and relying exclusively on job-levelpermissions:blocks to grant scopes.OSSF Scorecard scores both patterns the same on the Token-Permissions check, so this is a preference, not a requirement — tracked here as an explicit defense-in-depth refinement.
Motivation
permissions: {}declares "the workflow grants nothing by default; every scope is an explicit job-level opt-in." This matches the per-job pattern the repo already uses across all 30 workflows.permissions:block would inherit nothing (and fail loudly on e.g.actions/checkout) rather than silently inheritingcontents: readwith unintended scope.contents: readat the top level.Current State
permissions: { contents: read }onlysecurity-scan.yml,docs-check-terraform.yml,terraform-lint.yml,scorecard.yml, …contents: read+ additional top-level scopespages-deploy.yml(pages/id-token/attestations),docs-automation-commenter.yml(issues/pull-requests),matrix-folder-check.yml,security-comprehensive.yml,security-deployment.yml,security-staleness-check.yml(each addsactions: read)permissions: {}Scope
permissions: { contents: read }withpermissions: {}. Every job in these workflows already declares its ownpermissions:block per PR chore: vulnerability remediation (#409 phases A-G), OSSF hardening, and Docusaurus migration completion #408.Acceptance Criteria
.github/workflows/usepermissions: {}at the top leveldevafter the change (no jobs lose required scopes).github/copilot-instructions.md(or equivalent) notespermissions: {}as the conventionReferences
permissionssyntax: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissionsPriority
Low — non-blocking style/hardening refinement. Good candidate for a single follow-up PR after #408 merges.