security(ci): retire the admin gitea_token from CI entirely #333

Merged
k.renner merged 2 commits from claude-bot/cutover-bot-tokens into master 2026-07-21 15:20:36 +00:00
Member

Removes the admin-owned gitea_token from every pipeline, and stops issue-fix borrowing the security-review pipeline's identity. After this PR gitea_token is unreferenced and can be deleted from Woodpecker and rotated.

Pipeline Was Now Scopes
alert-monitor gitea_token (admin) alert_monitor_bot_token write:issue
issue-fix gitea_token + security_bot_token issue_fix_bot_token write:issue, write:repository
update-audit gitea_token (admin) update_audit_bot_token write:issue
build-ci-images gitea_token (admin) ci_images_token package: read+write

All secrets are in place, event-scoped to match each pipeline's when: block (cron, manual for the agent pipelines; push, manual for image builds).

Scope containment verified empirically, not assumed. The three bot accounts are write collaborators, but the PAT scope is the effective cap:

write:issue-only tokens:  issues 200 | /user/repos 403 | /admin/users 403
ci_images_token:          /v2/ registry 200 | /user/repos 403 | /admin/users 403 | issues 403

Also:

  • Retires two stale rationales in comments (the gitea_token workaround and the security_bot_token 403 that motivated it).
  • issue-fix's git identity is now issue-fix-bot@renner.uno, so alert fixes stop appearing as security-review output — this is what made #327 look like security-bot's work.
  • security_bot_token is used by security-scan.yml alone again.

One caveat, deliberately not fixed here: ci_images_token is still owned by gitea_admin. Gitea scopes packages to a user/org namespace and these images live under gitea_admin/*, so no other account can publish there. The credential is now narrow (packages only), but moving the images to a dedicated org is the real fix — a 38-reference migration across pipelines and Flux-deployed CronJobs, tracked in #330 rather than rushed in here. That pipeline runs no LLM agent, so the exfil path motivating the other cutovers does not apply to it.

Merge #329 (agent Read sandbox) too — independent, different lines.

Refs #330. Closes #197.

🤖 Generated with Claude Code

Removes the admin-owned `gitea_token` from **every** pipeline, and stops `issue-fix` borrowing the security-review pipeline's identity. After this PR `gitea_token` is unreferenced and can be deleted from Woodpecker and rotated. | Pipeline | Was | Now | Scopes | |---|---|---|---| | `alert-monitor` | `gitea_token` (admin) | `alert_monitor_bot_token` | `write:issue` | | `issue-fix` | `gitea_token` + `security_bot_token` | `issue_fix_bot_token` | `write:issue`, `write:repository` | | `update-audit` | `gitea_token` (admin) | `update_audit_bot_token` | `write:issue` | | `build-ci-images` | `gitea_token` (admin) | `ci_images_token` | `package: read+write` | All secrets are in place, event-scoped to match each pipeline's `when:` block (`cron, manual` for the agent pipelines; `push, manual` for image builds). **Scope containment verified empirically, not assumed.** The three bot accounts are write collaborators, but the PAT scope is the effective cap: ``` write:issue-only tokens: issues 200 | /user/repos 403 | /admin/users 403 ci_images_token: /v2/ registry 200 | /user/repos 403 | /admin/users 403 | issues 403 ``` Also: - Retires two stale rationales in comments (the `gitea_token` workaround and the `security_bot_token` 403 that motivated it). - issue-fix's git identity is now `issue-fix-bot@renner.uno`, so alert fixes stop appearing as security-review output — this is what made #327 look like security-bot's work. - `security_bot_token` is used by `security-scan.yml` alone again. **One caveat, deliberately not fixed here:** `ci_images_token` is still *owned by* `gitea_admin`. Gitea scopes packages to a user/org namespace and these images live under `gitea_admin/*`, so no other account can publish there. The credential is now narrow (packages only), but moving the images to a dedicated org is the real fix — a 38-reference migration across pipelines and Flux-deployed CronJobs, tracked in #330 rather than rushed in here. That pipeline runs no LLM agent, so the exfil path motivating the other cutovers does not apply to it. Merge #329 (agent Read sandbox) too — independent, different lines. Refs #330. Closes #197. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 1 commit 2026-07-21 15:11:16 +00:00
Removes the admin-owned gitea_token from every pipeline that runs an LLM
agent, and stops issue-fix borrowing the security-review pipeline's
identity. Each pipeline now holds a credential that can do only what that
pipeline needs.

  alert-monitor  -> alert_monitor_bot_token   (write:issue)
  issue-fix      -> issue_fix_bot_token       (write:issue, write:repository)
  update-audit   -> update_audit_bot_token    (write:issue)

The accounts are write collaborators, but the PAT scope is the effective
cap — verified empirically: the write:issue-only tokens return 200 on the
issues API and 403 on /user/repos and /admin/users.

Also retires two stale rationales in the comments (the gitea_token
workaround and the security_bot_token 403 that motivated it), and changes
issue-fix's git identity from security-bot@renner.uno to
issue-fix-bot@renner.uno so alert fixes no longer appear as
security-review output.

build-ci-images.yml still uses gitea_token for registry/release writes and
needs a write:package token before gitea_token can be retired and rotated;
tracked in #330.

Refs #330. Closes #197.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot added 1 commit 2026-07-21 15:18:50 +00:00
security(ci): move image builds off the admin token to a package-scoped PAT
ci/woodpecker/push/build-ci-images Pipeline was successful
43ed92b148
build-ci-images.yml was the last consumer of gitea_token. It used the
admin-owned credential purely as a container-registry login, which meant a
credential that could do anything the admin account could was sitting in a
pipeline that only ever needed to push images.

Replace it with ci_images_token: a PAT scoped to `package: read+write` and
nothing else. Verified — 403 on /user/repos, /admin/users and the issues
API; 200 on the /v2/ registry endpoints kaniko authenticates against.

gitea_token is now unreferenced across every pipeline and can be removed
from Woodpecker and rotated.

The token remains OWNED by gitea_admin because Gitea scopes packages to a
user/org namespace and these images live under gitea_admin/*; a different
account cannot publish there. Moving them to a dedicated org is a
38-reference migration across pipelines and Flux-deployed CronJobs and is
tracked in #330.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
k.renner changed title from security(ci): give each agent pipeline its own scoped bot identity to security(ci): retire the admin gitea_token from CI entirely 2026-07-21 15:19:06 +00:00
k.renner merged commit 99d559dbda into master 2026-07-21 15:20:36 +00:00
Sign in to join this conversation.