Skip to content

ci: read the bot's key from the stable-only data-update environment - #323

Merged
liquidsec merged 2 commits into
stablefrom
security/bot-key-in-data-update
Oct 22, 2026
Merged

liquidsec merged 2 commits into
stablefrom
security/bot-key-in-data-update

Conversation

@pjhartlieb

@pjhartlieb pjhartlieb commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

@liquidsec this is step 0 of the bot hardening discussed in #406 (mapping). It needs your review.

Change

The only two jobs that use cloudcheck-update-bot's key, daily-update.yml and python-tests.yml's publish, now:

  • run in the data-update environment, which accepts deployments from stable only, with admin bypass off and no reviewers, since it runs on a schedule;
  • read the App ID from the environment variable CLOUDCHECK_APP_ID (already set, 4078637), and the key and BBOT_IO_API_KEY from environment secrets;
  • mint the App token for this repository only, with contents write only;
  • drop to contents: read. The push and the tag push already use the App token, not GITHUB_TOKEN.

Nothing else changes. The data generation, the commit, the push and the publish steps are untouched. The data validation, pre-push path check, SHA pins and --locked stay in your Phase 1 PR.

Why

Today any writer can run a modified copy of daily-update.yml from a branch (workflow_dispatch, or a pull_request run). That copy can read CLOUDCHECK_APP_PRIVATE_KEY, a repository secret, and then push to stable through the bot's bypass. In the environment, those secrets are released only to jobs running on stable.

Safe to merge before the new key exists

Inside an environment job, an environment secret overrides a repository secret of the same name, and the repository secret still applies when the environment has none. So until an org owner puts the new key in data-update, both jobs keep using today's key. Nothing breaks if this merges first.

Verification after merge

  1. Run daily-update by hand from stable, and confirm it pushes, or reports no change.
  2. On the next stable push, the publish job runs in data-update.
  3. Once the new key and BBOT_IO_API_KEY are in the environment, delete the repository copies, revoke the old key, and run daily-update once more.

dev has the same copies of both files, so a back-merge of stable into dev keeps them in step. Their dev copies can't read data-update anyway, because it accepts stable only.

Lint: actionlint clean; zizmor finds nothing on either file, before or after.

daily-update.yml and python-tests.yml's publish job, the only two jobs
that use cloudcheck-update-bot's key, now run in the data-update
environment (deployment branch: stable only, no admin bypass). They read
the App ID from the environment variable CLOUDCHECK_APP_ID and the key
(and BBOT_IO_API_KEY) from environment secrets, so no other branch, pull
request or dispatched copy of a workflow can read them.

The App token is minted for this repository only, with contents write
only, and both jobs drop to contents: read: the push and the tag push
already use the App token, not GITHUB_TOKEN.

Safe to merge before the new key is in place: inside an environment job
an environment secret overrides the repository secret of the same name,
and the repository secret still applies when the environment has none.

Refs blacklanternsecurity/bbot-enterprise#406, blacklanternsecurity/.github-config#16

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pjhartlieb
pjhartlieb requested a review from liquidsec October 21, 2026 21:17
@singlerider singlerider added the github_actions Pull requests that update GitHub Actions code label Oct 22, 2026
@liquidsec

Copy link
Copy Markdown
Contributor

@pjhartlieb thanks, this matches step 1 of the plan, and I've checked the environment config (stable-only branch policy, admin bypass off). Two changes before merge:

1. Drop owner: and repositories: from both token steps.
On schedule runs the event payload is close to empty, so github.event.repository.name is probably blank there. With owner set and repositories empty, create-github-app-token mints a token for every repo in the owner's installation, which is the opposite of what the comment says. With both left out, the action already limits the token to the current repo. Keep permission-contents: write. Also, a manual dispatch has a full payload, so the post-merge check wouldn't catch this. Only the 06:00 scheduled run would.

2. Take publish off the bot's key instead of moving it into data-update.
publish uses the bot token only to push the release tag. No workflow triggers on tags and no ruleset covers tags, so GITHUB_TOKEN with contents: write can push it. Today the job holds a token that can push straight to stable past review, and it stays available while maturin build and maturin publish run third-party build code (Rust build scripts, crates.io and PyPI build dependencies). Please:

  • drop the token step and environment: data-update from publish;
  • set contents: write on the job and use the default checkout credentials for the tag push.

GITHUB_TOKEN can't push to stable, because the PR rule applies to it. That leaves daily-update as the only job that uses the bot's key.

After that, good to merge, with the new key and BBOT_IO_API_KEY going into data-update and the repo secrets deleted and the old key revoked on the same day, as planned.

Review on #323:

- create-github-app-token with owner set and repositories empty mints a
  token for every repository in the installation. On a scheduled run
  github.event.repository.name may be empty, so drop owner and
  repositories: with neither set, the token covers only this repository.

- publish used the bot's key only to push the release tag, and held a
  token that can push straight to stable while maturin runs third-party
  build code. It now pushes the tag with GITHUB_TOKEN (contents: write),
  which the stable PR rule applies to, and leaves data-update. Checkout
  keeps no credentials, so only the tag step gets the token; the build
  steps can't push branches or move tags.

daily-update is now the only job that uses the bot's key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pjhartlieb

Copy link
Copy Markdown
Contributor Author

@liquidsec thanks, both are right and both are in 5ccaf6e6.

  1. Token scope. I checked create-github-app-token's README at v3: with owner set and repositories empty, the token covers every repo in the installation, and with neither set, only the current repo. owner and repositories are gone from daily-update. It keeps permission-contents: write.

  2. publish off the bot's key. The token step and environment: data-update are gone. The job has contents: write and pushes the tag with GITHUB_TOKEN. I confirmed no workflow triggers on tags and all three rulesets target branches only. daily-update is now the only job that uses the bot's key.

One addition, following your point about third-party build code: with default checkout credentials, GITHUB_TOKEN would sit in .git/config during maturin build and maturin publish. No ruleset covers tags, so that code could move or delete a v* tag. So checkout now uses persist-credentials: false, and only the tag step gets the token, as GH_TOKEN in the push URL. git fetch --tags doesn't need credentials on a public repo.

Checks: actionlint 1.7.12 is clean. zizmor 1.15.2 (offline, medium and up, both files) shows no new findings against stable and one fewer artipacked. The rest is pre-existing.

The PR is still safe to merge before the new key exists: daily-update falls back to the repository secrets until data-update has its own.

@liquidsec liquidsec left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, both changes look right, and persist-credentials: false with the token passed only to the tag step is a good addition.

One thing to confirm before merging: moving publish to GITHUB_TOKEN assumes the stable ruleset's bypass list contains only the cloudcheck-update-bot app. If "GitHub Actions" (or a role that covers it) is on that list, GITHUB_TOKEN with contents: write could push to stable. I can't see bypass actors at my permission level, so could an admin check Settings → Rules → default?

Also, the PR description still says both jobs run in data-update and that the tag push uses the App token. Please update it before squashing.

@liquidsec
liquidsec merged commit cc828f8 into stable Oct 22, 2026
20 checks passed
@liquidsec
liquidsec deleted the security/bot-key-in-data-update branch October 22, 2026 19:19
liquidsec added a commit that referenced this pull request Oct 23, 2026
chore: merge stable into dev (#323 workflows, signature data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

github_actions Pull requests that update GitHub Actions code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants