Dev #72
No files matched your search
@@ -19,7 +19,11 @@ Full access by default (ADR 0001). The server does not decide what is safe; the
|
||||
owner restricts exposure with `READ_ONLY`, `TOOLSETS`, `DISABLED_TOOLS` and the
|
||||
scope of the Gitea token. Everything else below is load-bearing:
|
||||
|
||||
- Every tool call produces one audit event in the hash-chained, append-only log.
|
||||
- Every tool call is audited in the hash-chained, append-only log with a write-ahead
|
||||
model: a `tool_call_started` entry is written before the call executes (the call is
|
||||
refused with `audit_unavailable` when that entry cannot be written), and a
|
||||
`tool_invocation` entry with the outcome is written after it. Anything that counts
|
||||
calls in the log counts `tool_invocation` entries.
|
||||
- No secrets or tokens in logs, errors, audit entries or results. Output is
|
||||
passed through secret masking (`SECRET_DETECTION_MODE`, default `mask`).
|
||||
`Identity.token` and every `SecretStr` setting never appear in `repr`.
|
||||
@@ -62,18 +66,21 @@ src/aegis_gitea_mcp/
|
||||
errors.py ToolError, GiteaAPIError(status, kind, message)
|
||||
audit.py hash-chained audit log; security.py secret masking;
|
||||
response_limits.py, rate_limit.py (HTTP), request_context.py, logging_utils.py
|
||||
server.py, transports, oauth/ planned (chunks B4, B5), not present yet
|
||||
scripts/ generate_catalog.py, validate_audit_log.py, ci/, check_docs.py (B11)
|
||||
server.py the low-level SDK Server: tool list, call dispatcher, audit calls
|
||||
transports.py stdio runner and the streamable HTTP app
|
||||
auth.py static bearer verification and identity resolution
|
||||
oauth/ per-user OAuth: provider, resolver, encrypted store, upstream, callback
|
||||
scripts/ generate_catalog.py, validate_audit_log.py, check_docs.py, ci/
|
||||
spec/ pinned Gitea swagger
|
||||
tests/integration/ live Gitea in Docker (harness.py), opt-in
|
||||
tests/integration/ live Gitea in Docker (harness.py), opt-in; sweep of every tool
|
||||
tests/mcp/ MCP smoke tests (stdio, HTTP, container image), opt-in
|
||||
tests/security/ security regression suite
|
||||
```
|
||||
|
||||
Modules marked planned do not exist yet. Request flow (target):
|
||||
client -> SDK transport and auth verifier -> `Identity` -> toolset/read-only
|
||||
filters -> schema validation -> executor (Gitea call with the identity's token,
|
||||
optional `sudo`) -> limits and masking -> result envelope (ADR 0005) -> audit.
|
||||
The executor and the catalog exist; the server and transports that connect them
|
||||
to MCP clients do not yet.
|
||||
Request flow: client -> SDK transport and auth verifier -> `Identity` ->
|
||||
toolset/read-only filters -> schema validation -> `tool_call_started` audit entry
|
||||
-> executor (Gitea call with the identity's token, optional `sudo`) -> limits and
|
||||
masking -> result envelope (ADR 0005) -> `tool_invocation` audit entry.
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -87,13 +94,14 @@ make lint | format | test | test-integration | run | validate-audit | docker-bui
|
||||
```
|
||||
|
||||
Live-Gitea integration tests carry the `integration` marker and run only with
|
||||
`AEGIS_INTEGRATION=1` (`make test-integration`).
|
||||
`AEGIS_INTEGRATION=1` (`make test-integration`); the MCP smoke tests under
|
||||
`tests/mcp` carry `mcp_smoke`. `tests/integration/sweep_baseline.json` pins the
|
||||
expected outcome of every tool, see `tests/integration/README.md`.
|
||||
|
||||
## Quality gates
|
||||
|
||||
Ruff (E,W,F,I,B,C4,UP,S,SIM,RUF), ruff format, mypy and pytest must be green
|
||||
before every commit. Coverage threshold is 80% until the final chunk and 90%
|
||||
from then on; never lower it. Tests must not write outside `tmp_path`
|
||||
before every commit. The coverage threshold is 90%; never lower it. Tests must not write outside `tmp_path`
|
||||
(the autouse fixture in `tests/conftest.py` isolates env, cwd, `DATA_DIR`,
|
||||
`AUDIT_LOG_PATH` and singletons). Python 3.10+, line length 100, public
|
||||
functions need docstrings and type hints. Documentation lives under `docs/`;
|
||||
|
||||
@@ -12,12 +12,9 @@ HTTP, using the official `mcp` SDK v2 low-level `Server`. Full access by default
|
||||
and the Gitea token scope. Auth modes: admin-token (`GITEA_TOKEN` / static
|
||||
`MCP_BEARER_TOKEN`) and per-user OAuth on HTTP (user's own Gitea token).
|
||||
|
||||
The project is mid-rebuild (chunk plan in `docs/audit.md`). Built so far: tooling,
|
||||
`config.py`, `identity.py`, `errors.py`, the generated catalog (`catalog/`), the
|
||||
Gitea executor (`gitea/`), the setup command (`setup_cmd.py`, `setup_snippets.py`,
|
||||
`setup_template.py`), the integration harness, CI and audit/security/limits
|
||||
helpers. Planned: server and transports (B4), OAuth (B5), smoke tests (B9),
|
||||
docs (B11).
|
||||
The rebuild is complete: catalog, executor, server, both transports, per-user OAuth,
|
||||
setup command, CI, security suite, MCP smoke tests and the live integration suite are in.
|
||||
The remaining open items are tracked in `docs/roadmap.md`.
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -27,7 +24,7 @@ make lint # ruff check, ruff format --check, mypy src
|
||||
make format # ruff format + ruff check --fix
|
||||
make test # uv run pytest (coverage gate)
|
||||
make test-integration # AEGIS_INTEGRATION=1, needs Docker
|
||||
make run # uv run aegis-gitea-mcp serve (not implemented until B4)
|
||||
make run # uv run aegis-gitea-mcp serve
|
||||
uv run python scripts/generate_catalog.py --check # catalog matches the spec
|
||||
make validate-audit # verify audit hash chain
|
||||
uv run pytest tests/test_config.py::test_defaults -v # single test
|
||||
@@ -44,11 +41,12 @@ uv run pytest tests/test_config.py::test_defaults -v # single test
|
||||
upload building, `SLASH_ROUTES`), `pagination.py`, `redaction.py`.
|
||||
- `setup_cmd.py`, `setup_snippets.py`, `setup_template.py` (done): the `setup`
|
||||
command, its client snippets and the built-in `.env` template.
|
||||
- Server (B4, planned): low-level SDK `Server` with one `on_list_tools` and one
|
||||
- `server.py` (done): low-level SDK `Server` with one `on_list_tools` and one
|
||||
`on_call_tool` dispatcher, jsonschema validation, audit, `is_error` results for
|
||||
tool failures, `gitea_api_request` escape-hatch tool.
|
||||
- Auth (B5, planned): static bearer verifier and OAuth provider with encrypted
|
||||
token store.
|
||||
tool failures, `gitea_api_request` escape-hatch tool. `transports.py`: stdio
|
||||
runner and streamable HTTP app.
|
||||
- `auth.py` and `oauth/` (done): static bearer verifier and the OAuth provider
|
||||
with encrypted token store.
|
||||
- Core helpers (done): `config.py` (`Settings`, `get_settings()`,
|
||||
`reset_settings()`), `identity.py`, `errors.py` (`ToolError`,
|
||||
`GiteaAPIError`), `audit.py`, `security.py`, `response_limits.py`,
|
||||
@@ -61,7 +59,9 @@ points `DATA_DIR`/`AUDIT_LOG_PATH` into `tmp_path`.
|
||||
|
||||
## Invariants
|
||||
|
||||
Kept: audit event per tool call; no secrets in logs/errors/results; no stack
|
||||
Kept: write-ahead audit (a `tool_call_started` entry before the call runs, the
|
||||
call is refused when it cannot be written, a `tool_invocation` entry with the
|
||||
outcome after it; count calls by `tool_invocation`); no secrets in logs/errors/results; no stack
|
||||
traces by default; `127.0.0.1` default bind and fail-closed HTTP auth off
|
||||
loopback; untrusted-content labelling; schema validation with no extra
|
||||
properties; response size bounds; validated `GITEA_URL` and strict
|
||||
@@ -73,7 +73,7 @@ Details in `AGENTS.md`.
|
||||
|
||||
Python 3.10+, line length 100, ruff (E,W,F,I,B,C4,UP,S,SIM,RUF) plus ruff format,
|
||||
strict-ish mypy (`disallow_untyped_defs`, relaxed in tests). Public functions need
|
||||
docstrings and type hints. Coverage 80% now, 90% from the final chunk. All
|
||||
docstrings and type hints. Coverage gate 90%. All
|
||||
documentation goes under `docs/`; security-impacting changes update docs in the
|
||||
same changeset. No em-dashes in text.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ GiteaExecutor (gitea/executor.py)
|
||||
Gitea REST API (/api/v1)
|
||||
```
|
||||
|
||||
Every tool call ends in one audit event, and every result is wrapped in the same envelope.
|
||||
Every tool call is audited (a write-ahead `tool_call_started` entry, then one `tool_invocation` entry with the outcome), and every result is wrapped in the same envelope.
|
||||
|
||||
## Catalog
|
||||
|
||||
|
||||
+2
-2
@@ -26,12 +26,12 @@ because credentials belong in the identity, not in the URL.
|
||||
## Behaviour
|
||||
|
||||
- It goes through the same executor as every generated tool: the identity's token, no redirects,
|
||||
response limits, redaction and secret masking, the result envelope and one audit event.
|
||||
response limits, redaction and secret masking, the result envelope and the same audit entries (`tool_call_started`, then `tool_invocation`).
|
||||
- There is no allow list of paths and no write classification: with full access, the token's
|
||||
scopes and the identity's Gitea permissions are the limit.
|
||||
- `READ_ONLY=true` keeps the tool listed but it accepts only `GET`.
|
||||
- `TOOLSETS` without `misc`, or `DISABLED_TOOLS=gitea_api_request`, removes it.
|
||||
- The audit event records the method and the path, so it is visible which endpoint was called.
|
||||
- The audit entry records the method and the path, so it is visible which endpoint was called.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Troubleshooting
|
||||
|
||||
Start with the result of the failing call: errors carry `error.status`, `error.kind` and
|
||||
Gitea's sanitized message, and the audit log has one event per call (see
|
||||
Gitea's sanitized message, and the audit log has a result entry (`tool_invocation`) per call, preceded by a `tool_call_started` entry (see
|
||||
[audit-logging.md](audit-logging.md)).
|
||||
|
||||
## The server does not start
|
||||
|
||||
Reference in new issue
Block a user