Agent token scope: the capability boundary, and its ADR #24

Closed
opened 2026-10-06 12:34:35 +00:00 by pit · 1 comment
Owner

Parent

#17 — Install forgejo-mcp on the Guest as a LAN-reachable Service

What to build

The boundary that makes the endpoint safe to hand an Agent, recorded and then demonstrated against the live endpoint.

The decision: each Agent presents its own Forgejo token, so no single credential is shared across the fleet. Triage is write:issue; browsing PRs and repository code adds read:repository; write:repository — which unlocks contents, Actions, PR merge and push — is refused, so a leaked Agent token's blast radius stops at issues.

The demonstration: the server enforces none of this, so the scopes are the whole boundary and have to be shown to hold. An issue call succeeds with a write:issue token; the same call is refused 403 by a token lacking the issue category; and a write:issue token is refused 403 on a PR-merge or file-write call.

The decision is recorded as an ADR taking the next free number, with its row in the ADR index.

Acceptance criteria

  • An ADR records the Agent token scope: per-Agent tokens; write:issue for triage; read:repository added for PR and code browsing; write:repository refused; and that the Service holds no credential and enforces no authorisation, so the token is the only boundary.
  • The ADR takes the next free number and is listed in the ADR index. (The publish manifest publishes docs/adr/* as a glob, so a new ADR needs no manifest edit.)
  • Capability seam verified live: success with a write:issue token, 403 for the same call without the issue category, and 403 for a PR-merge or file-write call under a write:issue token.
## Parent #17 — Install forgejo-mcp on the Guest as a LAN-reachable Service ## What to build The boundary that makes the endpoint safe to hand an Agent, recorded and then demonstrated against the live endpoint. The decision: each Agent presents **its own** Forgejo token, so no single credential is shared across the fleet. Triage is `write:issue`; browsing PRs and repository code adds `read:repository`; `write:repository` — which unlocks contents, Actions, PR merge and push — is refused, so a leaked Agent token's blast radius stops at issues. The demonstration: the server enforces none of this, so the scopes are the whole boundary and have to be shown to hold. An issue call succeeds with a `write:issue` token; the same call is refused `403` by a token lacking the issue category; and a `write:issue` token is refused `403` on a PR-merge or file-write call. The decision is recorded as an ADR taking the next free number, with its row in the ADR index. ## Acceptance criteria - [ ] An ADR records the Agent token scope: per-Agent tokens; `write:issue` for triage; `read:repository` added for PR and code browsing; `write:repository` refused; and that the Service holds no credential and enforces no authorisation, so the token is the only boundary. - [ ] The ADR takes the next free number and is listed in the ADR index. (The publish manifest publishes `docs/adr/*` as a glob, so a new ADR needs no manifest edit.) - [ ] Capability seam verified live: success with a `write:issue` token, `403` for the same call without the issue category, and `403` for a PR-merge or file-write call under a `write:issue` token.
Author
Owner

Implemented on the integration branch hermes/17-forgejo-mcp (commit 6f882f0), draft PR #56.

ADR 0005 — docs/adr/0005-agent-token-scope.md, the next free number (0001–0004 taken), with its row added to docs/adr/index.md. No manifest edit: docs/adr/* is already a published glob.

It records: per-Agent Forgejo tokens (no shared fleet credential); write:issue for triage, plus read:organization — honestly more than the ticket's prose, because the label tools (add_issue_labels, create_issue/update_issue with labels) call GET /orgs/<owner>/labels first and that call is 403 under write:issue alone, failing the whole tool; read:repository to browse PRs and code; write:repository refused; and that the credential-less Service enforces no authorisation, so the token scope is the only boundary.

Capability seam, verified live against the PRE endpoint http://10.12.0.142:8089/mcp on pit/mcp-probe (tokens minted on the PRE Guest only — no PROD mutation; server forgejo-mcp 3.2.0, 156 tools):

call token answer
list_repo_issues write:issue ok (isError=False)
list_repo_issues read:repository refused: token does not have at least one of required scope(s): [read:issue]
create_file write:issue refused: ... [write:repository]
merge_pull_request write:issue refused: server returned HTTP 403 (expected 200)

Closes with PR #56.

Implemented on the integration branch `hermes/17-forgejo-mcp` (commit 6f882f0), draft PR #56. **ADR 0005** — `docs/adr/0005-agent-token-scope.md`, the next free number (0001–0004 taken), with its row added to `docs/adr/index.md`. No manifest edit: `docs/adr/*` is already a published glob. It records: per-Agent Forgejo tokens (no shared fleet credential); `write:issue` for triage, **plus `read:organization`** — honestly more than the ticket's prose, because the label tools (`add_issue_labels`, `create_issue`/`update_issue` with `labels`) call `GET /orgs/<owner>/labels` first and that call is `403` under `write:issue` alone, failing the whole tool; `read:repository` to browse PRs and code; `write:repository` refused; and that the credential-less Service enforces no authorisation, so the token scope is the only boundary. **Capability seam, verified live** against the PRE endpoint `http://10.12.0.142:8089/mcp` on `pit/mcp-probe` (tokens minted on the PRE Guest only — no PROD mutation; server `forgejo-mcp 3.2.0`, 156 tools): | call | token | answer | | --- | --- | --- | | `list_repo_issues` | `write:issue` | **ok** (`isError=False`) | | `list_repo_issues` | `read:repository` | **refused**: `token does not have at least one of required scope(s): [read:issue]` | | `create_file` | `write:issue` | **refused**: `... [write:repository]` | | `merge_pull_request` | `write:issue` | **refused**: `server returned HTTP 403 (expected 200)` | Closes with PR #56.
pit closed this issue 2026-10-07 20:14:13 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
olympus/infra-forge#24
No description provided.