You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
housekeeping-dependencies.yml runs everything in one job that holds permissions: id-token: write. A compromised package pulled in by the install-renovate composite action — or a compromise in Renovate itself — executes in the same job that can authenticate to Vault as ci-renovate. For the lifetime of the run it could ask Vault Transit to sign fresh App JWTs for the hash-dependencies App and mint new installation tokens of its own choosing, well beyond the single token the workflow hands to Renovate. npm ci --ignore-scripts from the committed lockfile (SRE-901) keeps install-time code out, but runtime dependency code still runs next to the OIDC permission.
After
Two jobs with disjoint privileges:
mint-token is the only job with id-token: write, and it installs nothing — it runs only the existing github-app-token composite action (vault-action + the curl/openssl token.sh, unchanged) plus one openssl enc step. No npm, no node_modules, so no dependency code ever executes next to the Vault credential.
renovate has no id-token permission at all (actions: read, contents: read only — what the cache download and run already used). The worst a compromised dependency can reach here is the one installation token it was going to be handed anyway: valid for an hour, scoped to the calling repository. It can no longer trade that position up into fresh Vault-signed tokens.
lint.yml also installs the Renovate dependency tree but never held id-token: write, so there is nothing to split there; it instead gets an explicit permissions: contents: read so its dependency-installing job runs with a read-only GITHUB_TOKEN rather than whatever the default grant allows.
How the token crosses the job boundary
GitHub silently drops masked secret values from job outputs, so the plaintext token cannot be passed between jobs — which is exactly the property we lean on: the mint job encrypts the token (openssl enc -aes-256-cbc -pbkdf2 -salt, passphrase from the new RENOVATE_TOKEN_ENC_KEY secret) and exposes only the ciphertext as a job output. The Renovate job's first step decrypts it and immediately ::add-mask::es the plaintext before any other code runs. Job outputs are visible to anyone who can read the run — this repo is public — so ciphertext is also what makes the handoff safe to expose at all. Round-trip of the exact encrypt/decrypt commands was verified with a dummy token.
Before merging: create the secret
One new org-level Actions secret (I cannot create it):
Name: RENOVATE_TOKEN_ENC_KEY
Value: generate with openssl rand -base64 32
Scope: the org repos that run these Renovate workflows (this repo plus every caller of the reusable workflow)
Until it exists, the mint-token job fails fast at the Encrypt token step (${ENC_KEY:?}), without having handed anything to Renovate.
Caller-side changes required
housekeeping-dependencies.yml is a reusable workflow (on: workflow_call), and its secrets: block deliberately uses explicit passthrough rather than secrets: inherit (see the existing CF_ACCESS_STAGE_CLIENT_SECRET note). RENOVATE_TOKEN_ENC_KEY is declared required: true the same way, so every caller must add one line next to its existing CF_ACCESS_STAGE_CLIENT_SECRET passthrough:
A caller that omits it fails at dispatch (consistent with the existing design). Callers that use secrets: inherit (if any) need no change beyond the org secret being scoped to their repo. This PR touches no other repos; caller updates should land after the org secret exists and this merges.
Medium Risk
Changes CI privilege boundaries and introduces a new org secret required for all reusable-workflow callers; misconfiguration blocks Renovate until secrets are wired, but the design reduces Vault exposure during dependency execution.
Overview Splits the centralized Renovate workflow into two jobs so Vault OIDC (id-token: write) never runs alongside install-renovate / npm dependency code.
The new mint-token job only runs github-app-token plus AES-256-CBC encryption of the installation token and exports ciphertext as a job output. renovate depends on it, decrypts and masks the token as its first step, then runs the existing cache + Renovate steps with actions: read / contents: read only (no id-token). Workflow-level permissions are cleared to {}; callers must pass the new required secret RENOVATE_TOKEN_ENC_KEY alongside the existing Cloudflare secret.
lint.yml gets an explicit contents: read grant so its Renovate-related install runs with a read-only GITHUB_TOKEN.
Reviewed by Cursor Bugbot for commit cd6717d. Bugbot is set up for automated code reviews on this repo. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Tim Diekmann · Slack thread
SRE-904
Before
housekeeping-dependencies.ymlruns everything in one job that holdspermissions: id-token: write. A compromised package pulled in by theinstall-renovatecomposite action — or a compromise in Renovate itself — executes in the same job that can authenticate to Vault asci-renovate. For the lifetime of the run it could ask Vault Transit to sign fresh App JWTs for thehash-dependenciesApp and mint new installation tokens of its own choosing, well beyond the single token the workflow hands to Renovate.npm ci --ignore-scriptsfrom the committed lockfile (SRE-901) keeps install-time code out, but runtime dependency code still runs next to the OIDC permission.After
Two jobs with disjoint privileges:
mint-tokenis the only job withid-token: write, and it installs nothing — it runs only the existinggithub-app-tokencomposite action (vault-action + the curl/openssltoken.sh, unchanged) plus oneopenssl encstep. No npm, nonode_modules, so no dependency code ever executes next to the Vault credential.renovatehas noid-tokenpermission at all (actions: read,contents: readonly — what the cache download and run already used). The worst a compromised dependency can reach here is the one installation token it was going to be handed anyway: valid for an hour, scoped to the calling repository. It can no longer trade that position up into fresh Vault-signed tokens.lint.ymlalso installs the Renovate dependency tree but never heldid-token: write, so there is nothing to split there; it instead gets an explicitpermissions: contents: readso its dependency-installing job runs with a read-onlyGITHUB_TOKENrather than whatever the default grant allows.How the token crosses the job boundary
GitHub silently drops masked secret values from job outputs, so the plaintext token cannot be passed between jobs — which is exactly the property we lean on: the mint job encrypts the token (
openssl enc -aes-256-cbc -pbkdf2 -salt, passphrase from the newRENOVATE_TOKEN_ENC_KEYsecret) and exposes only the ciphertext as a job output. The Renovate job's first step decrypts it and immediately::add-mask::es the plaintext before any other code runs. Job outputs are visible to anyone who can read the run — this repo is public — so ciphertext is also what makes the handoff safe to expose at all. Round-trip of the exact encrypt/decrypt commands was verified with a dummy token.Before merging: create the secret
One new org-level Actions secret (I cannot create it):
RENOVATE_TOKEN_ENC_KEYopenssl rand -base64 32Until it exists, the
mint-tokenjob fails fast at theEncrypt tokenstep (${ENC_KEY:?}), without having handed anything to Renovate.Caller-side changes required
housekeeping-dependencies.ymlis a reusable workflow (on: workflow_call), and itssecrets:block deliberately uses explicit passthrough rather thansecrets: inherit(see the existingCF_ACCESS_STAGE_CLIENT_SECRETnote).RENOVATE_TOKEN_ENC_KEYis declaredrequired: truethe same way, so every caller must add one line next to its existingCF_ACCESS_STAGE_CLIENT_SECRETpassthrough:A caller that omits it fails at dispatch (consistent with the existing design). Callers that use
secrets: inherit(if any) need no change beyond the org secret being scoped to their repo. This PR touches no other repos; caller updates should land after the org secret exists and this merges.