Workflow does not declare permissions
Control: Workflow permissions must be declared· Config key: workflowsMustDeclarePermissions
📋 What is this?
A job declares no permissions: block at either the job or the workflow level. Its GITHUB_TOKEN inherits whatever the repo / organisation default is, which is often write-all. Jobs covered by a workflow-level or job-level block are not flagged.
⚠️ Impact
Implicit permissions are silently broad. A workflow that legitimately needs contents: read may end up with contents: write, id-token: write, and a dozen other scopes. A compromised step can use any one of them to push to the repo or impersonate the workflow.
🔧 How to fix
Add a top-level permissions: block listing only the scopes the workflow actually needs. Most workflows only need contents: read.
# .github/workflows/test.yml: ❌ Inherits whatever the repo default isname: teston: [push, pull_request]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test# .github/workflows/test.yml: ✅ Least privilegename: teston: [push, pull_request]permissions: contents: readjobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - run: npm test
# .plumber.yamlgithub: controls: workflowsMustDeclarePermissions: enabled: true💡 Tips
- Override per-job when a specific job needs more (e.g.
releaseneedingcontents: write). - Org admins can also enforce
permissions: read-allas the org default, a useful safety net. - Pair with ISSUE-803 to catch the over-broad case explicitly.
⚙️ Configuration
This control is configured in .plumber.yaml under the github section:
github:
controls:
workflowsMustDeclarePermissions:
enabled: trueSee the CLI documentation for the full configuration reference.