diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 194b1da..d6c58e2 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -93,13 +93,16 @@ on: DOCKERHUB_TOKEN: description: "Docker Hub token for dhi.io base images and docker.io pushes; only read when configured" +# Read-only by default; each job that pushes to GHCR asks for `packages: write` itself. permissions: contents: read - packages: write jobs: build: name: Build ${{ matrix.platform.platform }} + permissions: + contents: read + packages: write strategy: fail-fast: true matrix: @@ -243,6 +246,9 @@ jobs: merge: name: Publish multi-arch manifests needs: build + permissions: + contents: read + packages: write runs-on: ubuntu-24.04 timeout-minutes: 15 steps: diff --git a/tests/test_docker_build.py b/tests/test_docker_build.py index 1d83ab1..c94e259 100644 --- a/tests/test_docker_build.py +++ b/tests/test_docker_build.py @@ -17,6 +17,15 @@ class DockerBuildWorkflowTests(unittest.TestCase): self.assertIn("DOCKERHUB_TOKEN", call["secrets"]) self.assertNotIn("DIGITALOCEAN_TOKEN", call["secrets"]) + def test_write_permission_is_job_scoped(self): + """Only the pushing jobs get `packages: write` (SonarCloud githubactions:S8233).""" + self.assertEqual(WORKFLOW["permissions"], {"contents": "read"}) + for job in ("build", "merge"): + self.assertEqual( + WORKFLOW["jobs"][job]["permissions"], + {"contents": "read", "packages": "write"}, + ) + def test_dockerhub_login_in_build_job(self): steps = WORKFLOW["jobs"]["build"]["steps"] dh_step = next(