Skip to content

Harden CI/CD workflow token permissions (OpenSSF Scorecard Token-Permissions: 0) #1645

Description

@anshul23102

Summary

.github/workflows/ci_cd.yml does not declare a top-level permissions: block, and 8 of its 11 jobs declare no permissions: of their own. Those jobs run with the repository's default GITHUB_TOKEN scope rather than an explicit least-privilege scope.

OpenSSF Scorecard currently reports Token-Permissions: 0 for this repository (overall score 6.2, scan dated 2026-07-27).

Current state

Job Declared permissions
pre-commit none
code-ql security-events: write
run-tests none
build-package none
test-build-package none
test-docker-image none
test-docker-image-build none
publish-nettacker-dev-to-docker-registry none
publish-nettacker-latest-to-docker-registry none
publish-to-test-pypi contents: read, id-token: write
publish-to-pypi contents: read, id-token: write

The two PyPI publishing jobs already follow least privilege. The pattern was simply never extended to the rest of the file.

Why this is worth fixing

The practical impact depends on the organisation and repository default token setting, which is not visible externally, so this is defence in depth rather than a directly exploitable issue. Two things make it worth doing anyway:

  1. Two of the unscoped jobs (publish-nettacker-dev-to-docker-registry and publish-nettacker-latest-to-docker-registry) handle DOCKER_HUB_ACCESS_TOKEN and push owasp/nettacker:latest. These are the jobs where a broadly scoped ambient token is least desirable.
  2. An explicit permissions: {} default means any future job added to this workflow starts from zero rather than inheriting whatever the repository default happens to be at that time.

Proposed change

  • Add permissions: {} at the workflow level as a deny-by-default.
  • Add permissions: contents: read to the eight jobs that currently declare none.
  • Add the missing contents: read to code-ql alongside its existing security-events: write.

No job gains a permission it does not already have in practice, and no job logic changes. The diff is additive: 19 added lines, 0 removed.

Verification

  • Workflow still parses; check-yaml from the existing pre-commit config passes.
  • All 11 jobs end up with an explicit, minimal scope.
  • Only code-ql (security-events: write) and the two PyPI jobs (id-token: write) retain any write scope, which they require.

Note on the rest of the Scorecard report

Scorecard also reports Dangerous-Workflow: 0, but that scan predates ca200a1 (2026-08-01), which migrated the PR check to a SHA-pinned action with scoped permissions. That finding appears to be already resolved and should clear on the next scan. This issue is only about Token-Permissions.

I would like to work on this. @securestep9 could you assign it to me? I understand PRs are auto-closed without an assigned issue reference.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions