Add fail-closed author allowlist gating job dispatch
Sessions run claude with --dangerously-skip-permissions and a prompt built from issue/PR/comment text, so only trusted authors may supply that text. teabot now dispatches a job only when the triggering event's author login is on an allowlist; an empty allowlist dispatches nothing (fail-closed). Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
This commit is contained in:
@@ -21,6 +21,8 @@ Override the path with `--config/-c`. Generate a starting point with
|
||||
| `anthropic_api_key` | *(unset)* | If set, injected as `ANTHROPIC_API_KEY`. |
|
||||
| `anthropic_base_url` | *(unset)* | If set, injected as `ANTHROPIC_BASE_URL`. |
|
||||
| `state_dir` | `~/.local/state/teabot` | Where processed-event state is persisted. |
|
||||
| `allowed_authors` | *(empty → fail-closed)* | Gitea usernames whose events may trigger a job. See [Author allowlist](#author-allowlist-security). |
|
||||
| `repo_allowed_authors` | *(unset)* | Optional per-repo override of `allowed_authors`, keyed by `owner/name`. |
|
||||
| `personalities` | *(required)* | Bot identities (see below). |
|
||||
|
||||
`~` and `~/` are expanded in path-valued keys.
|
||||
@@ -73,6 +75,45 @@ prevention** (it never reacts to events authored by any personality's username).
|
||||
At least one personality must be able to implement and at least one to review,
|
||||
or config validation fails.
|
||||
|
||||
## Author allowlist (security)
|
||||
|
||||
Each job runs `claude --print --dangerously-skip-permissions` inside a container,
|
||||
and its **prompt is built from event text** — the issue title/body, PR
|
||||
description/diff, and comments. That text is attacker-controllable: anyone who
|
||||
can open an issue or comment on the repo could otherwise inject instructions
|
||||
into a session that has permissions disabled. The allowlist is the control that
|
||||
closes this: teabot dispatches a job **only** when the triggering event's author
|
||||
login is on the allowlist.
|
||||
|
||||
```yaml
|
||||
# Global allowlist.
|
||||
allowed_authors:
|
||||
- benvin
|
||||
- trusted-colleague
|
||||
|
||||
# Optional per-repo override. A present entry fully REPLACES the global list for
|
||||
# that repo; an empty list disables dispatch for it; an absent entry falls back
|
||||
# to the global list.
|
||||
repo_allowed_authors:
|
||||
unkin/teabot:
|
||||
- benvin
|
||||
```
|
||||
|
||||
Semantics:
|
||||
|
||||
- **Fail-closed.** An empty/absent allowlist dispatches **nothing**. teabot logs
|
||||
a warning at startup when no allowlist is configured anywhere.
|
||||
- Events from non-allowlisted authors are **recorded** as processed (so they
|
||||
don't re-trigger) and logged at info with the author name, but never spawn a
|
||||
container. They are *not* marked as engaged threads.
|
||||
- **Comment follow-ups are gated on the new comment's author**, not just the
|
||||
thread. A comment from a non-allowlisted user on a thread teabot previously
|
||||
acted on is ignored — otherwise an untrusted comment could reopen the
|
||||
injection path on a bot thread.
|
||||
- Matching is case-insensitive (Gitea usernames are unique case-insensitively).
|
||||
- This is **separate from and in addition to** loop prevention: bot personality
|
||||
logins are always excluded first, even if one were listed here.
|
||||
|
||||
## Claude credentials
|
||||
|
||||
By default teabot uses your Claude **subscription** auth: it copies
|
||||
|
||||
Reference in New Issue
Block a user