From 3eefac42cddcb39dd04871be6a4cd0c8ec447d88 Mon Sep 17 00:00:00 2001 From: fullsend-code Date: Fri, 29 May 2026 12:50:38 +0000 Subject: [PATCH 1/2] =?UTF-8?q?docs(#1662):=20ADR=200043=20=E2=80=94=20req?= =?UTF-8?q?uire=20authorization=20on=20all=20slash=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ADR proposing that /fs-triage, /fs-code, and /fs-review use the same is_authorized gate already enforced by /fs-fix, /fs-retro, and /fs-prioritize. The ADR addresses the four design questions from the issue: automatic event triggers remain ungated, bot-to-bot workflows are preserved via the existing Bot-type bypass, unauthorized users see silent failure (consistent with existing gated commands), and is_authorized is a platform-level boundary not overridable per-repo. Note: make lint could not run due to sandbox Go toolchain permission error. ADR-specific linters (lint-adr-frontmatter, lint-adr-numbers, lint-adr-status) all passed. Closes #1662 Signed-off-by: Adam Scerra Co-authored-by: Cursor --- ...ire-authorization-on-all-slash-commands.md | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 docs/ADRs/0043-require-authorization-on-all-slash-commands.md diff --git a/docs/ADRs/0043-require-authorization-on-all-slash-commands.md b/docs/ADRs/0043-require-authorization-on-all-slash-commands.md new file mode 100644 index 000000000..8b7f9ebdc --- /dev/null +++ b/docs/ADRs/0043-require-authorization-on-all-slash-commands.md @@ -0,0 +1,160 @@ +--- +title: "43. Require authorization on all agent slash commands" +status: Proposed +relates_to: + - agent-architecture + - security-threat-model +topics: + - authorization + - slash-commands + - dispatch +--- + +# 43. Require authorization on all agent slash commands + +Date: 2026-05-29 + +## Status + +Proposed + +Builds on [ADR 0034](0034-centralized-shim-routing-via-dispatch.md) +(centralized dispatch routing) and +[ADR 0042](0042-fs-prefix-for-slash-commands.md) (`/fs-` prefix +convention). + +Related: [#877](https://github.com/fullsend-ai/fullsend/issues/877) +(agents must not model their own authority limitations — this ADR +implements the platform-level enforcement that principle requires). + +## Context + +The dispatch routing logic (`dispatch.yml` / `reusable-dispatch.yml`) +defines an `is_authorized` helper that checks whether the comment author +has an `author_association` of OWNER, MEMBER, or COLLABORATOR. Today, +only a subset of slash commands gate on this check: + +| Command | Gated? | Notes | +|------------------|--------|----------------------------------| +| `/fs-triage` | No | Any commenter triggers triage | +| `/fs-code` | No | Any commenter triggers code | +| `/fs-review` | No | Any commenter triggers review | +| `/fs-fix` | Yes | `is_authorized` + non-Bot check | +| `/fs-retro` | Yes | `is_authorized` + non-Bot check | +| `/fs-prioritize` | Yes | `is_authorized` + non-Bot check | +| `/fs-fix-stop` | Yes | Author association in shim `if` | + +The ungated commands (`/fs-triage`, `/fs-code`, `/fs-review`) allow any +GitHub user who can comment on a public issue or PR to trigger agent +inference runs. This creates two risks: + +1. **Cost exposure.** Each agent run consumes inference compute. An + external user posting `/fs-code` on every open issue in a public org + could generate significant cost with no rate limit. +2. **Abuse surface.** The security threat model + ([security-threat-model.md](../problems/security-threat-model.md)) + ranks external prompt injection as the highest-priority threat. An + unauthorized user triggering agent runs is a prerequisite for many + injection attacks — the attacker needs the agent to run before they + can influence its behavior. + +The inconsistency also violates the principle of least surprise: a +contributor who sees `/fs-fix` silently ignored (because they are not +authorized) would reasonably expect `/fs-code` to behave the same way. + +## Decision + +All agent slash commands require `is_authorized` before dispatching. The +dispatch routing logic must call `is_authorized` for `/fs-triage`, +`/fs-code`, and `/fs-review` with the same guard pattern already used by +`/fs-fix`, `/fs-retro`, and `/fs-prioritize`: + +```bash +if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then + STAGE="" +fi +``` + +### Automatic event-triggered workflows remain ungated + +The `is_authorized` requirement applies only to slash commands — explicit +human-initiated triggers parsed from `issue_comment` events. The +following automatic triggers are **not** gated by `is_authorized`: + +- `issues.opened` / `issues.edited` → auto-triage +- `issues.labeled` with `ready-to-code` or `ready-for-review` → code or + review +- `pull_request_target.opened` / `synchronize` / `ready_for_review` → + review +- `pull_request_target.closed` → retro +- `pull_request_review.submitted` with `changes_requested` → fix + +These events are generated by GitHub itself based on repository actions, +not by arbitrary commenters. Their authorization is inherent in the +permissions required to perform the triggering action (e.g., only users +with write access can apply labels, only the PR author or a maintainer +can mark a PR ready for review). + +### Bot-to-bot workflows are preserved + +The `COMMENT_USER_TYPE != "Bot"` check precedes `is_authorized` in the +guard. Bot accounts (GitHub App bots) bypass the `is_authorized` gate +entirely. This preserves existing automated workflows where one agent's +post-script triggers the next stage by posting a slash command (e.g., +triage completing and commenting `/fs-code` to start implementation). + +Bot accounts are trusted because they authenticate via GitHub App +installation tokens scoped to the org, not via user credentials. A bot +comment on an issue implies the org has installed and authorized that +GitHub App. + +### Error messaging for unauthorized users + +When a non-Bot user fails `is_authorized`, the dispatch script sets no +`STAGE`, and the workflow exits without dispatching. The user receives no +explicit error message — the command is silently ignored, consistent +with the existing behavior for `/fs-fix`, `/fs-retro`, and +`/fs-prioritize`. + +This is a deliberate choice: posting an error comment would confirm to +an attacker that the slash command was recognized and parsed, leaking +information about the dispatch mechanism. Silent failure is the safer +default for a security boundary. + +If user experience feedback indicates that authorized users are confused +by silent failures (e.g., typos in `author_association` configuration), +a future change could add error messaging gated behind a per-repo opt-in +flag. That decision is out of scope for this ADR. + +### Interaction with per-repo configurability + +The `is_authorized` check is a platform-level security boundary, not a +per-repo policy. Individual repos cannot disable it. Per-repo +configurability (e.g., which stages are enabled, which labels trigger +automation) operates within the authorization boundary — a repo can +disable `/fs-code` entirely, but it cannot make `/fs-code` available to +unauthorized users. + +If a future per-repo configuration system needs to customize +authorization rules (e.g., allowing CONTRIBUTOR association in addition +to OWNER/MEMBER/COLLABORATOR), it should do so by extending the +`is_authorized` function's association list, not by bypassing the check. + +## Consequences + +- All slash commands will require OWNER, MEMBER, or COLLABORATOR + association, closing the cost-exposure and abuse-surface gaps. +- External users (association NONE, CONTRIBUTOR, FIRST_TIME_CONTRIBUTOR, + FIRST_TIMER, MANNEQUIN) can no longer trigger agent runs via slash + commands on public repos. +- Automatic event-triggered workflows continue to function without + authorization gates, preserving the current behavior for issue + creation, label application, and PR events. +- Bot-to-bot orchestration (e.g., triage → code handoff) is unaffected + because bot accounts bypass the human authorization check. +- The dispatch routing logic becomes consistent: every slash command + branch follows the same `non-Bot && is_authorized` guard pattern, + reducing cognitive load for contributors reading the dispatch script. +- Silent failure for unauthorized users means no change to the existing + UX pattern — users who are already familiar with `/fs-fix` being + silently ignored will see the same behavior on all commands. From dca81386536c432d2ab4c12467c83211b0ea7cf4 Mon Sep 17 00:00:00 2001 From: Adam Scerra Date: Fri, 29 May 2026 11:55:05 -0400 Subject: [PATCH 2/2] docs: revise ADR 0043 to gate all dispatch paths universally Address reviewer feedback: - Expand scope from slash commands to all dispatch paths (including issues.opened and pull_request_target.opened) - Replace silent failure with visible feedback for unauthorized users - Remove #553 reference (tangential), keep #877 - Rename file to match updated title Signed-off-by: Adam Scerra Co-authored-by: Cursor --- ...thorization-on-all-agent-dispatch-paths.md | 168 ++++++++++++++++++ ...ire-authorization-on-all-slash-commands.md | 160 ----------------- 2 files changed, 168 insertions(+), 160 deletions(-) create mode 100644 docs/ADRs/0043-require-authorization-on-all-agent-dispatch-paths.md delete mode 100644 docs/ADRs/0043-require-authorization-on-all-slash-commands.md diff --git a/docs/ADRs/0043-require-authorization-on-all-agent-dispatch-paths.md b/docs/ADRs/0043-require-authorization-on-all-agent-dispatch-paths.md new file mode 100644 index 000000000..7de2a249f --- /dev/null +++ b/docs/ADRs/0043-require-authorization-on-all-agent-dispatch-paths.md @@ -0,0 +1,168 @@ +--- +title: "43. Require authorization on all agent dispatch paths" +status: Proposed +relates_to: + - agent-architecture + - security-threat-model +topics: + - authorization + - slash-commands + - dispatch +--- + +# 43. Require authorization on all agent dispatch paths + +Date: 2026-05-29 + +## Status + +Proposed + +Builds on [ADR 0034](0034-centralized-shim-routing-via-dispatch.md) +(centralized dispatch routing) and +[ADR 0042](0042-fs-prefix-for-slash-commands.md) (`/fs-` prefix +convention). + +Related: [#877](https://github.com/fullsend-ai/fullsend/issues/877) +(agents must not model their own authority limitations — this ADR +implements the platform-level enforcement that principle requires). + +## Context + +The dispatch routing logic (`dispatch.yml` / `reusable-dispatch.yml`) +defines an `is_authorized` helper that checks whether the acting user +has an `author_association` of OWNER, MEMBER, or COLLABORATOR. Today, +only a subset of dispatch paths gate on this check: + +| Trigger | Gated? | Notes | +|---------|--------|-------| +| `/fs-triage` | No | Any commenter triggers triage | +| `/fs-code` | No | Any commenter triggers code | +| `/fs-review` | No | Any commenter triggers review | +| `/fs-fix` | Yes | `is_authorized` + non-Bot check | +| `/fs-retro` | Yes | `is_authorized` + non-Bot check | +| `/fs-prioritize` | Yes | `is_authorized` + non-Bot check | +| `issues.opened` | No | Any issue opener triggers triage | +| `pull_request_target.opened` | No | Any PR author triggers review | + +The ungated paths allow any GitHub user to trigger agent inference runs +— either by commenting a slash command on a public issue/PR, or by +opening an issue or PR directly. This creates two risks: + +1. **Cost exposure.** Each agent run consumes inference compute. An + external user opening issues or posting `/fs-code` across a public + org could generate significant cost with no rate limit. +2. **Abuse surface.** The security threat model + ([security-threat-model.md](../problems/security-threat-model.md)) + ranks external prompt injection as the highest-priority threat. An + unauthorized user triggering agent runs is a prerequisite for many + injection attacks — the attacker needs the agent to run before they + can influence its behavior. + +The inconsistency also violates the principle of least surprise: a +contributor who sees `/fs-fix` rejected would reasonably expect +`/fs-code` and auto-triage to behave the same way. + +## Decision + +All agent dispatch paths require `is_authorized` before dispatching. +The authorization check applies universally — to slash commands and to +automatic event triggers where the acting user may be external. + +### Slash commands + +The dispatch routing logic must call `is_authorized` for `/fs-triage`, +`/fs-code`, and `/fs-review` with the same guard pattern already used by +`/fs-fix`, `/fs-retro`, and `/fs-prioritize`: + +```bash +if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then + STAGE="" +fi +``` + +### Automatic event triggers + +For events where the acting user may be external, the dispatch logic +must check the actor's `author_association` before setting a `STAGE`: + +| Event | Actor checked | Gated? | +|-------|---------------|--------| +| `issues.opened` / `issues.edited` | Issue opener | Yes | +| `pull_request_target.opened` / `synchronize` | PR author | Yes | +| `issues.labeled` | Label applier | Already implicit (requires write access) | +| `pull_request_target.ready_for_review` | PR author/maintainer | Already implicit | +| `pull_request_target.closed` | Closer | Already implicit (requires write access) | +| `pull_request_review.submitted` | Reviewer | Already gated (requires review-bot authorship) | + +For external contributors (issues opened or PRs submitted by +non-members), the agent does not fire automatically. A maintainer can +still trigger the agent explicitly by: + +- Applying a label (`ready-to-code`, `ready-for-review`) — label + application requires write access, which is an implicit auth gate. +- Posting a slash command (`/fs-triage`, `/fs-code`, `/fs-review`). + +This does not prevent external contributions — it prevents spending +inference compute on them automatically. + +### Bot-to-bot workflows are preserved + +The `COMMENT_USER_TYPE != "Bot"` check precedes `is_authorized` in the +slash command guard. Bot accounts (GitHub App bots) bypass the +`is_authorized` gate entirely. This preserves existing automated +workflows where one agent's post-script triggers the next stage by +posting a slash command (e.g., triage completing and commenting +`/fs-code` to start implementation). + +Bot accounts are trusted because they authenticate via GitHub App +installation tokens scoped to the org, not via user credentials. + +### Visible feedback for unauthorized users + +When a non-Bot user fails `is_authorized`, the dispatch script must +provide visible feedback. The dispatch mechanism is open source and +present in every enrolled repo's workflow files — silent failure +provides no security benefit but does confuse legitimate contributors. + +The dispatch script must provide some form of visible response (e.g., a +reaction, a comment, or both) so the user knows their command was +received but not executed. The exact mechanism is an implementation +detail. + +For automatic triggers (e.g., unauthorized user opens an issue), no +feedback is needed — the user didn't explicitly request an agent run. + +### Interaction with per-repo configurability + +The `is_authorized` check is a platform-level security boundary, not a +per-repo policy. Individual repos cannot disable it. Per-repo +configurability (e.g., which stages are enabled, which labels trigger +automation) operates within the authorization boundary — a repo can +disable `/fs-code` entirely, but it cannot make `/fs-code` available to +unauthorized users. + +If a future per-repo configuration system needs to customize +authorization rules (e.g., allowing CONTRIBUTOR association in addition +to OWNER/MEMBER/COLLABORATOR), it should do so by extending the +`is_authorized` function's association list, not by bypassing the check. + +## Consequences + +- All dispatch paths require OWNER, MEMBER, or COLLABORATOR association, + closing the cost-exposure and abuse-surface gaps for both slash + commands and automatic triggers. +- External users can no longer trigger agent runs by opening issues, PRs, + or posting slash commands on public repos. +- Maintainers retain full control: labels and slash commands let them + trigger agents on external contributions when appropriate. +- Bot-to-bot orchestration (e.g., triage → code handoff) is unaffected + because bot accounts bypass the human authorization check. +- The dispatch routing logic becomes consistent: every dispatch path + checks authorization of the acting user, reducing cognitive load. +- Unauthorized slash command attempts get visible feedback (reaction + + comment), improving UX for legitimate contributors who don't yet have + the required association. +- External contributors who don't want to become members will depend on + maintainers to trigger agents on their behalf — an acceptable + trade-off to keep the abuse surface minimal. diff --git a/docs/ADRs/0043-require-authorization-on-all-slash-commands.md b/docs/ADRs/0043-require-authorization-on-all-slash-commands.md deleted file mode 100644 index 8b7f9ebdc..000000000 --- a/docs/ADRs/0043-require-authorization-on-all-slash-commands.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -title: "43. Require authorization on all agent slash commands" -status: Proposed -relates_to: - - agent-architecture - - security-threat-model -topics: - - authorization - - slash-commands - - dispatch ---- - -# 43. Require authorization on all agent slash commands - -Date: 2026-05-29 - -## Status - -Proposed - -Builds on [ADR 0034](0034-centralized-shim-routing-via-dispatch.md) -(centralized dispatch routing) and -[ADR 0042](0042-fs-prefix-for-slash-commands.md) (`/fs-` prefix -convention). - -Related: [#877](https://github.com/fullsend-ai/fullsend/issues/877) -(agents must not model their own authority limitations — this ADR -implements the platform-level enforcement that principle requires). - -## Context - -The dispatch routing logic (`dispatch.yml` / `reusable-dispatch.yml`) -defines an `is_authorized` helper that checks whether the comment author -has an `author_association` of OWNER, MEMBER, or COLLABORATOR. Today, -only a subset of slash commands gate on this check: - -| Command | Gated? | Notes | -|------------------|--------|----------------------------------| -| `/fs-triage` | No | Any commenter triggers triage | -| `/fs-code` | No | Any commenter triggers code | -| `/fs-review` | No | Any commenter triggers review | -| `/fs-fix` | Yes | `is_authorized` + non-Bot check | -| `/fs-retro` | Yes | `is_authorized` + non-Bot check | -| `/fs-prioritize` | Yes | `is_authorized` + non-Bot check | -| `/fs-fix-stop` | Yes | Author association in shim `if` | - -The ungated commands (`/fs-triage`, `/fs-code`, `/fs-review`) allow any -GitHub user who can comment on a public issue or PR to trigger agent -inference runs. This creates two risks: - -1. **Cost exposure.** Each agent run consumes inference compute. An - external user posting `/fs-code` on every open issue in a public org - could generate significant cost with no rate limit. -2. **Abuse surface.** The security threat model - ([security-threat-model.md](../problems/security-threat-model.md)) - ranks external prompt injection as the highest-priority threat. An - unauthorized user triggering agent runs is a prerequisite for many - injection attacks — the attacker needs the agent to run before they - can influence its behavior. - -The inconsistency also violates the principle of least surprise: a -contributor who sees `/fs-fix` silently ignored (because they are not -authorized) would reasonably expect `/fs-code` to behave the same way. - -## Decision - -All agent slash commands require `is_authorized` before dispatching. The -dispatch routing logic must call `is_authorized` for `/fs-triage`, -`/fs-code`, and `/fs-review` with the same guard pattern already used by -`/fs-fix`, `/fs-retro`, and `/fs-prioritize`: - -```bash -if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then - STAGE="" -fi -``` - -### Automatic event-triggered workflows remain ungated - -The `is_authorized` requirement applies only to slash commands — explicit -human-initiated triggers parsed from `issue_comment` events. The -following automatic triggers are **not** gated by `is_authorized`: - -- `issues.opened` / `issues.edited` → auto-triage -- `issues.labeled` with `ready-to-code` or `ready-for-review` → code or - review -- `pull_request_target.opened` / `synchronize` / `ready_for_review` → - review -- `pull_request_target.closed` → retro -- `pull_request_review.submitted` with `changes_requested` → fix - -These events are generated by GitHub itself based on repository actions, -not by arbitrary commenters. Their authorization is inherent in the -permissions required to perform the triggering action (e.g., only users -with write access can apply labels, only the PR author or a maintainer -can mark a PR ready for review). - -### Bot-to-bot workflows are preserved - -The `COMMENT_USER_TYPE != "Bot"` check precedes `is_authorized` in the -guard. Bot accounts (GitHub App bots) bypass the `is_authorized` gate -entirely. This preserves existing automated workflows where one agent's -post-script triggers the next stage by posting a slash command (e.g., -triage completing and commenting `/fs-code` to start implementation). - -Bot accounts are trusted because they authenticate via GitHub App -installation tokens scoped to the org, not via user credentials. A bot -comment on an issue implies the org has installed and authorized that -GitHub App. - -### Error messaging for unauthorized users - -When a non-Bot user fails `is_authorized`, the dispatch script sets no -`STAGE`, and the workflow exits without dispatching. The user receives no -explicit error message — the command is silently ignored, consistent -with the existing behavior for `/fs-fix`, `/fs-retro`, and -`/fs-prioritize`. - -This is a deliberate choice: posting an error comment would confirm to -an attacker that the slash command was recognized and parsed, leaking -information about the dispatch mechanism. Silent failure is the safer -default for a security boundary. - -If user experience feedback indicates that authorized users are confused -by silent failures (e.g., typos in `author_association` configuration), -a future change could add error messaging gated behind a per-repo opt-in -flag. That decision is out of scope for this ADR. - -### Interaction with per-repo configurability - -The `is_authorized` check is a platform-level security boundary, not a -per-repo policy. Individual repos cannot disable it. Per-repo -configurability (e.g., which stages are enabled, which labels trigger -automation) operates within the authorization boundary — a repo can -disable `/fs-code` entirely, but it cannot make `/fs-code` available to -unauthorized users. - -If a future per-repo configuration system needs to customize -authorization rules (e.g., allowing CONTRIBUTOR association in addition -to OWNER/MEMBER/COLLABORATOR), it should do so by extending the -`is_authorized` function's association list, not by bypassing the check. - -## Consequences - -- All slash commands will require OWNER, MEMBER, or COLLABORATOR - association, closing the cost-exposure and abuse-surface gaps. -- External users (association NONE, CONTRIBUTOR, FIRST_TIME_CONTRIBUTOR, - FIRST_TIMER, MANNEQUIN) can no longer trigger agent runs via slash - commands on public repos. -- Automatic event-triggered workflows continue to function without - authorization gates, preserving the current behavior for issue - creation, label application, and PR events. -- Bot-to-bot orchestration (e.g., triage → code handoff) is unaffected - because bot accounts bypass the human authorization check. -- The dispatch routing logic becomes consistent: every slash command - branch follows the same `non-Bot && is_authorized` guard pattern, - reducing cognitive load for contributors reading the dispatch script. -- Silent failure for unauthorized users means no change to the existing - UX pattern — users who are already familiar with `/fs-fix` being - silently ignored will see the same behavior on all commands.