From 18ed087999eaa57b1f5c3eff728dbdac7bb80652 Mon Sep 17 00:00:00 2001 From: Philip Hartlieb Date: Wed, 21 Oct 2026 17:16:52 -0400 Subject: [PATCH 1/2] ci: read the bot's key from the stable-only data-update environment daily-update.yml and python-tests.yml's publish job, the only two jobs that use cloudcheck-update-bot's key, now run in the data-update environment (deployment branch: stable only, no admin bypass). They read the App ID from the environment variable CLOUDCHECK_APP_ID and the key (and BBOT_IO_API_KEY) from environment secrets, so no other branch, pull request or dispatched copy of a workflow can read them. The App token is minted for this repository only, with contents write only, and both jobs drop to contents: read: the push and the tag push already use the App token, not GITHUB_TOKEN. Safe to merge before the new key is in place: inside an environment job an environment secret overrides the repository secret of the same name, and the repository secret still applies when the environment has none. Refs blacklanternsecurity/bbot-enterprise#406, blacklanternsecurity/.github-config#16 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/daily-update.yml | 13 +++++++++++-- .github/workflows/python-tests.yml | 10 ++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/daily-update.yml b/.github/workflows/daily-update.yml index 192b0b8..b042e88 100644 --- a/.github/workflows/daily-update.yml +++ b/.github/workflows/daily-update.yml @@ -15,16 +15,25 @@ concurrency: jobs: update: runs-on: ubuntu-24.04 + # The bot's key and BBOT_IO_API_KEY live only in this environment, which + # accepts the stable branch only, so no other branch, pull request or + # dispatched copy of this workflow can read them. + environment: data-update + # The push uses the App token below, not GITHUB_TOKEN. permissions: - contents: write + contents: read steps: - name: Generate app token id: app-token uses: actions/create-github-app-token@v3 with: - app-id: ${{ secrets.CLOUDCHECK_APP_ID }} + app-id: ${{ vars.CLOUDCHECK_APP_ID }} private-key: ${{ secrets.CLOUDCHECK_APP_PRIVATE_KEY }} + # Only this repository, only contents write: the most the bot needs. + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + permission-contents: write - name: Checkout stable branch uses: actions/checkout@v7 diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index a618029..1af8a6c 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -47,15 +47,21 @@ jobs: runs-on: ubuntu-24.04 needs: test if: github.event_name == 'push' && github.ref == 'refs/heads/stable' + # Reads the bot's key from data-update (stable only), like daily-update.yml. + # The tag push uses the App token, not GITHUB_TOKEN. + environment: data-update permissions: - contents: write + contents: read steps: - name: Generate app token id: app-token uses: actions/create-github-app-token@v3 with: - app-id: ${{ secrets.CLOUDCHECK_APP_ID }} + app-id: ${{ vars.CLOUDCHECK_APP_ID }} private-key: ${{ secrets.CLOUDCHECK_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + permission-contents: write - uses: actions/checkout@v7 with: fetch-depth: 0 From 5ccaf6e600deddb7f653809a76e07fd30e092748 Mon Sep 17 00:00:00 2001 From: Philip Hartlieb Date: Thu, 22 Oct 2026 13:12:28 -0400 Subject: [PATCH 2/2] ci: scope the bot token to this repo; tag releases with GITHUB_TOKEN Review on #323: - create-github-app-token with owner set and repositories empty mints a token for every repository in the installation. On a scheduled run github.event.repository.name may be empty, so drop owner and repositories: with neither set, the token covers only this repository. - publish used the bot's key only to push the release tag, and held a token that can push straight to stable while maturin runs third-party build code. It now pushes the tag with GITHUB_TOKEN (contents: write), which the stable PR rule applies to, and leaves data-update. Checkout keeps no credentials, so only the tag step gets the token; the build steps can't push branches or move tags. daily-update is now the only job that uses the bot's key. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/daily-update.yml | 6 +++--- .github/workflows/python-tests.yml | 24 +++++++++--------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/.github/workflows/daily-update.yml b/.github/workflows/daily-update.yml index b042e88..340827c 100644 --- a/.github/workflows/daily-update.yml +++ b/.github/workflows/daily-update.yml @@ -30,9 +30,9 @@ jobs: with: app-id: ${{ vars.CLOUDCHECK_APP_ID }} private-key: ${{ secrets.CLOUDCHECK_APP_PRIVATE_KEY }} - # Only this repository, only contents write: the most the bot needs. - owner: ${{ github.repository_owner }} - repositories: ${{ github.event.repository.name }} + # Only contents write: the most the bot needs. With no owner or + # repositories input, the token covers only this repository; setting + # owner alone would widen it to every repository in the installation. permission-contents: write - name: Checkout stable branch diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 1af8a6c..5249878 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -47,25 +47,17 @@ jobs: runs-on: ubuntu-24.04 needs: test if: github.event_name == 'push' && github.ref == 'refs/heads/stable' - # Reads the bot's key from data-update (stable only), like daily-update.yml. - # The tag push uses the App token, not GITHUB_TOKEN. - environment: data-update + # The tag push uses GITHUB_TOKEN, not the bot's key: the PR rule on stable + # applies to GITHUB_TOKEN, so this job can't push to stable. permissions: - contents: read + contents: write steps: - - name: Generate app token - id: app-token - uses: actions/create-github-app-token@v3 - with: - app-id: ${{ vars.CLOUDCHECK_APP_ID }} - private-key: ${{ secrets.CLOUDCHECK_APP_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - repositories: ${{ github.event.repository.name }} - permission-contents: write + # No credentials left in .git/config, so the build steps below (third-party + # build code) can't push. Only the tag step gets the token. - uses: actions/checkout@v7 with: fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} + persist-credentials: false - name: Set up Python uses: actions/setup-python@v7 with: @@ -96,11 +88,13 @@ jobs: run: uv run maturin publish --skip-existing --username __token__ --password ${{ secrets.PYPI_TOKEN }} - name: Tag release if: steps.version_check.outputs.changed == 'true' + env: + GH_TOKEN: ${{ github.token }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git tag -a "${{ steps.get_version.outputs.VERSION }}" -m "Release ${{ steps.get_version.outputs.VERSION }}" - git push origin "refs/tags/${{ steps.get_version.outputs.VERSION }}" + git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "refs/tags/${{ steps.get_version.outputs.VERSION }}" linux: runs-on: ${{ matrix.platform.runner }} needs: publish