From d3df2e57f8ac9afc324b9f1913b214358ab7e8b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 13:49:14 +0000 Subject: [PATCH 1/9] SRE-904: Split token minting from the release run Port the hashintel/.github#99 pattern to release.yml: only a new mint-token job holds id-token: write and runs no repository dependency code; the release job receives the App token as ciphertext and holds no OIDC permission, so compromised build/publish dependencies can no longer mint fresh Vault-signed App JWTs. Also drop the unused id-token grant from deploy.yml's setup job, bump hashintel/.github pins to the merge commit of that PR, and pass the now-required RENOVATE_TOKEN_ENC_KEY through to the housekeeping workflow. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018HPkZcHmt5qmnGHtVB96d7 --- .github/workflows/deploy.yml | 5 +- .github/workflows/housekeeping.yml | 3 +- .github/workflows/preflight-todo-comments.yml | 2 +- .github/workflows/preflight.yml | 4 +- .github/workflows/release.yml | 58 +++++++++++++++++-- 5 files changed, 62 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4e862b8b9c5..1e7da2f722c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -37,8 +37,11 @@ concurrency: jobs: setup: runs-on: ubuntu-24.04 + # No `id-token`: this job runs dependency code (`turbo query`) but consumes + # no OIDC — it passes `rust: false`, and the sccache credential fetch from + # Vault is the only OIDC consumer in `install-tools`. permissions: - id-token: write + contents: read outputs: sourcemaps: ${{ steps.packages.outputs.sourcemaps }} backend-images: ${{ steps.packages.outputs.backend-images }} diff --git a/.github/workflows/housekeeping.yml b/.github/workflows/housekeeping.yml index 9793b99ac7b..67080ab3bd7 100644 --- a/.github/workflows/housekeeping.yml +++ b/.github/workflows/housekeeping.yml @@ -65,7 +65,7 @@ jobs: actions: read contents: read id-token: write - uses: hashintel/.github/.github/workflows/housekeeping-dependencies.yml@8c1a3b18a4f6528fc8eabcf9acda7780a1e47776 # main + uses: hashintel/.github/.github/workflows/housekeeping-dependencies.yml@b7a5d7f651c1d6a862d5dd97aa6164125bedb6c5 # main with: repoCache: ${{ inputs.repoCache || 'enabled' }} logLevel: ${{ inputs.logLevel || 'info' }} @@ -73,3 +73,4 @@ jobs: dryRun: ${{ inputs.dryRun || 'disabled' }} secrets: CF_ACCESS_STAGE_CLIENT_SECRET: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + RENOVATE_TOKEN_ENC_KEY: ${{ secrets.RENOVATE_TOKEN_ENC_KEY }} diff --git a/.github/workflows/preflight-todo-comments.yml b/.github/workflows/preflight-todo-comments.yml index f30384bda6f..972e941ab9a 100644 --- a/.github/workflows/preflight-todo-comments.yml +++ b/.github/workflows/preflight-todo-comments.yml @@ -11,4 +11,4 @@ jobs: name: Todo comments permissions: contents: read - uses: hashintel/.github/.github/workflows/preflight-todo-comments.yml@8c1a3b18a4f6528fc8eabcf9acda7780a1e47776 # main + uses: hashintel/.github/.github/workflows/preflight-todo-comments.yml@b7a5d7f651c1d6a862d5dd97aa6164125bedb6c5 # main diff --git a/.github/workflows/preflight.yml b/.github/workflows/preflight.yml index 45394a48b22..337e6bdc84d 100644 --- a/.github/workflows/preflight.yml +++ b/.github/workflows/preflight.yml @@ -14,7 +14,7 @@ jobs: permissions: contents: read pull-requests: write - uses: hashintel/.github/.github/workflows/preflight-dependencies.yml@8c1a3b18a4f6528fc8eabcf9acda7780a1e47776 # main + uses: hashintel/.github/.github/workflows/preflight-dependencies.yml@b7a5d7f651c1d6a862d5dd97aa6164125bedb6c5 # main with: # GHSA-frvp-7c67-39w9: advisory has a wrong affected-version range; the fix was # backported in @hono/node-server. Remove once github/advisory-database#8824 merges. @@ -37,4 +37,4 @@ jobs: name: PR title permissions: contents: read - uses: hashintel/.github/.github/workflows/preflight-pr-title.yml@8c1a3b18a4f6528fc8eabcf9acda7780a1e47776 # main + uses: hashintel/.github/.github/workflows/preflight-pr-title.yml@b7a5d7f651c1d6a862d5dd97aa6164125bedb6c5 # main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fb134e66d53..b1566b32073 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,13 @@ ## Uses a GitHub App installation token so that ‘Version Packages’ PRs trigger ## downstream workflows (GITHUB_TOKEN-created PRs do not). ## https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs +## +## Minting and publishing are separate jobs. Only the mint job holds +## `id-token: write`, and it runs no repository dependency code, so a +## compromised build-time dependency in the release job cannot reach Vault to +## sign fresh App JWTs. The token crosses the job boundary encrypted: GitHub +## drops masked values from job outputs, so the plaintext could not make the +## trip anyway, and the ciphertext is useless without `RELEASE_TOKEN_ENC_KEY`. name: Release on: @@ -8,17 +15,21 @@ on: branches: - main jobs: - release: + mint-token: + name: Mint token runs-on: ubuntu-latest if: github.repository == 'hashintel/hash' environment: main permissions: + contents: read id-token: write + outputs: + token-ciphertext: ${{ steps.encrypt.outputs.token-ciphertext }} steps: - name: Get token id: app-token - uses: hashintel/.github/.github/actions/github-app-token@8c1a3b18a4f6528fc8eabcf9acda7780a1e47776 # main + uses: hashintel/.github/.github/actions/github-app-token@b7a5d7f651c1d6a862d5dd97aa6164125bedb6c5 # main with: vault-address: ${{ vars.VAULT_PROD_ADDR }} vault-role: ci-hash-release @@ -28,17 +39,54 @@ jobs: cf-access-client-id: ${{ vars.CF_ACCESS_PROD_CLIENT_ID }} cf-access-client-secret: ${{ secrets.CF_ACCESS_PROD_CLIENT_SECRET }} + # The token is already masked, and GitHub silently drops masked values + # from job outputs, so only ciphertext can cross the job boundary. The + # plaintext must never be echoed here. + - name: Encrypt token + id: encrypt + env: + TOKEN: ${{ steps.app-token.outputs.token }} + ENC_KEY: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} + run: | + : "${TOKEN:?}" "${ENC_KEY:?}" + ciphertext=$(printf '%s' "${TOKEN}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass env:ENC_KEY -base64 -A) + echo "token-ciphertext=${ciphertext}" >>"${GITHUB_OUTPUT}" + + release: + runs-on: ubuntu-latest + needs: mint-token + environment: main + # No `id-token` here: compromised dependency code in this job gets at most + # the hour-long installation token, not the ability to mint new ones. + permissions: + contents: read + + steps: + # First step, so the plaintext is masked before anything else runs. + - name: Decrypt token + id: app-token + env: + TOKEN_CIPHERTEXT: ${{ needs.mint-token.outputs.token-ciphertext }} + ENC_KEY: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} + run: | + : "${TOKEN_CIPHERTEXT:?}" "${ENC_KEY:?}" + token=$(printf '%s' "${TOKEN_CIPHERTEXT}" | openssl enc -d -aes-256-cbc -pbkdf2 -pass env:ENC_KEY -base64 -A) + echo "::add-mask::${token}" + echo "token=${token}" >>"${GITHUB_OUTPUT}" + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.app-token.outputs.token }} + # `sccache: false` because this job intentionally holds no `id-token` + # permission, which the sccache credential fetch from Vault relies on. + # Rust itself stays: publishing `@blockprotocol/type-system` builds the + # wasm artifact from the Rust crate via wasm-pack. - name: Install tools uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + sccache: false - name: Warm up repository uses: ./.github/actions/warm-up-repo From 4ba42f933411885087817515ece9e802d6bdef09 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 16:29:53 +0000 Subject: [PATCH 2/9] SRE-904: Trim workflow comments --- .github/workflows/deploy.yml | 3 --- .github/workflows/release.yml | 18 ++---------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1e7da2f722c..d0caa56cce0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -37,9 +37,6 @@ concurrency: jobs: setup: runs-on: ubuntu-24.04 - # No `id-token`: this job runs dependency code (`turbo query`) but consumes - # no OIDC — it passes `rust: false`, and the sccache credential fetch from - # Vault is the only OIDC consumer in `install-tools`. permissions: contents: read outputs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1566b32073..a7149f279db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,13 +1,6 @@ ## Uses a GitHub App installation token so that ‘Version Packages’ PRs trigger ## downstream workflows (GITHUB_TOKEN-created PRs do not). ## https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs -## -## Minting and publishing are separate jobs. Only the mint job holds -## `id-token: write`, and it runs no repository dependency code, so a -## compromised build-time dependency in the release job cannot reach Vault to -## sign fresh App JWTs. The token crosses the job boundary encrypted: GitHub -## drops masked values from job outputs, so the plaintext could not make the -## trip anyway, and the ciphertext is useless without `RELEASE_TOKEN_ENC_KEY`. name: Release on: @@ -39,9 +32,7 @@ jobs: cf-access-client-id: ${{ vars.CF_ACCESS_PROD_CLIENT_ID }} cf-access-client-secret: ${{ secrets.CF_ACCESS_PROD_CLIENT_SECRET }} - # The token is already masked, and GitHub silently drops masked values - # from job outputs, so only ciphertext can cross the job boundary. The - # plaintext must never be echoed here. + # GitHub drops masked values from job outputs, so only ciphertext can cross. - name: Encrypt token id: encrypt env: @@ -56,8 +47,6 @@ jobs: runs-on: ubuntu-latest needs: mint-token environment: main - # No `id-token` here: compromised dependency code in this job gets at most - # the hour-long installation token, not the ability to mint new ones. permissions: contents: read @@ -78,10 +67,7 @@ jobs: with: token: ${{ steps.app-token.outputs.token }} - # `sccache: false` because this job intentionally holds no `id-token` - # permission, which the sccache credential fetch from Vault relies on. - # Rust itself stays: publishing `@blockprotocol/type-system` builds the - # wasm artifact from the Rust crate via wasm-pack. + # The sccache credential fetch from Vault needs `id-token: write`. - name: Install tools uses: ./.github/actions/install-tools with: From f720252df1b46e7816c30a4528076af4fa05ed42 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 16:44:38 +0000 Subject: [PATCH 3/9] SRE-904: Keep sccache in the release job Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018HPkZcHmt5qmnGHtVB96d7 --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7149f279db..79fd3814196 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,7 @@ jobs: environment: main permissions: contents: read + id-token: write steps: # First step, so the plaintext is masked before anything else runs. @@ -67,12 +68,10 @@ jobs: with: token: ${{ steps.app-token.outputs.token }} - # The sccache credential fetch from Vault needs `id-token: write`. - name: Install tools uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - sccache: false - name: Warm up repository uses: ./.github/actions/warm-up-repo From 3793a0f545e31a710558a7f7e79f441aa96fb52d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 17:04:41 +0000 Subject: [PATCH 4/9] SRE-904: Hand sccache credentials across the job boundary Dedicated encrypt-secret/decrypt-secret/mint-sccache-credentials actions carry the sccache R2 credentials from an OIDC-holding credentials job to the dependency-running jobs in release, lint, and test, so those jobs keep sccache without id-token. Dead id-token grants and unused Vault inputs are dropped from the rust-less setup jobs in lint, test, bench, and codspeed; codspeed scopes its grant to the job CodSpeed's OIDC auth needs it in. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018HPkZcHmt5qmnGHtVB96d7 --- .github/actions/decrypt-secret/action.yml | 36 +++++++++++++ .github/actions/encrypt-secret/action.yml | 27 ++++++++++ .github/actions/install-sccache/action.yml | 41 +++++++++++++-- .github/actions/install-tools/action.yml | 10 +++- .../mint-sccache-credentials/action.yml | 52 +++++++++++++++++++ .github/workflows/bench.yml | 5 +- .github/workflows/codspeed.yml | 9 ++-- .github/workflows/lint.yml | 45 +++++++++++----- .github/workflows/release.yml | 48 ++++++++++------- .github/workflows/test.yml | 48 +++++++++++------ 10 files changed, 259 insertions(+), 62 deletions(-) create mode 100644 .github/actions/decrypt-secret/action.yml create mode 100644 .github/actions/encrypt-secret/action.yml create mode 100644 .github/actions/mint-sccache-credentials/action.yml diff --git a/.github/actions/decrypt-secret/action.yml b/.github/actions/decrypt-secret/action.yml new file mode 100644 index 00000000000..cf6c15664b8 --- /dev/null +++ b/.github/actions/decrypt-secret/action.yml @@ -0,0 +1,36 @@ +name: Decrypt secret +description: Decrypts a ciphertext produced by encrypt-secret, masking every plaintext line before it is output. +inputs: + ciphertext: + description: The encrypted value, base64-encoded. + required: true + encryption_key: + description: The symmetric key decrypting the value. + required: true +outputs: + plaintext: + description: The decrypted value. + value: ${{ steps.decrypt.outputs.plaintext }} + +runs: + using: composite + steps: + - name: Decrypt value + id: decrypt + shell: bash + env: + CIPHERTEXT: ${{ inputs.ciphertext }} + ENC_KEY: ${{ inputs.encryption_key }} + run: | + : "${CIPHERTEXT:?}" "${ENC_KEY:?}" + plaintext=$(printf '%s' "${CIPHERTEXT}" | openssl enc -d -aes-256-cbc -pbkdf2 -pass env:ENC_KEY -base64 -A) + while IFS= read -r line; do + if [[ -n "${line}" ]]; then + echo "::add-mask::${line}" + fi + done <<<"${plaintext}" + { + echo "plaintext<<__PLAINTEXT_EOF__" + printf '%s\n' "${plaintext}" + echo "__PLAINTEXT_EOF__" + } >>"${GITHUB_OUTPUT}" diff --git a/.github/actions/encrypt-secret/action.yml b/.github/actions/encrypt-secret/action.yml new file mode 100644 index 00000000000..9382a392270 --- /dev/null +++ b/.github/actions/encrypt-secret/action.yml @@ -0,0 +1,27 @@ +name: Encrypt secret +description: Encrypts a value with a symmetric key so that only ciphertext crosses a job boundary. +inputs: + value: + description: The plaintext to encrypt. May span multiple lines. + required: true + encryption_key: + description: The symmetric key encrypting the value. + required: true +outputs: + ciphertext: + description: The encrypted value, base64-encoded. + value: ${{ steps.encrypt.outputs.ciphertext }} + +runs: + using: composite + steps: + - name: Encrypt value + id: encrypt + shell: bash + env: + VALUE: ${{ inputs.value }} + ENC_KEY: ${{ inputs.encryption_key }} + run: | + : "${VALUE:?}" "${ENC_KEY:?}" + ciphertext=$(printf '%s' "${VALUE}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass env:ENC_KEY -base64 -A) + echo "ciphertext=${ciphertext}" >>"${GITHUB_OUTPUT}" diff --git a/.github/actions/install-sccache/action.yml b/.github/actions/install-sccache/action.yml index 0ea1aecbfdb..3bdca02d217 100644 --- a/.github/actions/install-sccache/action.yml +++ b/.github/actions/install-sccache/action.yml @@ -3,19 +3,26 @@ description: Setup sccache for Rust project caching inputs: vault_address: description: The URL of the Vault server holding `ci/sccache/r2`. - required: true + default: "" cf_access_client_id: description: Cloudflare Access service-token client ID for that Vault. - required: true + default: "" cf_access_client_secret: description: Cloudflare Access service-token client secret for that Vault. - required: true + default: "" + ciphertext: + description: Encrypted credential bundle from mint-sccache-credentials, used instead of a direct Vault fetch. + default: "" + encryption_key: + description: The symmetric key decrypting `ciphertext`. + default: "" runs: using: composite steps: - name: Retrieve secrets id: secrets + if: ${{ inputs.ciphertext == '' }} uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3.4.0 with: # The endpoint sits behind Cloudflare Access, hence the service-token @@ -32,6 +39,34 @@ runs: ci/data/sccache/r2 access_key_id | SCCACHE_AWS_ACCESS_KEY_ID ; ci/data/sccache/r2 secret_access_key | SCCACHE_AWS_SECRET_ACCESS_KEY ; + - name: Decrypt secrets + id: decrypt + if: ${{ inputs.ciphertext != '' }} + uses: ./.github/actions/decrypt-secret + with: + ciphertext: ${{ inputs.ciphertext }} + encryption_key: ${{ inputs.encryption_key }} + + - name: Export credentials + if: ${{ inputs.ciphertext != '' }} + shell: bash + env: + CREDENTIALS: ${{ steps.decrypt.outputs.plaintext }} + run: | + { + read -r account_id + read -r bucket + read -r access_key_id + read -r secret_access_key + } <<<"${CREDENTIALS}" + : "${account_id:?}" "${bucket:?}" "${access_key_id:?}" "${secret_access_key:?}" + { + echo "SCCACHE_ACCOUNT_ID=${account_id}" + echo "SCCACHE_BUCKET=${bucket}" + echo "SCCACHE_AWS_ACCESS_KEY_ID=${access_key_id}" + echo "SCCACHE_AWS_SECRET_ACCESS_KEY=${secret_access_key}" + } >>"${GITHUB_ENV}" + - name: Setup sccache uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 diff --git a/.github/actions/install-tools/action.yml b/.github/actions/install-tools/action.yml index bdca6a21efa..6b0aa03eb48 100644 --- a/.github/actions/install-tools/action.yml +++ b/.github/actions/install-tools/action.yml @@ -14,6 +14,12 @@ inputs: cf_access_client_secret: description: Cloudflare Access service-token client secret for that Vault. Empty on fork pull requests, which is what skips sccache there. default: "" + sccache_ciphertext: + description: Encrypted sccache credential bundle from mint-sccache-credentials, used instead of a direct Vault fetch. Empty on fork pull requests, which is what skips sccache there. + default: "" + sccache_encryption_key: + description: The symmetric key decrypting `sccache_ciphertext`. + default: "" rust: description: Should Rust be installed? Can either be `"true"` or `true` default: "true" @@ -55,10 +61,12 @@ runs: command: ${{ github.action_path }}/install-rust.sh - name: "Install sccache" - if: ${{ (inputs.rust == true || inputs.rust == 'true') && (inputs.sccache == true || inputs.sccache == 'true') && inputs.cf_access_client_secret != '' }} + if: ${{ (inputs.rust == true || inputs.rust == 'true') && (inputs.sccache == true || inputs.sccache == 'true') && (inputs.cf_access_client_secret != '' || inputs.sccache_ciphertext != '') }} continue-on-error: true uses: ./.github/actions/install-sccache with: vault_address: ${{ inputs.vault_address }} cf_access_client_id: ${{ inputs.cf_access_client_id }} cf_access_client_secret: ${{ inputs.cf_access_client_secret }} + ciphertext: ${{ inputs.sccache_ciphertext }} + encryption_key: ${{ inputs.sccache_encryption_key }} diff --git a/.github/actions/mint-sccache-credentials/action.yml b/.github/actions/mint-sccache-credentials/action.yml new file mode 100644 index 00000000000..d5449793098 --- /dev/null +++ b/.github/actions/mint-sccache-credentials/action.yml @@ -0,0 +1,52 @@ +name: Mint sccache credentials +description: Fetches the sccache R2 credentials from Vault and encrypts them for a job-boundary handoff. +inputs: + vault_address: + description: The URL of the Vault server holding `ci/sccache/r2`. + required: true + cf_access_client_id: + description: Cloudflare Access service-token client ID for that Vault. + required: true + cf_access_client_secret: + description: Cloudflare Access service-token client secret for that Vault. Empty on fork pull requests, which is what skips the fetch there. + default: "" + encryption_key: + description: The symmetric key encrypting the credential bundle. + default: "" +outputs: + ciphertext: + description: The encrypted credential bundle. Empty when the fetch was skipped or failed. + value: ${{ steps.encrypt.outputs.ciphertext }} + +runs: + using: composite + steps: + - name: Retrieve secrets + id: secrets + if: ${{ inputs.cf_access_client_secret != '' }} + continue-on-error: true + uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3.4.0 + with: + url: ${{ inputs.vault_address }} + method: jwt + role: ci-hash-sccache + extraHeaders: | + CF-Access-Client-Id: ${{ inputs.cf_access_client_id }} + CF-Access-Client-Secret: ${{ inputs.cf_access_client_secret }} + secrets: | + ci/data/sccache/r2 account_id | SCCACHE_ACCOUNT_ID ; + ci/data/sccache/r2 bucket | SCCACHE_BUCKET ; + ci/data/sccache/r2 access_key_id | SCCACHE_AWS_ACCESS_KEY_ID ; + ci/data/sccache/r2 secret_access_key | SCCACHE_AWS_SECRET_ACCESS_KEY ; + + - name: Encrypt credentials + id: encrypt + if: ${{ steps.secrets.outcome == 'success' }} + uses: ./.github/actions/encrypt-secret + with: + value: | + ${{ env.SCCACHE_ACCOUNT_ID }} + ${{ env.SCCACHE_BUCKET }} + ${{ env.SCCACHE_AWS_ACCESS_KEY_ID }} + ${{ env.SCCACHE_AWS_SECRET_ACCESS_KEY }} + encryption_key: ${{ inputs.encryption_key }} diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 1b715a14d18..f4a15385211 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -37,7 +37,7 @@ jobs: if: needs.optimize-ci.outputs.skip == 'false' runs-on: ubuntu-24.04 permissions: - id-token: write + contents: read outputs: unit: ${{ steps.packages.outputs.unit }} integration: ${{ steps.packages.outputs.integration }} @@ -51,9 +51,6 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} rust: false - name: Determine changed packages diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 934b853f165..5f4d9da49e2 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -11,13 +11,12 @@ on: permissions: contents: read - id-token: write jobs: setup: runs-on: ubuntu-24.04 permissions: - id-token: write + contents: read outputs: packages: ${{ steps.packages.outputs.packages }} steps: @@ -30,9 +29,6 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} rust: false - name: Determine changed packages that have codspeed @@ -48,6 +44,9 @@ jobs: name: Run benchmarks needs: [setup] runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write strategy: matrix: ${{ fromJSON(needs.setup.outputs.packages) }} fail-fast: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 77666f26c79..40fb8a016d2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: setup: runs-on: ubuntu-24.04 permissions: - id-token: write + contents: read outputs: packages: ${{ steps.packages.outputs.packages }} steps: @@ -33,9 +33,6 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} rust: false - name: Determine changed packages @@ -47,13 +44,32 @@ jobs: echo "packages=$PACKAGES" | tee -a $GITHUB_OUTPUT + sccache-credentials: + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + outputs: + ciphertext: ${{ steps.mint.outputs.ciphertext }} + steps: + - name: Checkout source code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Mint sccache credentials + id: mint + uses: ./.github/actions/mint-sccache-credentials + with: + vault_address: ${{ vars.VAULT_STAGE_ADDR }} + cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} + cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} + package: name: Package permissions: - id-token: write contents: read security-events: write - needs: [setup] + needs: [setup, sccache-credentials] strategy: matrix: ${{ fromJSON(needs.setup.outputs.packages) }} fail-fast: false @@ -72,9 +88,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + sccache_ciphertext: ${{ needs.sccache-credentials.outputs.ciphertext }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository uses: ./.github/actions/prune-repository @@ -202,10 +217,10 @@ jobs: global: name: Global permissions: - id-token: write contents: read checks: write pull-requests: write + needs: [sccache-credentials] runs-on: ubuntu-24.04 steps: - name: Checkout repository @@ -218,9 +233,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + sccache_ciphertext: ${{ needs.sccache-credentials.outputs.ciphertext }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Warm up repository uses: ./.github/actions/warm-up-repo @@ -405,13 +419,16 @@ jobs: passed: name: Linting passed - needs: [setup, package, global] + needs: [setup, sccache-credentials, package, global] if: always() runs-on: ubuntu-latest steps: - name: Check setup script run: | [[ ${{ needs.setup.result }} = success ]] + - name: Check sccache credentials + run: | + [[ ${{ needs.sccache-credentials.result }} = success ]] - name: Check package results run: | [[ ${{ needs.package.result }} =~ success|skipped ]] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 79fd3814196..b54a098566f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,9 +17,12 @@ jobs: contents: read id-token: write outputs: - token-ciphertext: ${{ steps.encrypt.outputs.token-ciphertext }} + token-ciphertext: ${{ steps.encrypt.outputs.ciphertext }} + sccache-ciphertext: ${{ steps.mint-sccache.outputs.ciphertext }} steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Get token id: app-token uses: hashintel/.github/.github/actions/github-app-token@b7a5d7f651c1d6a862d5dd97aa6164125bedb6c5 # main @@ -35,13 +38,19 @@ jobs: # GitHub drops masked values from job outputs, so only ciphertext can cross. - name: Encrypt token id: encrypt - env: - TOKEN: ${{ steps.app-token.outputs.token }} - ENC_KEY: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} - run: | - : "${TOKEN:?}" "${ENC_KEY:?}" - ciphertext=$(printf '%s' "${TOKEN}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass env:ENC_KEY -base64 -A) - echo "token-ciphertext=${ciphertext}" >>"${GITHUB_OUTPUT}" + uses: ./.github/actions/encrypt-secret + with: + value: ${{ steps.app-token.outputs.token }} + encryption_key: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} + + - name: Mint sccache credentials + id: mint-sccache + uses: ./.github/actions/mint-sccache-credentials + with: + vault_address: ${{ vars.VAULT_STAGE_ADDR }} + cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} + cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} release: runs-on: ubuntu-latest @@ -49,29 +58,28 @@ jobs: environment: main permissions: contents: read - id-token: write steps: - # First step, so the plaintext is masked before anything else runs. + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # Directly after checkout, so the plaintext is masked before anything else runs. - name: Decrypt token id: app-token - env: - TOKEN_CIPHERTEXT: ${{ needs.mint-token.outputs.token-ciphertext }} - ENC_KEY: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} - run: | - : "${TOKEN_CIPHERTEXT:?}" "${ENC_KEY:?}" - token=$(printf '%s' "${TOKEN_CIPHERTEXT}" | openssl enc -d -aes-256-cbc -pbkdf2 -pass env:ENC_KEY -base64 -A) - echo "::add-mask::${token}" - echo "token=${token}" >>"${GITHUB_OUTPUT}" + uses: ./.github/actions/decrypt-secret + with: + ciphertext: ${{ needs.mint-token.outputs.token-ciphertext }} + encryption_key: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - token: ${{ steps.app-token.outputs.token }} + token: ${{ steps.app-token.outputs.plaintext }} - name: Install tools uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} + sccache_ciphertext: ${{ needs.mint-token.outputs.sccache-ciphertext }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Warm up repository uses: ./.github/actions/warm-up-repo @@ -83,7 +91,7 @@ jobs: version: yarn changeset:version commitMode: github-api env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.plaintext }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_LOGLEVEL: verbose TURBO_LOG_LEVEL: debug diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc14d27faca..41c2732e702 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: setup: runs-on: ubuntu-24.04 permissions: - id-token: write + contents: read outputs: unit-tests: ${{ steps.packages.outputs.unit-tests }} integration-tests: ${{ steps.packages.outputs.integration-tests }} @@ -49,9 +49,6 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} rust: false - name: Determine changed packages @@ -100,16 +97,36 @@ jobs: echo "integration-tests=$INTEGRATION_TEST_PACKAGES" | tee -a $GITHUB_OUTPUT echo "publish-rust=$PUBLISH_PACKAGES" | tee -a $GITHUB_OUTPUT + sccache-credentials: + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + outputs: + ciphertext: ${{ steps.mint.outputs.ciphertext }} + steps: + - name: Checkout source code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Mint sccache credentials + id: mint + uses: ./.github/actions/mint-sccache-credentials + with: + vault_address: ${{ vars.VAULT_STAGE_ADDR }} + cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} + cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} + unit-tests: name: Unit - needs: [setup] + needs: [setup, sccache-credentials] strategy: matrix: ${{ fromJSON(needs.setup.outputs.unit-tests) }} fail-fast: false if: needs.setup.outputs.unit-tests != '{"name":[],"include":[]}' runs-on: ubuntu-24.04 permissions: - id-token: write + contents: read steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -123,9 +140,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + sccache_ciphertext: ${{ needs.sccache-credentials.outputs.ciphertext }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository uses: ./.github/actions/prune-repository @@ -203,14 +219,14 @@ jobs: integration-tests: name: Integration - needs: [setup, optimize-ci] + needs: [setup, sccache-credentials, optimize-ci] strategy: matrix: ${{ fromJSON(needs.setup.outputs.integration-tests) }} fail-fast: false if: needs.setup.outputs.integration-tests != '{"name":[],"include":[]}' && needs.optimize-ci.outputs.skip == 'false' runs-on: ubuntu-24.04 permissions: - id-token: write + contents: read steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -222,9 +238,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + sccache_ciphertext: ${{ needs.sccache-credentials.outputs.ciphertext }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository uses: ./.github/actions/prune-repository @@ -378,13 +393,16 @@ jobs: passed: name: Tests passed - needs: [setup, unit-tests, integration-tests, publish-rust, optimize-ci] + needs: [setup, sccache-credentials, unit-tests, integration-tests, publish-rust, optimize-ci] if: always() runs-on: ubuntu-latest steps: - name: Check setup script run: | [[ ${{ needs.setup.result }} = success ]] + - name: Check sccache credentials + run: | + [[ ${{ needs.sccache-credentials.result }} = success ]] - name: Check unit tests run: | [[ ${{ needs.unit-tests.result }} =~ success|skipped ]] From ea02e865b4e1ff2758adc487b6080f01e0d9448c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 17:21:31 +0000 Subject: [PATCH 5/9] SRE-904: Name the handoff by content and bundle credentials as JSON The sccache bundle crosses as JSON with named keys, built with jq and parsed by key on the consuming side, instead of a positional newline string. Handoff inputs and outputs say what the ciphertext contains (encrypted-token, encrypted-sccache-credentials); only the generic encrypt-secret/decrypt-secret actions keep content-agnostic names. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018HPkZcHmt5qmnGHtVB96d7 --- .github/actions/install-sccache/action.yml | 26 +++++++++---------- .github/actions/install-tools/action.yml | 8 +++--- .../mint-sccache-credentials/action.yml | 23 +++++++++++----- .github/workflows/lint.yml | 6 ++--- .github/workflows/release.yml | 8 +++--- .github/workflows/test.yml | 6 ++--- 6 files changed, 43 insertions(+), 34 deletions(-) diff --git a/.github/actions/install-sccache/action.yml b/.github/actions/install-sccache/action.yml index 3bdca02d217..d83741ebed5 100644 --- a/.github/actions/install-sccache/action.yml +++ b/.github/actions/install-sccache/action.yml @@ -10,11 +10,11 @@ inputs: cf_access_client_secret: description: Cloudflare Access service-token client secret for that Vault. default: "" - ciphertext: + encrypted_credentials: description: Encrypted credential bundle from mint-sccache-credentials, used instead of a direct Vault fetch. default: "" encryption_key: - description: The symmetric key decrypting `ciphertext`. + description: The symmetric key decrypting `encrypted_credentials`. default: "" runs: @@ -22,7 +22,7 @@ runs: steps: - name: Retrieve secrets id: secrets - if: ${{ inputs.ciphertext == '' }} + if: ${{ inputs.encrypted_credentials == '' }} uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3.4.0 with: # The endpoint sits behind Cloudflare Access, hence the service-token @@ -41,25 +41,25 @@ runs: - name: Decrypt secrets id: decrypt - if: ${{ inputs.ciphertext != '' }} + if: ${{ inputs.encrypted_credentials != '' }} uses: ./.github/actions/decrypt-secret with: - ciphertext: ${{ inputs.ciphertext }} + ciphertext: ${{ inputs.encrypted_credentials }} encryption_key: ${{ inputs.encryption_key }} - name: Export credentials - if: ${{ inputs.ciphertext != '' }} + if: ${{ inputs.encrypted_credentials != '' }} shell: bash env: CREDENTIALS: ${{ steps.decrypt.outputs.plaintext }} run: | - { - read -r account_id - read -r bucket - read -r access_key_id - read -r secret_access_key - } <<<"${CREDENTIALS}" - : "${account_id:?}" "${bucket:?}" "${access_key_id:?}" "${secret_access_key:?}" + account_id=$(jq --raw-output --exit-status '.account_id' <<<"${CREDENTIALS}") + bucket=$(jq --raw-output --exit-status '.bucket' <<<"${CREDENTIALS}") + access_key_id=$(jq --raw-output --exit-status '.access_key_id' <<<"${CREDENTIALS}") + secret_access_key=$(jq --raw-output --exit-status '.secret_access_key' <<<"${CREDENTIALS}") + for value in "${account_id}" "${bucket}" "${access_key_id}" "${secret_access_key}"; do + echo "::add-mask::${value}" + done { echo "SCCACHE_ACCOUNT_ID=${account_id}" echo "SCCACHE_BUCKET=${bucket}" diff --git a/.github/actions/install-tools/action.yml b/.github/actions/install-tools/action.yml index 6b0aa03eb48..36a4fc128ab 100644 --- a/.github/actions/install-tools/action.yml +++ b/.github/actions/install-tools/action.yml @@ -14,11 +14,11 @@ inputs: cf_access_client_secret: description: Cloudflare Access service-token client secret for that Vault. Empty on fork pull requests, which is what skips sccache there. default: "" - sccache_ciphertext: + encrypted_sccache_credentials: description: Encrypted sccache credential bundle from mint-sccache-credentials, used instead of a direct Vault fetch. Empty on fork pull requests, which is what skips sccache there. default: "" sccache_encryption_key: - description: The symmetric key decrypting `sccache_ciphertext`. + description: The symmetric key decrypting `encrypted_sccache_credentials`. default: "" rust: description: Should Rust be installed? Can either be `"true"` or `true` @@ -61,12 +61,12 @@ runs: command: ${{ github.action_path }}/install-rust.sh - name: "Install sccache" - if: ${{ (inputs.rust == true || inputs.rust == 'true') && (inputs.sccache == true || inputs.sccache == 'true') && (inputs.cf_access_client_secret != '' || inputs.sccache_ciphertext != '') }} + if: ${{ (inputs.rust == true || inputs.rust == 'true') && (inputs.sccache == true || inputs.sccache == 'true') && (inputs.cf_access_client_secret != '' || inputs.encrypted_sccache_credentials != '') }} continue-on-error: true uses: ./.github/actions/install-sccache with: vault_address: ${{ inputs.vault_address }} cf_access_client_id: ${{ inputs.cf_access_client_id }} cf_access_client_secret: ${{ inputs.cf_access_client_secret }} - ciphertext: ${{ inputs.sccache_ciphertext }} + encrypted_credentials: ${{ inputs.encrypted_sccache_credentials }} encryption_key: ${{ inputs.sccache_encryption_key }} diff --git a/.github/actions/mint-sccache-credentials/action.yml b/.github/actions/mint-sccache-credentials/action.yml index d5449793098..fd9ae7d522b 100644 --- a/.github/actions/mint-sccache-credentials/action.yml +++ b/.github/actions/mint-sccache-credentials/action.yml @@ -14,8 +14,8 @@ inputs: description: The symmetric key encrypting the credential bundle. default: "" outputs: - ciphertext: - description: The encrypted credential bundle. Empty when the fetch was skipped or failed. + encrypted_credentials: + description: The encrypted credential bundle, JSON with named keys. Empty when the fetch was skipped or failed. value: ${{ steps.encrypt.outputs.ciphertext }} runs: @@ -39,14 +39,23 @@ runs: ci/data/sccache/r2 access_key_id | SCCACHE_AWS_ACCESS_KEY_ID ; ci/data/sccache/r2 secret_access_key | SCCACHE_AWS_SECRET_ACCESS_KEY ; + - name: Bundle credentials + id: bundle + if: ${{ steps.secrets.outcome == 'success' }} + shell: bash + run: | + credentials=$(jq --null-input --compact-output \ + --arg account_id "${SCCACHE_ACCOUNT_ID:?}" \ + --arg bucket "${SCCACHE_BUCKET:?}" \ + --arg access_key_id "${SCCACHE_AWS_ACCESS_KEY_ID:?}" \ + --arg secret_access_key "${SCCACHE_AWS_SECRET_ACCESS_KEY:?}" \ + '$ARGS.named') + echo "credentials=${credentials}" >>"${GITHUB_OUTPUT}" + - name: Encrypt credentials id: encrypt if: ${{ steps.secrets.outcome == 'success' }} uses: ./.github/actions/encrypt-secret with: - value: | - ${{ env.SCCACHE_ACCOUNT_ID }} - ${{ env.SCCACHE_BUCKET }} - ${{ env.SCCACHE_AWS_ACCESS_KEY_ID }} - ${{ env.SCCACHE_AWS_SECRET_ACCESS_KEY }} + value: ${{ steps.bundle.outputs.credentials }} encryption_key: ${{ inputs.encryption_key }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 40fb8a016d2..b9938a56863 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -50,7 +50,7 @@ jobs: contents: read id-token: write outputs: - ciphertext: ${{ steps.mint.outputs.ciphertext }} + encrypted-credentials: ${{ steps.mint.outputs.encrypted_credentials }} steps: - name: Checkout source code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -88,7 +88,7 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - sccache_ciphertext: ${{ needs.sccache-credentials.outputs.ciphertext }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository @@ -233,7 +233,7 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - sccache_ciphertext: ${{ needs.sccache-credentials.outputs.ciphertext }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Warm up repository diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b54a098566f..61c11e13bb3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,8 +17,8 @@ jobs: contents: read id-token: write outputs: - token-ciphertext: ${{ steps.encrypt.outputs.ciphertext }} - sccache-ciphertext: ${{ steps.mint-sccache.outputs.ciphertext }} + encrypted-token: ${{ steps.encrypt.outputs.ciphertext }} + encrypted-sccache-credentials: ${{ steps.mint-sccache.outputs.encrypted_credentials }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -67,7 +67,7 @@ jobs: id: app-token uses: ./.github/actions/decrypt-secret with: - ciphertext: ${{ needs.mint-token.outputs.token-ciphertext }} + ciphertext: ${{ needs.mint-token.outputs.encrypted-token }} encryption_key: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -78,7 +78,7 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - sccache_ciphertext: ${{ needs.mint-token.outputs.sccache-ciphertext }} + encrypted_sccache_credentials: ${{ needs.mint-token.outputs.encrypted-sccache-credentials }} sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Warm up repository diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 41c2732e702..8a94da29ede 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -103,7 +103,7 @@ jobs: contents: read id-token: write outputs: - ciphertext: ${{ steps.mint.outputs.ciphertext }} + encrypted-credentials: ${{ steps.mint.outputs.encrypted_credentials }} steps: - name: Checkout source code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -140,7 +140,7 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - sccache_ciphertext: ${{ needs.sccache-credentials.outputs.ciphertext }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository @@ -238,7 +238,7 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - sccache_ciphertext: ${{ needs.sccache-credentials.outputs.ciphertext }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository From 466cde0d5ce8bd4c0e5f7a5c3cc3d480390bdfff Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 17:38:10 +0000 Subject: [PATCH 6/9] SRE-904: Make the encrypted handoff the only sccache route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install-sccache no longer talks to Vault: it requires the encrypted credential bundle, and install-tools drops the Vault/Cloudflare inputs. Every sccache consumer — bench, codspeed, canary-release, and deploy's sourcemaps job — mints the bundle in its own sccache-credentials job, moving the fetch out of the dependency-running jobs; their remaining id-token grants serve only their other OIDC uses (legacy-Vault AWS upload, CodSpeed auth, Sentry). Fork pull requests skip the fetch and run uncached, as before. Also wraps the overlong needs list in test.yml that failed lint:format. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018HPkZcHmt5qmnGHtVB96d7 --- .github/actions/install-sccache/action.yml | 38 ++------------- .github/actions/install-tools/action.yml | 16 +------ .github/workflows/bench.yml | 56 ++++++++++++++++------ .github/workflows/canary-release.yml | 26 ++++++++-- .github/workflows/codspeed.yml | 29 +++++++++-- .github/workflows/deploy.yml | 36 ++++++++++---- .github/workflows/test.yml | 10 +++- 7 files changed, 130 insertions(+), 81 deletions(-) diff --git a/.github/actions/install-sccache/action.yml b/.github/actions/install-sccache/action.yml index d83741ebed5..645a67a7ae5 100644 --- a/.github/actions/install-sccache/action.yml +++ b/.github/actions/install-sccache/action.yml @@ -1,54 +1,24 @@ name: Install sccache description: Setup sccache for Rust project caching inputs: - vault_address: - description: The URL of the Vault server holding `ci/sccache/r2`. - default: "" - cf_access_client_id: - description: Cloudflare Access service-token client ID for that Vault. - default: "" - cf_access_client_secret: - description: Cloudflare Access service-token client secret for that Vault. - default: "" encrypted_credentials: - description: Encrypted credential bundle from mint-sccache-credentials, used instead of a direct Vault fetch. - default: "" + description: Encrypted credential bundle from mint-sccache-credentials. + required: true encryption_key: description: The symmetric key decrypting `encrypted_credentials`. - default: "" + required: true runs: using: composite steps: - - name: Retrieve secrets - id: secrets - if: ${{ inputs.encrypted_credentials == '' }} - uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3.4.0 - with: - # The endpoint sits behind Cloudflare Access, hence the service-token - # headers. Vault's own ACL still decides what the role may read. - url: ${{ inputs.vault_address }} - method: jwt - role: ci-hash-sccache - extraHeaders: | - CF-Access-Client-Id: ${{ inputs.cf_access_client_id }} - CF-Access-Client-Secret: ${{ inputs.cf_access_client_secret }} - secrets: | - ci/data/sccache/r2 account_id | SCCACHE_ACCOUNT_ID ; - ci/data/sccache/r2 bucket | SCCACHE_BUCKET ; - ci/data/sccache/r2 access_key_id | SCCACHE_AWS_ACCESS_KEY_ID ; - ci/data/sccache/r2 secret_access_key | SCCACHE_AWS_SECRET_ACCESS_KEY ; - - - name: Decrypt secrets + - name: Decrypt credentials id: decrypt - if: ${{ inputs.encrypted_credentials != '' }} uses: ./.github/actions/decrypt-secret with: ciphertext: ${{ inputs.encrypted_credentials }} encryption_key: ${{ inputs.encryption_key }} - name: Export credentials - if: ${{ inputs.encrypted_credentials != '' }} shell: bash env: CREDENTIALS: ${{ steps.decrypt.outputs.plaintext }} diff --git a/.github/actions/install-tools/action.yml b/.github/actions/install-tools/action.yml index 36a4fc128ab..a035101c97e 100644 --- a/.github/actions/install-tools/action.yml +++ b/.github/actions/install-tools/action.yml @@ -5,17 +5,8 @@ inputs: token: description: GitHub token for authentication required: true - vault_address: - description: The URL of the Vault server holding the sccache credentials. - default: "" - cf_access_client_id: - description: Cloudflare Access service-token client ID for that Vault. - default: "" - cf_access_client_secret: - description: Cloudflare Access service-token client secret for that Vault. Empty on fork pull requests, which is what skips sccache there. - default: "" encrypted_sccache_credentials: - description: Encrypted sccache credential bundle from mint-sccache-credentials, used instead of a direct Vault fetch. Empty on fork pull requests, which is what skips sccache there. + description: Encrypted sccache credential bundle from mint-sccache-credentials. Empty on fork pull requests, which is what skips sccache there. default: "" sccache_encryption_key: description: The symmetric key decrypting `encrypted_sccache_credentials`. @@ -61,12 +52,9 @@ runs: command: ${{ github.action_path }}/install-rust.sh - name: "Install sccache" - if: ${{ (inputs.rust == true || inputs.rust == 'true') && (inputs.sccache == true || inputs.sccache == 'true') && (inputs.cf_access_client_secret != '' || inputs.encrypted_sccache_credentials != '') }} + if: ${{ (inputs.rust == true || inputs.rust == 'true') && (inputs.sccache == true || inputs.sccache == 'true') && inputs.encrypted_sccache_credentials != '' }} continue-on-error: true uses: ./.github/actions/install-sccache with: - vault_address: ${{ inputs.vault_address }} - cf_access_client_id: ${{ inputs.cf_access_client_id }} - cf_access_client_secret: ${{ inputs.cf_access_client_secret }} encrypted_credentials: ${{ inputs.encrypted_sccache_credentials }} encryption_key: ${{ inputs.sccache_encryption_key }} diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index f4a15385211..cba8f6830b4 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -67,9 +67,31 @@ jobs: echo "unit=$UNIT_BENCH_PACKAGES" | tee -a $GITHUB_OUTPUT echo "integration=$INTEGRATION_BENCH_PACKAGES" | tee -a $GITHUB_OUTPUT + sccache-credentials: + needs: [optimize-ci] + if: needs.optimize-ci.outputs.skip == 'false' + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + outputs: + encrypted-credentials: ${{ steps.mint.outputs.encrypted_credentials }} + steps: + - name: Checkout source code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Mint sccache credentials + id: mint + uses: ./.github/actions/mint-sccache-credentials + with: + vault_address: ${{ vars.VAULT_STAGE_ADDR }} + cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} + cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} + unit-benches: name: Unit - needs: [setup] + needs: [setup, sccache-credentials] permissions: id-token: write contents: read @@ -94,9 +116,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository if: github.event_name == 'pull_request' @@ -135,9 +156,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository uses: ./.github/actions/prune-repository @@ -189,7 +209,7 @@ jobs: integration-benches: name: Integration - needs: [setup] + needs: [setup, sccache-credentials] permissions: id-token: write contents: read @@ -214,9 +234,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository if: github.event_name == 'pull_request' @@ -314,9 +333,6 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} sccache: false # sccache is already running in the background - name: Prune repository @@ -422,7 +438,14 @@ jobs: passed: name: Benches passed - needs: [setup, unit-benches, integration-benches, optimize-ci] + needs: + [ + setup, + sccache-credentials, + unit-benches, + integration-benches, + optimize-ci, + ] if: always() && needs.optimize-ci.outputs.skip == 'false' runs-on: ubuntu-latest permissions: @@ -431,6 +454,9 @@ jobs: - name: Check setup script run: | [[ ${{ needs.setup.result }} = success ]] + - name: Check sccache credentials + run: | + [[ ${{ needs.sccache-credentials.result }} = success ]] - name: Check unit benches run: | [[ ${{ needs.unit-benches.result }} =~ success|skipped ]] diff --git a/.github/workflows/canary-release.yml b/.github/workflows/canary-release.yml index e02b8be0b51..9e429ae1e42 100644 --- a/.github/workflows/canary-release.yml +++ b/.github/workflows/canary-release.yml @@ -4,11 +4,30 @@ on: workflow_dispatch permissions: contents: write pull-requests: read # `@changesets/changelog-github` - id-token: write # Vault OIDC jobs: + sccache-credentials: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # Vault OIDC + outputs: + encrypted-credentials: ${{ steps.mint.outputs.encrypted_credentials }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Mint sccache credentials + id: mint + uses: ./.github/actions/mint-sccache-credentials + with: + vault_address: ${{ vars.VAULT_STAGE_ADDR }} + cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} + cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} + release: runs-on: ubuntu-latest + needs: [sccache-credentials] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -17,9 +36,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Warm up repository uses: ./.github/actions/warm-up-repo diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 5f4d9da49e2..7244aa2ff5a 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -40,13 +40,33 @@ jobs: echo "packages=$PACKAGES" | tee -a $GITHUB_OUTPUT + sccache-credentials: + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + outputs: + encrypted-credentials: ${{ steps.mint.outputs.encrypted_credentials }} + steps: + - name: Checkout source code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Mint sccache credentials + id: mint + uses: ./.github/actions/mint-sccache-credentials + with: + vault_address: ${{ vars.VAULT_STAGE_ADDR }} + cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} + cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} + benchmarks: name: Run benchmarks - needs: [setup] + needs: [setup, sccache-credentials] runs-on: ubuntu-24.04 permissions: contents: read - id-token: write + id-token: write # for OpenID Connect authentication with CodSpeed strategy: matrix: ${{ fromJSON(needs.setup.outputs.packages) }} fail-fast: false @@ -64,9 +84,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository uses: ./.github/actions/prune-repository diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d0caa56cce0..a5299c87d80 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -55,9 +55,6 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} rust: false # Service catalog: the single source of truth for build/manifest/deploy. @@ -231,9 +228,29 @@ jobs: echo "backend-deploys=$DEPLOY_MATRIX" | tee -a $GITHUB_OUTPUT echo "backend-staging=$STAGING_MATRIX" | tee -a $GITHUB_OUTPUT + sccache-credentials: + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + outputs: + encrypted-credentials: ${{ steps.mint.outputs.encrypted_credentials }} + steps: + - name: Checkout source code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Mint sccache credentials + id: mint + uses: ./.github/actions/mint-sccache-credentials + with: + vault_address: ${{ vars.VAULT_STAGE_ADDR }} + cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} + cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} + sourcemaps: name: Sourcemaps - needs: [setup] + needs: [setup, sccache-credentials] strategy: matrix: ${{ fromJSON(needs.setup.outputs.sourcemaps) }} fail-fast: false @@ -279,9 +296,8 @@ jobs: uses: ./.github/actions/install-tools with: token: ${{ secrets.GITHUB_TOKEN }} - vault_address: ${{ vars.VAULT_STAGE_ADDR }} - cf_access_client_id: ${{ vars.CF_ACCESS_STAGE_CLIENT_ID }} - cf_access_client_secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }} + encrypted_sccache_credentials: ${{ needs.sccache-credentials.outputs.encrypted-credentials }} + sccache_encryption_key: ${{ secrets.SCCACHE_ENC_KEY }} - name: Prune repository uses: ./.github/actions/prune-repository @@ -575,13 +591,17 @@ jobs: # `needs: deploy` notifier would miss, since a skipped job is not a failure). passed: name: Deployments passed - needs: [setup, sourcemaps, build, stage, manifest, deploy] + needs: + [setup, sccache-credentials, sourcemaps, build, stage, manifest, deploy] if: always() runs-on: ubuntu-latest steps: - name: Check setup run: | [[ ${{ needs.setup.result }} = success ]] + - name: Check sccache credentials + run: | + [[ ${{ needs.sccache-credentials.result }} = success ]] - name: Check sourcemaps run: | [[ ${{ needs.sourcemaps.result }} =~ success|skipped ]] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a94da29ede..467e2aeae35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -393,7 +393,15 @@ jobs: passed: name: Tests passed - needs: [setup, sccache-credentials, unit-tests, integration-tests, publish-rust, optimize-ci] + needs: + [ + setup, + sccache-credentials, + unit-tests, + integration-tests, + publish-rust, + optimize-ci, + ] if: always() runs-on: ubuntu-latest steps: From 3f45a447f02ded534ef666df1729e7d07de467bc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 17:49:32 +0000 Subject: [PATCH 7/9] SRE-904: Say what the encrypted values contain encrypt-secret and decrypt-secret speak in value/encrypted_value pairs, and the release handoff names its output encrypted-github-token. --- .github/actions/decrypt-secret/action.yml | 20 +++++++++---------- .github/actions/encrypt-secret/action.yml | 8 ++++---- .github/actions/install-sccache/action.yml | 4 ++-- .../mint-sccache-credentials/action.yml | 2 +- .github/workflows/release.yml | 8 ++++---- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/actions/decrypt-secret/action.yml b/.github/actions/decrypt-secret/action.yml index cf6c15664b8..72b48cc9034 100644 --- a/.github/actions/decrypt-secret/action.yml +++ b/.github/actions/decrypt-secret/action.yml @@ -1,16 +1,16 @@ name: Decrypt secret description: Decrypts a ciphertext produced by encrypt-secret, masking every plaintext line before it is output. inputs: - ciphertext: + encrypted_value: description: The encrypted value, base64-encoded. required: true encryption_key: description: The symmetric key decrypting the value. required: true outputs: - plaintext: + value: description: The decrypted value. - value: ${{ steps.decrypt.outputs.plaintext }} + value: ${{ steps.decrypt.outputs.value }} runs: using: composite @@ -19,18 +19,18 @@ runs: id: decrypt shell: bash env: - CIPHERTEXT: ${{ inputs.ciphertext }} + ENCRYPTED_VALUE: ${{ inputs.encrypted_value }} ENC_KEY: ${{ inputs.encryption_key }} run: | - : "${CIPHERTEXT:?}" "${ENC_KEY:?}" - plaintext=$(printf '%s' "${CIPHERTEXT}" | openssl enc -d -aes-256-cbc -pbkdf2 -pass env:ENC_KEY -base64 -A) + : "${ENCRYPTED_VALUE:?}" "${ENC_KEY:?}" + value=$(printf '%s' "${ENCRYPTED_VALUE}" | openssl enc -d -aes-256-cbc -pbkdf2 -pass env:ENC_KEY -base64 -A) while IFS= read -r line; do if [[ -n "${line}" ]]; then echo "::add-mask::${line}" fi - done <<<"${plaintext}" + done <<<"${value}" { - echo "plaintext<<__PLAINTEXT_EOF__" - printf '%s\n' "${plaintext}" - echo "__PLAINTEXT_EOF__" + echo "value<<__VALUE_EOF__" + printf '%s\n' "${value}" + echo "__VALUE_EOF__" } >>"${GITHUB_OUTPUT}" diff --git a/.github/actions/encrypt-secret/action.yml b/.github/actions/encrypt-secret/action.yml index 9382a392270..48a83f0047b 100644 --- a/.github/actions/encrypt-secret/action.yml +++ b/.github/actions/encrypt-secret/action.yml @@ -8,9 +8,9 @@ inputs: description: The symmetric key encrypting the value. required: true outputs: - ciphertext: + encrypted_value: description: The encrypted value, base64-encoded. - value: ${{ steps.encrypt.outputs.ciphertext }} + value: ${{ steps.encrypt.outputs.encrypted_value }} runs: using: composite @@ -23,5 +23,5 @@ runs: ENC_KEY: ${{ inputs.encryption_key }} run: | : "${VALUE:?}" "${ENC_KEY:?}" - ciphertext=$(printf '%s' "${VALUE}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass env:ENC_KEY -base64 -A) - echo "ciphertext=${ciphertext}" >>"${GITHUB_OUTPUT}" + encrypted_value=$(printf '%s' "${VALUE}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass env:ENC_KEY -base64 -A) + echo "encrypted_value=${encrypted_value}" >>"${GITHUB_OUTPUT}" diff --git a/.github/actions/install-sccache/action.yml b/.github/actions/install-sccache/action.yml index 645a67a7ae5..465676619f2 100644 --- a/.github/actions/install-sccache/action.yml +++ b/.github/actions/install-sccache/action.yml @@ -15,13 +15,13 @@ runs: id: decrypt uses: ./.github/actions/decrypt-secret with: - ciphertext: ${{ inputs.encrypted_credentials }} + encrypted_value: ${{ inputs.encrypted_credentials }} encryption_key: ${{ inputs.encryption_key }} - name: Export credentials shell: bash env: - CREDENTIALS: ${{ steps.decrypt.outputs.plaintext }} + CREDENTIALS: ${{ steps.decrypt.outputs.value }} run: | account_id=$(jq --raw-output --exit-status '.account_id' <<<"${CREDENTIALS}") bucket=$(jq --raw-output --exit-status '.bucket' <<<"${CREDENTIALS}") diff --git a/.github/actions/mint-sccache-credentials/action.yml b/.github/actions/mint-sccache-credentials/action.yml index fd9ae7d522b..c91263784d7 100644 --- a/.github/actions/mint-sccache-credentials/action.yml +++ b/.github/actions/mint-sccache-credentials/action.yml @@ -16,7 +16,7 @@ inputs: outputs: encrypted_credentials: description: The encrypted credential bundle, JSON with named keys. Empty when the fetch was skipped or failed. - value: ${{ steps.encrypt.outputs.ciphertext }} + value: ${{ steps.encrypt.outputs.encrypted_value }} runs: using: composite diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61c11e13bb3..7c61271bc5f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: contents: read id-token: write outputs: - encrypted-token: ${{ steps.encrypt.outputs.ciphertext }} + encrypted-github-token: ${{ steps.encrypt.outputs.encrypted_value }} encrypted-sccache-credentials: ${{ steps.mint-sccache.outputs.encrypted_credentials }} steps: @@ -67,12 +67,12 @@ jobs: id: app-token uses: ./.github/actions/decrypt-secret with: - ciphertext: ${{ needs.mint-token.outputs.encrypted-token }} + encrypted_value: ${{ needs.mint-token.outputs.encrypted-github-token }} encryption_key: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - token: ${{ steps.app-token.outputs.plaintext }} + token: ${{ steps.app-token.outputs.value }} - name: Install tools uses: ./.github/actions/install-tools @@ -91,7 +91,7 @@ jobs: version: yarn changeset:version commitMode: github-api env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.plaintext }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.value }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_LOGLEVEL: verbose TURBO_LOG_LEVEL: debug From e05bfd2ae2494f840918527ddd919e17623aed78 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 14:55:05 +0000 Subject: [PATCH 8/9] SRE-904: Use the shared secret-handoff actions from hashintel/.github MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit encrypt-secret and decrypt-secret moved upstream (hashintel/.github#103, same interface); the local copies are gone and all references pin the merge commit. mint-sccache-credentials stays local — it is bound to this repository's Vault role and R2 credential layout. --- .github/actions/decrypt-secret/action.yml | 36 ------------------- .github/actions/encrypt-secret/action.yml | 27 -------------- .github/actions/install-sccache/action.yml | 2 +- .../mint-sccache-credentials/action.yml | 2 +- .github/workflows/release.yml | 4 +-- 5 files changed, 4 insertions(+), 67 deletions(-) delete mode 100644 .github/actions/decrypt-secret/action.yml delete mode 100644 .github/actions/encrypt-secret/action.yml diff --git a/.github/actions/decrypt-secret/action.yml b/.github/actions/decrypt-secret/action.yml deleted file mode 100644 index 72b48cc9034..00000000000 --- a/.github/actions/decrypt-secret/action.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Decrypt secret -description: Decrypts a ciphertext produced by encrypt-secret, masking every plaintext line before it is output. -inputs: - encrypted_value: - description: The encrypted value, base64-encoded. - required: true - encryption_key: - description: The symmetric key decrypting the value. - required: true -outputs: - value: - description: The decrypted value. - value: ${{ steps.decrypt.outputs.value }} - -runs: - using: composite - steps: - - name: Decrypt value - id: decrypt - shell: bash - env: - ENCRYPTED_VALUE: ${{ inputs.encrypted_value }} - ENC_KEY: ${{ inputs.encryption_key }} - run: | - : "${ENCRYPTED_VALUE:?}" "${ENC_KEY:?}" - value=$(printf '%s' "${ENCRYPTED_VALUE}" | openssl enc -d -aes-256-cbc -pbkdf2 -pass env:ENC_KEY -base64 -A) - while IFS= read -r line; do - if [[ -n "${line}" ]]; then - echo "::add-mask::${line}" - fi - done <<<"${value}" - { - echo "value<<__VALUE_EOF__" - printf '%s\n' "${value}" - echo "__VALUE_EOF__" - } >>"${GITHUB_OUTPUT}" diff --git a/.github/actions/encrypt-secret/action.yml b/.github/actions/encrypt-secret/action.yml deleted file mode 100644 index 48a83f0047b..00000000000 --- a/.github/actions/encrypt-secret/action.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Encrypt secret -description: Encrypts a value with a symmetric key so that only ciphertext crosses a job boundary. -inputs: - value: - description: The plaintext to encrypt. May span multiple lines. - required: true - encryption_key: - description: The symmetric key encrypting the value. - required: true -outputs: - encrypted_value: - description: The encrypted value, base64-encoded. - value: ${{ steps.encrypt.outputs.encrypted_value }} - -runs: - using: composite - steps: - - name: Encrypt value - id: encrypt - shell: bash - env: - VALUE: ${{ inputs.value }} - ENC_KEY: ${{ inputs.encryption_key }} - run: | - : "${VALUE:?}" "${ENC_KEY:?}" - encrypted_value=$(printf '%s' "${VALUE}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass env:ENC_KEY -base64 -A) - echo "encrypted_value=${encrypted_value}" >>"${GITHUB_OUTPUT}" diff --git a/.github/actions/install-sccache/action.yml b/.github/actions/install-sccache/action.yml index 3508a0bf98c..6c6143e4de1 100644 --- a/.github/actions/install-sccache/action.yml +++ b/.github/actions/install-sccache/action.yml @@ -13,7 +13,7 @@ runs: steps: - name: Decrypt credentials id: decrypt - uses: $/.github/actions/decrypt-secret + uses: hashintel/.github/.github/actions/decrypt-secret@583b0468f2033c884bc82281e8f9c48edf1daeb7 # main with: encrypted_value: ${{ inputs.encrypted_credentials }} encryption_key: ${{ inputs.encryption_key }} diff --git a/.github/actions/mint-sccache-credentials/action.yml b/.github/actions/mint-sccache-credentials/action.yml index 94828ff9aa6..343651eb4f0 100644 --- a/.github/actions/mint-sccache-credentials/action.yml +++ b/.github/actions/mint-sccache-credentials/action.yml @@ -55,7 +55,7 @@ runs: - name: Encrypt credentials id: encrypt if: ${{ steps.secrets.outcome == 'success' }} - uses: $/.github/actions/encrypt-secret + uses: hashintel/.github/.github/actions/encrypt-secret@583b0468f2033c884bc82281e8f9c48edf1daeb7 # main with: value: ${{ steps.bundle.outputs.credentials }} encryption_key: ${{ inputs.encryption_key }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85ca25e8437..16881a13240 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: # GitHub drops masked values from job outputs, so only ciphertext can cross. - name: Encrypt token id: encrypt - uses: $/.github/actions/encrypt-secret + uses: hashintel/.github/.github/actions/encrypt-secret@583b0468f2033c884bc82281e8f9c48edf1daeb7 # main with: value: ${{ steps.app-token.outputs.token }} encryption_key: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} @@ -65,7 +65,7 @@ jobs: # Directly after checkout, so the plaintext is masked before anything else runs. - name: Decrypt token id: app-token - uses: $/.github/actions/decrypt-secret + uses: hashintel/.github/.github/actions/decrypt-secret@583b0468f2033c884bc82281e8f9c48edf1daeb7 # main with: encrypted_value: ${{ needs.mint-token.outputs.encrypted-github-token }} encryption_key: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} From 795b132a764139436c4fb2925b99b01aab735319 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 15:05:05 +0000 Subject: [PATCH 9/9] SRE-904: Keep the release publishable when the sccache mint fails The GitHub App token mint stays fail-hard; only the sccache bundle is best-effort, flowing an empty output into the uncached-build path. --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 16881a13240..7b3cab949b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,8 +43,10 @@ jobs: value: ${{ steps.app-token.outputs.token }} encryption_key: ${{ secrets.RELEASE_TOKEN_ENC_KEY }} + # Best-effort: on failure the output stays empty and the release builds uncached. - name: Mint sccache credentials id: mint-sccache + continue-on-error: true uses: $/.github/actions/mint-sccache-credentials with: vault_address: ${{ vars.VAULT_STAGE_ADDR }}