diff --git a/.github/workflows/batch-sync.yml b/.github/workflows/batch-sync.yml index 4a9fc78..5f79fc7 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: @@ -63,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 @@ -82,7 +97,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 +170,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 +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. + # 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})"