From dc4e7ccd71e9b048a1194cbf812517904e130651 Mon Sep 17 00:00:00 2001 From: Not Darko <93942788+darkobas2@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:34:45 +0200 Subject: [PATCH 1/2] ci: publish to batch-archive with a scoped bee-runner App token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GHA_PAT_BASIC org secret was never shared with this repo, so `Checkout batch-archive` resolved its token to empty and failed with "Input required and not supplied: token". Rather than widen that PAT (contents:write across 16 repos, tied to a personal account), mint a token from the bee-runner GitHub App, which is already installed org-wide with contents:write. The token is scoped to batch-archive alone, narrowed to contents:write, and expires in ~1h. Because of that expiry it is minted after the export, not at checkout — the export can easily outrun an hour. batch-archive is public, so the initial clone needs no App token at all; it uses GITHUB_TOKEN with persist-credentials: false so nothing long-lived sits in .git/config while the export runs, and the final push carries the App token itself. --- .github/workflows/batch-sync.yml | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/.github/workflows/batch-sync.yml b/.github/workflows/batch-sync.yml index 4a9fc78..821e1f2 100644 --- a/.github/workflows/batch-sync.yml +++ b/.github/workflows/batch-sync.yml @@ -15,9 +15,10 @@ name: Batch Sync # Security: dispatch inputs reach run: scripts only via env — `${{ }}` # interpolation inside run: lets a crafted input inject shell. # -# Secrets: GHA_PAT_BASIC needs contents:write on ethersphere/batch-archive; -# PRIVATE_GNOSIS_RPC_URL is required; GNOSIS_RPC_USER / GNOSIS_RPC_PASSWORD -# are needed only off-allowlist. +# Secrets: BEE_RUNNER_APP_ID / BEE_RUNNER_KEY mint a short-lived App token +# scoped to ethersphere/batch-archive for the publish push (the App is already +# installed org-wide with contents:write); PRIVATE_GNOSIS_RPC_URL is required; +# GNOSIS_RPC_USER / GNOSIS_RPC_PASSWORD are needed only off-allowlist. on: workflow_dispatch: @@ -82,7 +83,11 @@ jobs: uses: actions/checkout@v5 with: repository: ethersphere/batch-archive - token: ${{ secrets.GHA_PAT_BASIC }} + # Public repo: the default token can read it. Write happens at the end + # with an App token, so no long-lived credential sits in .git/config + # for the length of the export. + token: ${{ github.token }} + persist-credentials: false path: batch-archive fetch-depth: 0 @@ -151,9 +156,23 @@ jobs: --slim=true \ --verbosity "${VERBOSITY}" + # Minted here, not at checkout: an App token lives ~1h and the export + # above can outrun that. permission-contents keeps it to what the push + # needs, and repositories keeps it off every other repo in the org. + - name: Generate token for batch-archive + id: archive-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.BEE_RUNNER_APP_ID }} + private-key: ${{ secrets.BEE_RUNNER_KEY }} + owner: ethersphere + repositories: batch-archive + permission-contents: write + - name: Publish to batch-archive env: TRIGGERED_BY: ${{ github.actor }} + ARCHIVE_TOKEN: ${{ steps.archive-token.outputs.token }} run: | set -euo pipefail cp snapshot.ndjson.gzip batch-archive/archive/export.ndjson.gzip @@ -193,5 +212,7 @@ jobs: git tag "${new_tag}" # --atomic: a rejected push to main rejects the tag too, so no # orphaned tag can become a later run's resume point. - git push --atomic origin HEAD:main "refs/tags/${new_tag}" + git push --atomic \ + "https://x-access-token:${ARCHIVE_TOKEN}@github.com/ethersphere/batch-archive.git" \ + HEAD:main "refs/tags/${new_tag}" echo "::notice::published snapshot at block ${last_block} as ${new_tag} (resumed from ${ARCHIVE_TAG})" From f17af227ab0f96f6b3ef64ce444c9568c2142a71 Mon Sep 17 00:00:00 2001 From: Not Darko <93942788+darkobas2@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:51:52 +0200 Subject: [PATCH 2/2] ci: preflight the App token and keep it out of git argv Addresses both review comments on #17. Minting the publish token after the export means an App misconfiguration surfaces up to 120 minutes in, where the empty GHA_PAT_BASIC failed in seconds. Mint once at the top of the job purely as a check; the token is never used and is revoked by the post step. Passing the token in the remote URL puts it in git argv, readable via ps -eo args or /proc//cmdline. On the self-hosted bee runners those are long-lived shared machines, so a co-tenant job could lift it and hold contents:write on batch-archive until revocation. Read it from the environment through a credential helper instead. --- .github/workflows/batch-sync.yml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/batch-sync.yml b/.github/workflows/batch-sync.yml index 821e1f2..5f79fc7 100644 --- a/.github/workflows/batch-sync.yml +++ b/.github/workflows/batch-sync.yml @@ -64,6 +64,20 @@ jobs: timeout-minutes: 120 steps: + # Preflight: the real token is minted after the export (see below), which + # would push an App misconfiguration — lost contents:write, key no longer + # shared with this repo — to the end of a run that can take two hours. + # Minting once up front fails in seconds instead. This token is never + # used; create-github-app-token revokes it in its post step. + - name: Verify batch-archive App access + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.BEE_RUNNER_APP_ID }} + private-key: ${{ secrets.BEE_RUNNER_KEY }} + owner: ethersphere + repositories: batch-archive + permission-contents: write + - name: Checkout uses: actions/checkout@v5 @@ -212,7 +226,10 @@ jobs: git tag "${new_tag}" # --atomic: a rejected push to main rejects the tag too, so no # orphaned tag can become a later run's resume point. - git push --atomic \ - "https://x-access-token:${ARCHIVE_TOKEN}@github.com/ethersphere/batch-archive.git" \ - HEAD:main "refs/tags/${new_tag}" + # A token in the remote URL lands in git's argv, readable via + # `ps -eo args` by anything else on these long-lived shared runners. + # A credential helper reads it from the environment instead; the + # helper written to .git/config holds no secret itself. + git config credential.helper '!f() { echo username=x-access-token; echo "password=${ARCHIVE_TOKEN}"; }; f' + git push --atomic origin HEAD:main "refs/tags/${new_tag}" echo "::notice::published snapshot at block ${last_block} as ${new_tag} (resumed from ${ARCHIVE_TAG})"