docs(raw-api): document gitea_request, env vars and policy examples

Adds docs/raw-api.md (two-layer policy, sensitive denylist, env vars, write-mode
warning), links it from index and api-reference, documents RAW_API_ENABLED /
RAW_API_ALLOW_SENSITIVE in .env.example, and adds commented virtual-tool-name
deny examples to policy.yaml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-26 12:26:57 +02:00
parent 8e41fd12af
commit 8c84d76bd5
5 changed files with 158 additions and 2 deletions
+11
View File
@@ -63,6 +63,17 @@ WRITE_MODE=false
WRITE_REPOSITORY_WHITELIST= WRITE_REPOSITORY_WHITELIST=
WRITE_ALLOW_ALL_TOKEN_REPOS=false WRITE_ALLOW_ALL_TOKEN_REPOS=false
# Raw API dispatch (gitea_request escape hatch). See docs/raw-api.md.
# gitea_request can call any Gitea REST endpoint (method + path). It is still
# subject to policy.yaml, WRITE_MODE + the write whitelist, and a built-in
# admin/credential denylist. Set RAW_API_ENABLED=false to remove the tool's
# ability to dispatch entirely.
RAW_API_ENABLED=true
# Allow gitea_request to reach admin/credential surfaces (/admin, *tokens*,
# *secrets*, *hooks*, *keys*, applications/oauth2, runner registration tokens).
# Leave false unless you fully understand the exposure.
RAW_API_ALLOW_SENSITIVE=false
# Automation mode (disabled by default) # Automation mode (disabled by default)
AUTOMATION_ENABLED=false AUTOMATION_ENABLED=false
AUTOMATION_SCHEDULER_ENABLED=false AUTOMATION_SCHEDULER_ENABLED=false
+12 -2
View File
@@ -90,8 +90,18 @@ Scope requirements:
- `create_milestone` (`owner`, `repo`, `title`, optional `description`, `due_on`) - `create_milestone` (`owner`, `repo`, `title`, optional `description`, `due_on`)
- `edit_issue_comment` (`owner`, `repo`, `comment_id`, `body`) - `edit_issue_comment` (`owner`, `repo`, `comment_id`, `body`)
Not supported by design: merge, branch/label/release deletion, force push, repo/admin Not supported by the dedicated tools by design: merge, branch/label/release deletion,
management. force push, repo/admin management. Endpoints not covered above are reachable through the
generic `gitea_request` escape hatch (subject to policy, write-mode, and a sensitive-path
denylist) — see [Raw API Dispatch](raw-api.md).
## Raw API Dispatch
- `gitea_request` (`method`, `path`, optional `query`, `body`)
- Calls an arbitrary Gitea REST endpoint. `GET`/`HEAD` are reads; other methods are
writes and require write-mode plus a whitelisted repository. Admin/credential
endpoints are blocked unless `RAW_API_ALLOW_SENSITIVE=true`. See
[Raw API Dispatch](raw-api.md) for the two-layer policy model and full details.
Note: `create_issue`, `add_labels`, and `remove_labels` accept label **names**; the Note: `create_issue`, `add_labels`, and `remove_labels` accept label **names**; the
server resolves them to Gitea label ids and returns a clear error for unknown labels. server resolves them to Gitea label ids and returns a clear error for unknown labels.
+1
View File
@@ -17,6 +17,7 @@ AegisGitea MCP acts as a secure bridge between AI assistants (such as Claude, Cl
| [Getting Started](getting-started.md) | Installation and first-time setup | | [Getting Started](getting-started.md) | Installation and first-time setup |
| [Configuration](configuration.md) | All environment variables and settings | | [Configuration](configuration.md) | All environment variables and settings |
| [API Reference](api-reference.md) | HTTP endpoints and MCP tools | | [API Reference](api-reference.md) | HTTP endpoints and MCP tools |
| [Raw API Dispatch](raw-api.md) | The generic `gitea_request` escape-hatch tool |
| [Architecture](architecture.md) | System design and data flow | | [Architecture](architecture.md) | System design and data flow |
| [Security](security.md) | Authentication, rate limiting, and audit logging | | [Security](security.md) | Authentication, rate limiting, and audit logging |
| [Deployment](deployment.md) | Docker and production deployment | | [Deployment](deployment.md) | Docker and production deployment |
+119
View File
@@ -0,0 +1,119 @@
# Raw API Dispatch (`gitea_request`)
`gitea_request` is a generic escape hatch that can call **any** Gitea REST
endpoint by method and path. It exists for the long tail of the Gitea API that
the curated, typed tools do not cover (merging PRs, reviews, writing files,
webhooks, branch/tag protections, collaborators, Actions/CI, packages,
notifications, and so on).
> Prefer the dedicated tools whenever one exists. Use `gitea_request` only for
> endpoints they do not cover. It is subject to policy, write-mode, and the
> sensitive-path denylist described below.
## Arguments
| Field | Type | Notes |
|-------|------|-------|
| `method` | enum | `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` (case-insensitive). Any other method is rejected before any network call. |
| `path` | string | Gitea REST path. The `/api/v1` prefix is optional. A full URL may be supplied — the host and query string are stripped. |
| `query` | object | Optional query-string parameters. |
| `body` | object | Optional JSON request body. **Never logged.** |
The response is returned in a stable envelope:
```json
{
"method": "GET",
"path": "/api/v1/repos/acme/app/pulls/1",
"write": false,
"repository": "acme/app",
"data": { "...": "..." }
}
```
List responses add `count` and `omitted`; oversized objects are returned as a
truncated JSON string with `"truncated": true`. All responses are bounded by
`MAX_TOOL_RESPONSE_ITEMS` / `MAX_TOOL_RESPONSE_CHARS`.
## Two-layer authorization
A single tool surface would normally collapse the granularity of `policy.yaml`.
To preserve it, every call is authorized twice:
1. **Central gate (`server.py`).** The registered `gitea_request` tool name is
allowed/denied like any other tool. In service-PAT mode the central gate also
parses the target repository from the path and verifies that the signed-in
user has permission on that repository before the service PAT is used.
2. **Handler gate (`raw_tools.py`).** The handler derives a coarse **virtual
tool name** of the form `gitea_request:<METHOD>:<top-path-segment>` (for
example `gitea_request:GET:repos` or `gitea_request:DELETE:repos`) and runs
it back through the policy engine with the parsed repository, target path, and
a `is_write` flag (`true` for any method other than GET/HEAD). This reuses the
existing write-mode + write-whitelist enforcement and lets `policy.yaml` allow
or deny raw dispatch per method and per top-level path segment.
Because the policy engine matches tool names by **exact set membership** (only
`paths` use globbing), the virtual name is deliberately coarse and stable.
### Example: lock raw dispatch to reads
```yaml
tools:
deny:
- gitea_request:POST:repos
- gitea_request:PUT:repos
- gitea_request:PATCH:repos
- gitea_request:DELETE:repos
```
## Sensitive-path denylist
Independently of `policy.yaml`, the handler blocks endpoints that touch an
admin or credential surface **for every method, including GET** (a GET on these
already leaks credentials or privileged configuration):
- `/admin`
- `*tokens*`
- `*secrets*`
- `*hooks*`
- `*keys*` (and `*gpg_keys*`)
- `applications/oauth2`
- `actions/runners/registration-token`
This denylist lives in the handler and **cannot be re-opened from
`policy.yaml`.** It is overridden only by setting `RAW_API_ALLOW_SENSITIVE=true`.
## Configuration
| Variable | Default | Notes |
|----------|---------|-------|
| `RAW_API_ENABLED` | `true` | Killswitch. When `false`, `gitea_request` refuses every dispatch with a `403`. |
| `RAW_API_ALLOW_SENSITIVE` | `false` | When `true`, the admin/credential denylist is bypassed. Leave `false` unless you fully understand the exposure. |
## Security warning
> With `WRITE_MODE=true`, the **write whitelist is the only brake** on
> `POST`/`PUT`/`PATCH`/`DELETE` across the *entire* Gitea API surface reachable
> by `gitea_request`. Any write method against a whitelisted repository will be
> attempted. Keep the whitelist tight, prefer denying the write virtual tool
> names in `policy.yaml`, and keep `RAW_API_ALLOW_SENSITIVE=false`.
## Behavioral notes and edge cases
- **Full URL supplied instead of a path:** only the path is used; the host and
query string are discarded (`query` carries query parameters).
- **Path traversal (`..`):** rejected during argument validation (`400`).
- **Unknown / non-HTTP method:** rejected during argument validation, before any
network call.
- **Cross-repo endpoints** such as `/repos/search` and `/repos/issues/search`
are intentionally *not* treated as repository-scoped, so `repository` is
`null` for them.
- **Non-repository writes** such as `POST /user/repos` or `POST /orgs` are denied
with *"write operation requires a repository target"*. This is the secure
default — the per-user permission model is repository-scoped, so there is no
repository against which to verify the write. This behavior is intentional and
is not worked around.
- **Service-PAT mode:** non-repository endpoints (for example `GET /user/orgs`)
are denied by the central gate because per-user permission can only be verified
against a repository target. Use the dedicated tools for those, or run in
OAuth-only mode.
+15
View File
@@ -4,5 +4,20 @@ defaults:
tools: tools:
deny: [] deny: []
# The generic `gitea_request` tool authorizes each call under a coarse virtual
# tool name of the form `gitea_request:<METHOD>:<top-path-segment>`, e.g.
# `gitea_request:GET:repos` or `gitea_request:DELETE:repos`. To keep raw
# dispatch read-only while still allowing GETs, deny the write methods here:
#
# deny:
# - gitea_request:POST:repos
# - gitea_request:PUT:repos
# - gitea_request:PATCH:repos
# - gitea_request:DELETE:repos
#
# NOTE: The admin/credential denylist (/admin, *tokens*, *secrets*, *hooks*,
# *keys*, applications/oauth2, runner registration tokens) is enforced in the
# handler independently of this file and is NOT configured here. It can only be
# overridden by setting RAW_API_ALLOW_SENSITIVE=true.
repositories: {} repositories: {}