From b2098e1dc0460fd08415720838b2dde34ab323e0 Mon Sep 17 00:00:00 2001 From: "Yousef Iskander (yousefi)" Date: Wed, 28 Oct 2026 20:56:30 -0400 Subject: [PATCH] Fix permissions for registry-bot to push PRs --- .github/tests/test_security_automation.py | 26 +++++++++++++++++++ .github/workflows/README.md | 31 +++++++++++++++++++++++ .github/workflows/update-registry.yml | 3 +++ 3 files changed, 60 insertions(+) diff --git a/.github/tests/test_security_automation.py b/.github/tests/test_security_automation.py index 22c0635..9048c6a 100644 --- a/.github/tests/test_security_automation.py +++ b/.github/tests/test_security_automation.py @@ -517,6 +517,32 @@ def test_registry_refresh_tracks_computed_tag_inputs(): } <= paths +def test_registry_refresh_requests_required_app_permissions(): + workflow = load_workflow( + REPO_ROOT / ".github" / "workflows" / "update-registry.yml" + ) + steps = workflow["jobs"]["update-registry"]["steps"] + token_step = next( + step for step in steps + if uses_action(step, "actions/create-github-app-token") + ) + + assert token_step["id"] == "app_token" + assert { + key: value for key, value in token_step["with"].items() + if key.startswith("permission-") + } == { + "permission-contents": "write", + "permission-pull-requests": "write", + } + + publish_step = next( + step for step in steps + if step.get("name") == "Commit updated registry to a PR" + ) + assert publish_step["env"]["GH_TOKEN"] == "${{ steps.app_token.outputs.token }}" + + def test_registry_refresh_cannot_rewrite_or_self_approve_generated_prs(): path = REPO_ROOT / ".github" / "workflows" / "update-registry.yml" workflow = load_workflow(path) diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 4ee3ccf..efc95e3 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -121,6 +121,37 @@ and promotion criteria. Catalog changes can also trigger **Update Registry**. Generated updates reach `main` through a separate reviewed registry PR, not automatic merging. +## Registry App setup + +[Update Registry](update-registry.yml) uses an installation token for +`discovery-registry-bot` to push its branch and create its PR. The App needs these +repository permissions: + +| Permission | Required access | +| --- | --- | +| Contents | Read and write | +| Pull requests | Read and write | + +An App owner must configure these permissions, and a Microsoft +organization/installation administrator must approve any updated grant for the +installation covering `microsoft/discovery`. Changing the App configuration +alone does not establish installation approval. Keep `REGISTRY_APP_ID` and +`REGISTRY_APP_PRIVATE_KEY` configured; a permission change does not require +rotating these credentials. + +The workflow explicitly requests `permission-contents: write` and +`permission-pull-requests: write` for its current-repository token. These inputs +cannot elevate the App's approved installation permissions: token creation fails +if the requested grant is unavailable. + +A branch push rejected with HTTP 403 for `discovery-registry-bot[bot]` is an +authorization failure. Check both the App's Contents write permission and the +installation's approval. Successful token creation without explicit permission +requests does not prove write access. Do not broaden workflow-level +`permissions` to fix an App token: those permissions govern `GITHUB_TOKEN`, which +remains read-only. Registry PRs still require human/CODEOWNER review and +maintainer-controlled merging. + ## Release refresh automation Three workflows work together to keep the mutable `current` tag, the diff --git a/.github/workflows/update-registry.yml b/.github/workflows/update-registry.yml index 1ef7ede..c5ae4c9 100644 --- a/.github/workflows/update-registry.yml +++ b/.github/workflows/update-registry.yml @@ -179,6 +179,7 @@ jobs: # because the default GITHUB_TOKEN is read-only. The App # (discovery-registry-bot) must have Contents: Write and Pull requests: # Write on microsoft/discovery. + # Explicit token requests cannot grant permissions the installation lacks. # This App still creates PRs; it does not approve or enroll auto-merge. # If REGISTRY_APP_ID is unset (App not yet wired up), this step is # skipped and the workflow fails without pushing any branch. @@ -189,6 +190,8 @@ jobs: with: app-id: ${{ secrets.REGISTRY_APP_ID }} private-key: ${{ secrets.REGISTRY_APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write - name: Commit updated registry to a PR if: steps.diff.outputs.changed == 'true' && steps.app_check.outputs.configured == 'true'