Skip to main content
ISSUE-801 Medium Quick CLI Workflow triggers and permissions

Workflow does not declare permissions

Control: Workflow permissions must be declared · Config key: workflowsMustDeclarePermissions

📋 What is this?

A workflow has no top-level permissions: block. The job inherits whatever the repo / organisation default is, which is often write-all.

⚠️ 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 — any one of which a compromised step can use 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.

✗ Before No explicit `permissions:` — relies on the repo default.
# .github/workflows/test.yml — ❌ Inherits whatever the repo default is
name: test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
✓ After `contents: read` is sufficient for a test workflow.
# .github/workflows/test.yml — ✅ Least privilege
name: test
on: [push, pull_request]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: npm test
# .plumber.yaml
github:
controls:
workflowMustDeclarePermissions:
enabled: true

💡 Tips

  • Override per-job when a specific job needs more (e.g. release needing contents: write).
  • Org admins can also enforce permissions: read-all as 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: true

See the CLI documentation for the full configuration reference.