ExplicitJobPermissions
¶
Permissions should be declared on the job level only.
Defined by ExplicitJobPermissionsRule
which supports workflows in the "Default" ruleset along with MissingJobPermissions
.
Description¶
Declaring permissions on the workflow level leads to elevated permissions for all jobs. Even if the workflow has only one job, it is better to declare the permissions on the job level, this improves consistency, copy-paste-ability, and forms habits.
Move the permissions declaration from the workflow level to the job level.
References:
- Best practice in documentation
The two workflow[s ...] show the permissions key being used at the job level, as it is best practice to limit the permissions' scope.
- Explanation of the above
As a good security practice, you should always make sure that actions only have the minimum access they require by limiting the permissions granted to the GITHUB_TOKEN.
Compliant examples¶
Compliant example #1¶
Permissions are explicitly declared on the job level.
example.yml
on: push jobs: example: runs-on: ubuntu-latest permissions: contents: read steps: - run: echo "Example"
Compliant example #2¶
All permissions are explicitly forbidden on the workflow level.
example.yml
on: push permissions: {} jobs: example: runs-on: ubuntu-latest steps: - run: echo "Example"
Non-compliant examples¶
Non-compliant example #1¶
Permissions are declared on the workflow level, leading to escalated privileges for all jobs.
There are two jobs: build
and comment
:
- The
build
job needs to access contents to invoke themake
command. - The
comment
job needs to write comments to the pull request.
With the permissions:
being declared on the workflow,
both jobs will have the same permissions.
This leads to a larger attack surface:
- The comment job will be able to read the repository contents. This means that if the publish-comment-action is compromised, it can read/steal the repository contents.
- The build job will have full access to Pull Requests. This means that if the make command is compromised, it can do anything to PRs.
example.yml
on: pull_request: permissions: contents: read pull-requests: write jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: make comment: runs-on: ubuntu-latest steps: - uses: some/publish-comment-action@v0
- Line 7: Job[build] should have explicit permissions.
- Line 12: Job[comment] should have explicit permissions.
Non-compliant example #2¶
Permissions are declared on the workflow level.
Note: This could be actually acceptable, because the workflow has only one job, but for consistency, copy-paste-ability, and habit-forming, it's better to still flag it to enforce declaring it on the job level.
One exemption from this rule is when the permission list only contains contents: read
.
This is for practical reasons, as this is quite a common workflow structure.
example.yml
on: push permissions: actions: read jobs: example: runs-on: ubuntu-latest steps: - run: echo "Example"
- Line 5: Job[example] should have explicit permissions.
Non-compliant example #3¶
Redundant permissions declared on workflow-level.
When declaring permissions on the workflow-level as well as the job-level, the job-level restricts the workflow-level permissions.
However, it is not necessary to declare permissions on the workflow-level, this can help reduce duplication and maintenance overhead.
See MissingJobPermissions
,
which help prevent accidental missing permissions.
example.yml
on: push permissions: contents: read jobs: example: permissions: contents: read runs-on: ubuntu-latest steps: - run: echo "Example"
- Line 4: Workflow[example] has redundant permissions.