From b5270bf5de9e9e83dd06d66ab32d4240e40c5260 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sun, 19 Jul 2026 14:00:54 +0300 Subject: [PATCH 1/2] ci: mint dfcg-artifacts token via GitHub App instead of a PAT Replaces the long-lived, expiring DFCG_ARTIFACTS_ACCESS_TOKEN PAT with a short-lived installation token minted per-run from the blocksense-ci-token-provider GitHub App (app 3205470), scoped to contents:read on the private dfcg-artifacts repo. Removes the top-level DFCG_ARTIFACTS_ACCESS_TOKEN env and sets it only on the config-types test step (the sole consumer) from the minted token. --- .github/workflows/ci.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 667578cd98..f591827f1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,6 @@ concurrency: cancel-in-progress: true env: - DFCG_ARTIFACTS_ACCESS_TOKEN: ${{ secrets.DFCG_ARTIFACTS_ACCESS_TOKEN }} CMC_API_KEY: ${{ secrets.CMC_API_KEY }} APCA_API_KEY_ID: ${{ secrets.APCA_API_KEY_ID }} APCA_API_SECRET_KEY: ${{ secrets.APCA_API_SECRET_KEY }} @@ -56,6 +55,19 @@ jobs: steps: - uses: actions/checkout@v6 + # Mint a short-lived, contents:read token for the private + # dfcg-artifacts repo via the blocksense-ci-token-provider GitHub + # App, replacing the long-lived DFCG_ARTIFACTS_ACCESS_TOKEN PAT. + - name: Mint dfcg-artifacts token + id: dfcg-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.CI_TOKEN_PROVIDER_APP_ID }} + private-key: ${{ secrets.CI_TOKEN_PROVIDER_PRIVATE_KEY }} + owner: blocksense-network + repositories: dfcg-artifacts + permission-contents: read + - name: Install Nix uses: metacraft-labs/nixos-modules/.github/install-nix@main with: @@ -75,6 +87,8 @@ jobs: - name: Test all packages run: just test-ts + env: + DFCG_ARTIFACTS_ACCESS_TOKEN: ${{ steps.dfcg-token.outputs.token }} decoders-tests: timeout-minutes: 20 From 0dee5510a4aa042d44f3eec55d2f2468efba50d4 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Mon, 20 Jul 2026 03:07:06 +0300 Subject: [PATCH 2/2] fix(config-types): probe dfcg-artifacts repo, not /user, for token validity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App installation tokens (minted by the ci-token-provider GitHub App) are not users, so users.getAuthenticated() returns 403 "Resource not accessible by integration" even when the token can read the repo — which crashed the config-decoding suite instead of running it. Probe repos.get(dfcg-artifacts) instead: needs only metadata:read, works for both App tokens and PATs, and checks the access we actually rely on. Treat 401/403/404 as "can't read the repo" -> skip. --- .../src/dfcg/artifacts/downloader.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libs/ts/config-types/src/dfcg/artifacts/downloader.ts b/libs/ts/config-types/src/dfcg/artifacts/downloader.ts index 8570939576..f84867063a 100644 --- a/libs/ts/config-types/src/dfcg/artifacts/downloader.ts +++ b/libs/ts/config-types/src/dfcg/artifacts/downloader.ts @@ -82,19 +82,26 @@ export async function isTokenValid(): Promise { const octokit = new Octokit({ auth: GITHUB_TOKEN }); try { - await octokit.rest.users.getAuthenticated(); + // Probe access to the actual artifacts repo rather than `/user`. + // A GitHub App installation token (minted by the ci-token-provider + // App) is not a user, so `users.getAuthenticated()` returns 403 + // ("Resource not accessible by integration") even when the token can + // read the repo. `repos.get` only needs metadata:read and works for + // both App installation tokens and classic PATs — and it checks the + // thing we actually care about: can we reach dfcg-artifacts. + await octokit.rest.repos.get({ owner: OWNER, repo: REPO }); return true; } catch (error) { // Octokit can surface a RequestError from a differently-resolved copy of // `@octokit/request-error`, so `instanceof` is unreliable here. Fall back - // to the numeric `status` carried on the error so an invalid/expired token - // (401) makes us report the token as invalid — and callers skip — rather - // than crashing. + // to the numeric `status` carried on the error. A missing/expired/ + // unauthorized token (401/403/404) means we can't read the repo, so the + // callers skip rather than crash. const status = error instanceof RequestError ? error.status : (error as { status?: number } | null)?.status; - if (status === 401) { + if (status === 401 || status === 403 || status === 404) { return false; } throw error;