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:
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 bygitea_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.
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)
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>
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 entirely2026-07-21 15:19:06 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Removes the admin-owned
gitea_tokenfrom every pipeline, and stopsissue-fixborrowing the security-review pipeline's identity. After this PRgitea_tokenis unreferenced and can be deleted from Woodpecker and rotated.alert-monitorgitea_token(admin)alert_monitor_bot_tokenwrite:issueissue-fixgitea_token+security_bot_tokenissue_fix_bot_tokenwrite:issue,write:repositoryupdate-auditgitea_token(admin)update_audit_bot_tokenwrite:issuebuild-ci-imagesgitea_token(admin)ci_images_tokenpackage: read+writeAll secrets are in place, event-scoped to match each pipeline's
when:block (cron, manualfor the agent pipelines;push, manualfor image builds).Scope containment verified empirically, not assumed. The three bot accounts are write collaborators, but the PAT scope is the effective cap:
Also:
gitea_tokenworkaround and thesecurity_bot_token403 that motivated it).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_tokenis used bysecurity-scan.ymlalone again.One caveat, deliberately not fixed here:
ci_images_tokenis still owned bygitea_admin. Gitea scopes packages to a user/org namespace and these images live undergitea_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
security(ci): give each agent pipeline its own scoped bot identityto security(ci): retire the admin gitea_token from CI entirely