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:
+10
-1
@@ -32,12 +32,21 @@ main.go
|
||||
|
||||
## Filtering rules (loop prevention + dedup)
|
||||
|
||||
Two independent guards decide whether an event becomes a job:
|
||||
Three independent guards decide whether an event becomes a job:
|
||||
|
||||
- **Loop prevention** — any event authored by one of teabot's own personality
|
||||
logins is skipped. This is what stops the bot reacting to its own PRs and
|
||||
comments in an infinite loop. Bot-authored items are still *recorded* as
|
||||
processed so they are never reconsidered.
|
||||
- **Author allowlist (security)** — because jobs run
|
||||
`--dangerously-skip-permissions` with a prompt built from event text, teabot
|
||||
dispatches only for events whose author is on the `allowed_authors` list
|
||||
(with an optional per-repo override). This is **fail-closed**: an empty
|
||||
allowlist dispatches nothing. Non-allowlisted events are recorded (never
|
||||
re-triggered) and logged, but never spawn a container. Comment follow-ups are
|
||||
gated on the *new comment's* author, so an untrusted comment on a bot thread
|
||||
cannot reopen the injection path. See
|
||||
[configuration](configuration.md#author-allowlist-security).
|
||||
- **Dedup** — every dispatched issue/PR index and every seen comment ID is
|
||||
recorded in the state store (`~/.local/state/teabot/state.json`). An item is
|
||||
marked processed *before* its job starts, so a subsequent poll (or a restart
|
||||
|
||||
@@ -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