diff --git a/.github/workflows/github-governance.yml b/.github/workflows/github-governance.yml index 262ada2..d0b7964 100644 --- a/.github/workflows/github-governance.yml +++ b/.github/workflows/github-governance.yml @@ -47,7 +47,7 @@ jobs: persist-credentials: false - name: Plan repository governance (read only) - uses: LibreCodeCoop/github-governance@07896408fd605204d1c7c95c71b0f5785fcf8287 # public planning support; pin immutable commit + uses: LibreCodeCoop/github-governance@76fcdc8ae250c81aeb055533c2741385ac9f0b7b # public planning support; pin immutable commit with: token: ${{ github.token }} organization: ${{ inputs.repository == '' && 'LibreCodeCoop' || '' }} @@ -82,13 +82,14 @@ jobs: owner: LibreCodeCoop # zizmor: ignore[github-app] governance is explicitly organization-scoped repositories: ${{ inputs.repository }} permission-administration: write - # Needed to identify Nextcloud apps via appinfo/info.xml and inspect default branches. - permission-contents: read + # Content classification uses the caller's short-lived, read-only GITHUB_TOKEN. + # The privileged GitHub App must not require Contents permission. - name: Apply repository governance - uses: LibreCodeCoop/github-governance@07896408fd605204d1c7c95c71b0f5785fcf8287 # public planning support; pin immutable commit + uses: LibreCodeCoop/github-governance@76fcdc8ae250c81aeb055533c2741385ac9f0b7b # public planning support; pin immutable commit with: token: ${{ steps.app-token.outputs.token }} + contents-token: ${{ github.token }} organization: ${{ inputs.repository == '' && 'LibreCodeCoop' || '' }} repository: ${{ inputs.repository != '' && format('LibreCodeCoop/{0}', inputs.repository) || '' }} config: ${{ github.workspace }}/governance.config.json diff --git a/docs/repository-governance.md b/docs/repository-governance.md index 2874fe1..f164ce7 100644 --- a/docs/repository-governance.md +++ b/docs/repository-governance.md @@ -130,9 +130,11 @@ with `repository` set to the target repository, `apply=true` and the default The normal full-reconciliation mode remains possible only by setting `onboard_only=false` during an explicitly approved manual run. It must not be -used merely to get an onboarding job green. The Apply token needs repository -Administration (write) and Contents (read) and should be scoped to the named -repository. The GitHub App must be installed on that repository. Private and +used merely to get an onboarding job green. The Apply GitHub App token needs +repository Administration (write) only and should be scoped to the named +repository. The separate caller `GITHUB_TOKEN` supplies read-only public +repository content classification; it is never used for administration or +mutations. The GitHub App must be installed on that repository. Private and archived repositories are outside the configured public discovery scope. **Nextcloud translation bot safeguard.** For repositories with diff --git a/tests/test_governance_ci_gate_contract.py b/tests/test_governance_ci_gate_contract.py index 0502dea..d1d4361 100644 --- a/tests/test_governance_ci_gate_contract.py +++ b/tests/test_governance_ci_gate_contract.py @@ -52,7 +52,9 @@ def test_common_governance_onboarding_is_scoped_and_approval_gated(self) -> None from pathlib import Path workflow = (ROOT / '.github/workflows/github-governance.yml').read_text() self.assertIn('onboard-only:', workflow) - self.assertIn('permission-contents: read', workflow) + self.assertIn('permission-administration: write', workflow) + self.assertNotIn('permission-contents: read', workflow) + self.assertIn('contents-token: ${{ github.token }}', workflow) self.assertIn('environment: ruleset-sync', workflow) self.assertIn('onboard_only:', workflow) self.assertIn("cron: '17 6 * * *'", workflow)