diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index a443159..de241d9 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -16,6 +16,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + persist-credentials: false fetch-depth: 0 - name: Check release metadata diff --git a/.github/workflows/cut-release.yml b/.github/workflows/cut-release.yml index cb0b086..34ab273 100644 --- a/.github/workflows/cut-release.yml +++ b/.github/workflows/cut-release.yml @@ -48,12 +48,17 @@ jobs: client-id: Iv23liKBX2RYMoZIYuKa private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} owner: hotdata-dev + repositories: sdk-rust + permission-contents: write + permission-pull-requests: write - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.app-token.outputs.token }} fetch-depth: 0 fetch-tags: true + # release.sh pushes the release branch / tag with these credentials. + persist-credentials: true - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable with: @@ -68,8 +73,9 @@ jobs: - name: Configure git identity env: GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} run: | - slug="${{ steps.app-token.outputs.app-slug }}[bot]" + slug="${APP_SLUG}[bot]" id="$(gh api "/users/${slug}" --jq .id)" git config user.name "$slug" git config user.email "${id}+${slug}@users.noreply.github.com" @@ -80,10 +86,13 @@ jobs: - name: Point origin/HEAD at the default branch run: git remote set-head origin --auto + # Pass the dispatch input through env, never `${{ }}` inside run:, so a + # crafted value can't inject shell. release.sh validates its format. - name: Prepare release env: GH_TOKEN: ${{ steps.app-token.outputs.token }} - run: ./scripts/release.sh prepare "${{ inputs.bump }}" + BUMP: ${{ inputs.bump }} + run: ./scripts/release.sh prepare "$BUMP" # Branch protection on main gates the merge on CI plus the review gate, # so this only merges once everything is green. @@ -111,6 +120,9 @@ jobs: client-id: Iv23liKBX2RYMoZIYuKa private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} owner: hotdata-dev + repositories: sdk-rust + permission-contents: write + permission-pull-requests: write - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -118,6 +130,8 @@ jobs: token: ${{ steps.app-token.outputs.token }} fetch-depth: 0 fetch-tags: true + # release.sh pushes the release branch / tag with these credentials. + persist-credentials: true # release.sh reads the version with `cargo metadata`. - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable @@ -127,8 +141,9 @@ jobs: - name: Configure git identity env: GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} run: | - slug="${{ steps.app-token.outputs.app-slug }}[bot]" + slug="${APP_SLUG}[bot]" id="$(gh api "/users/${slug}" --jq .id)" git config user.name "$slug" git config user.email "${id}+${slug}@users.noreply.github.com" diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index d29b3c1..6a935c0 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -7,10 +7,13 @@ on: branches: [main] workflow_dispatch: +permissions: + contents: read + jobs: # Parity check runs on every PR and push: confirms every scenario in - # www.hotdata.dev/api/test-scenarios.yaml has a matching test file in this - # repo. www.hotdata.dev is private, so we fetch via the GitHub App token — + # hotdata-dev/www api/test-scenarios.yaml has a matching test file in this + # repo. hotdata-dev/www is private, so we fetch via the GitHub App token — # same pattern as regenerate.yml. sdk-rust convention: tests/.rs. # Scenarios listing 'rust' in optional_for are skipped (per-scenario rollout). scenario-parity: @@ -23,7 +26,11 @@ jobs: client-id: Iv23liKBX2RYMoZIYuKa private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} owner: hotdata-dev + repositories: www + permission-contents: read - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Fetch scenarios manifest env: GH_TOKEN: ${{ steps.app-token.outputs.token }} @@ -31,7 +38,7 @@ jobs: curl -sS -f -L \ -H "Accept: application/vnd.github.v3.raw" \ -H "Authorization: Bearer $GH_TOKEN" \ - https://api.github.com/repos/hotdata-dev/www.hotdata.dev/contents/api/test-scenarios.yaml \ + https://api.github.com/repos/hotdata-dev/www/contents/api/test-scenarios.yaml \ -o test-scenarios.yaml - name: Check parity run: | @@ -80,6 +87,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable with: toolchain: stable diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 831ae01..3bc7ce5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,6 +25,8 @@ jobs: url: https://crates.io/crates/hotdata steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable with: diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index 7452440..11b9aaa 100644 --- a/.github/workflows/regenerate.yml +++ b/.github/workflows/regenerate.yml @@ -12,6 +12,8 @@ on: required: false type: string +permissions: {} + jobs: regenerate: runs-on: ubuntu-latest @@ -23,10 +25,28 @@ jobs: client-id: Iv23liKBX2RYMoZIYuKa private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} owner: hotdata-dev + repositories: sdk-rust + permission-contents: write + permission-pull-requests: write + # Separate read-only token for the private www repo (spec + scenarios), + # so the write token above never reaches it. + - name: Generate www read token + id: www-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: Iv23liKBX2RYMoZIYuKa + private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} + owner: hotdata-dev + repositories: www + permission-contents: read + + # create-pull-request configures its own auth from its `token` input, so + # don't leave the app token in .git/config for the generator/cargo steps. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.app-token.outputs.token }} + persist-credentials: false - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable with: @@ -35,12 +55,12 @@ jobs: - name: Fetch merged OpenAPI spec env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} + GH_TOKEN: ${{ steps.www-token.outputs.token }} run: | curl -sS -f -L \ -H "Accept: application/vnd.github.v3.raw" \ -H "Authorization: Bearer $GH_TOKEN" \ - https://api.github.com/repos/hotdata-dev/www.hotdata.dev/contents/api/openapi.yaml \ + https://api.github.com/repos/hotdata-dev/www/contents/api/openapi.yaml \ -o openapi.yaml # OpenAPI 3.1 lets a `$ref` carry sibling keywords (e.g. a per-branch @@ -152,7 +172,7 @@ jobs: -g rust \ -o . \ -t .openapi-generator-templates \ - --additional-properties=packageName=hotdata,packageVersion=$PACKAGE_VERSION,library=reqwest,supportAsync=true,useChrono=false \ + --additional-properties="packageName=hotdata,packageVersion=${PACKAGE_VERSION},library=reqwest,supportAsync=true,useChrono=false" \ --type-mappings=JsonCell=JsonCell \ --import-mappings=JsonCell=crate::models::JsonCell \ --skip-validate-spec @@ -309,12 +329,12 @@ jobs: # land); the enforcing gate lives in integration-tests.yml. - name: Check integration test scenario parity env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} + GH_TOKEN: ${{ steps.www-token.outputs.token }} run: | curl -sS -f -L \ -H "Accept: application/vnd.github.v3.raw" \ -H "Authorization: Bearer $GH_TOKEN" \ - https://api.github.com/repos/hotdata-dev/www.hotdata.dev/contents/api/test-scenarios.yaml \ + https://api.github.com/repos/hotdata-dev/www/contents/api/test-scenarios.yaml \ -o test-scenarios.yaml python3 - <<'PY' import sys, pathlib, re @@ -371,7 +391,8 @@ jobs: if: steps.cpr.outputs.pull-request-number env: GH_TOKEN: ${{ steps.app-token.outputs.token }} + PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }} run: | - gh pr merge "${{ steps.cpr.outputs.pull-request-number }}" \ - --repo "${{ github.repository }}" \ + gh pr merge "$PR_NUMBER" \ + --repo "$GITHUB_REPOSITORY" \ --squash --auto diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cdf5316..ed9f2de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,6 +37,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + persist-credentials: false ref: ${{ env.RELEASE_TAG }} fetch-depth: 0