Commit Graph
51 Commits
Author SHA1 Message Date
Latte 6b11d29919 fix: annotate POST render endpoints read-only and irreversible POSTs destructive 2026-10-14 16:09:59 +02:00
Latte 156e963e21 fix: key slash encoding on route, redact more request secrets, bound JSON depth and uploads
Slash safety is decided per (path template, parameter) so single-segment
routes such as releases/tags/{tag} and editorconfig/{filepath} keep %2F.
Adds authorization_header, remote_password, mirror_password, mirror_token and
the AWS key fields to the redacted request fields. Deeply nested JSON from
Gitea is treated as text instead of raising RecursionError, and base64
uploads are capped before decoding.
2026-10-14 16:08:42 +02:00
Latte 9e50859133 feat: add Gitea request executor with result envelope, pagination and redaction 2026-10-14 15:55:56 +02:00
Latte 041b3ea7ff feat: generate the tool catalog from the pinned swagger
Adds scripts/generate_catalog.py (with --check), the naming rules, schema conversion, the committed tools.json for all 537 operations and the load_catalog() loader.
2026-10-14 15:51:01 +02:00
Latte 1d223e6799 feat: add the aegis-gitea-mcp setup command
Verifies the Gitea instance, token and scopes, registers or reuses the aegis-gitea-mcp OAuth app, generates missing secrets and writes a 0600 env file from the template. Prints client configs and deployment snippets without any secret value.
2026-10-14 15:50:02 +02:00
Latte b82e2866cb feat: new settings, CLI skeleton and interface skeletons
Adds the pydantic-settings Settings with validated GITEA_URL, OAuth scopes
and toolsets, the argparse CLI skeleton, catalog.model, identity and
GiteaAPIError. Test fixtures now isolate env, cwd and state paths in tmp.
2026-10-14 15:40:37 +02:00
Latte 4ac9626eb5 refactor!: remove legacy adapters, policy engine and API-key auth
Removes the FastAPI server, hand-rolled MCP protocol, tool registry and
tools, YAML policy, resource-type authz, API-key and OAuth proxy modules,
automation, observability, cache, the old Gitea client and stdio adapter,
the key-management scripts and their tests (ADR 0006).
2026-10-14 15:40:37 +02:00
Latte 5d4a98d06e feat(stdio): harden local MCP transport and add end-to-end tests
Reserve stdout for the JSON-RPC stream: _configure_stderr_logging() pins all
logging to stderr (and rewrites any stray stdout handler) so a log line can
never corrupt the stdio protocol. Extract a pure, testable build_server() from
_serve(). Add end-to-end tests over the mcp in-memory transport (initialize +
tools/list + tools/call), covering a successful round trip and a policy denial
surfaced as an MCP error.
2026-06-27 15:19:42 +02:00
LatteandClaude Opus 4.8 2859a7f917 style: apply ruff/black formatting to stdio_app
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:10:14 +02:00
LatteandClaude Opus 4.8 3392d8f69b feat(security): resource-type-aware authorization with fail-closed defaults
Add aegis_gitea_mcp.authz: classify every dispatched call (typed tools and
gitea_request) by resource type (repository/org/user_self/user_owned/
misc_global/admin/unknown) and enforce a type-specific rule in service-PAT
mode, on top of policy + WRITE_MODE. Every decision fails closed:

- org: signed-in user must be a verified org member (Gitea-checked).
- user_owned: owner must be the caller or a member org of the caller.
- user_self: token-owner-scoped endpoints denied (token is the bot's).
- admin: default-deny; allowed only with RAW_API_ALLOW_SENSITIVE opt-in AND a
  verified site admin.
- misc_global: reads allowed, writes denied.
- unknown / unverifiable: denied and audited.

Wire it into the server's service-PAT dispatch: repository calls keep the
existing per-user collaborator check; non-repo calls (previously blanket-denied)
now go through the resource-type gate, opening the org/user/admin surface
safely. Verification results are cached briefly (fail-closed: positives only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:09:50 +02:00
LatteandClaude Opus 4.8 2d7f12d0d0 feat: safe full-API coverage via classified gitea_request dispatch
Add a deterministic (method, path) read/write classifier with an explicit
render-only override table that can only downgrade provably side-effect-free
POSTs (markdown/markup) to reads, never the reverse — so a mutating call cannot
slip past the write-mode gate. Add a known-Gitea-prefix gate: gitea_request now
fails closed on any path whose top segment is not a recognized /api/v1 route
instead of passing unknown paths through. Expose raw_relative_segments for the
authorization layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:09:30 +02:00
LatteandClaude Opus 4.8 8902c4f642 feat: add local stdio adapter and uv-installable package with extras
Add aegis_gitea_mcp.stdio_app: a single-user, local MCP server over stdio
(official mcp SDK) that serves the same tools from the shared registry,
resolves the PAT owner via GET /user and pins request context to it, and runs
policy + WRITE_MODE + secret sanitization + audit while skipping the per-user
repo probe (the operator is the trusted token owner). Audit log falls back to a
per-user state path when the container default is unwritable.

Packaging: split deps into core (httpx/pydantic/mcp/...) and a [server] extra
(fastapi/uvicorn/PyJWT/python-multipart); add console scripts aegis-gitea-mcp
(stdio) and aegis-gitea-mcp-server (guarded HTTP entry); bump to 0.2.0 and fix
repo URLs. mcp added to requirements for CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:56:16 +02:00
LatteandClaude Opus 4.8 7da0c46de8 refactor: extract transport-agnostic core and shared tool registry
Introduce aegis_gitea_mcp.registry as the single name->handler source of
truth consumed by every transport adapter, moving TOOL_HANDLERS out of the
FastAPI server module. Add aegis_gitea_mcp.errors.ToolError so core handlers
no longer import fastapi.HTTPException; raw_tools now raises ToolError and the
HTTP adapter maps it back to HTTPException, preserving status codes and audit
behavior. Add a subprocess boundary test asserting the core imports without
pulling in fastapi/uvicorn/starlette.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:49:46 +02:00
LatteandClaude Opus 4.8 8e41fd12af feat(raw-api): register gitea_request tool and wire server dispatch
Registers gitea_request in AVAILABLE_TOOLS with write_operation=False
(deliberate: a static flag cannot describe a read-or-write tool; the handler
authorizes writes per-method) and maps the tool name to raw_api_request_tool in
the server handler registry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:26:56 +02:00
LatteandClaude Opus 4.8 2844c42ec8 feat(raw-api): add gitea_request schema, path parsing, client dispatch and handler
Adds the RawApiRequestArgs schema (extra=forbid), raw path normalization/
parsing helpers, a GiteaClient.raw_request that audits method+path only (never
the body), and the raw_api_request_tool handler. The handler derives a coarse
virtual tool name (gitea_request:METHOD:topsegment) plus repository/target_path
from the path and runs them back through the policy engine, enforces an
admin/credential sensitive-path denylist, and bounds responses. Two config
flags gate it: RAW_API_ENABLED (killswitch) and RAW_API_ALLOW_SENSITIVE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:26:26 +02:00
LatteandClaude Opus 4.8 41749fd7b4 fix: harden get_issue parsing and surface real errors (#27); align CI image publish
get_issue raised 'NoneType' object is not iterable on issues whose
labels/assignees Gitea returns as null or with non-dict elements (the #13
class), which reached clients as an opaque JSON-RPC -32603 with no detail.

- read_tools: skip non-dict label/assignee entries in get_issue_tool
- server: detect a wrapped GiteaNotFoundError via the __cause__ chain and
  return 404 / JSON-RPC -32000 with a clear message; include the exception
  type name in masked internal errors so future masked failures are
  diagnosable without exposing messages or stack traces
- tests: cover non-dict collection elements and the not-found / typed-error
  responses
- ci: rewrite docker.yml to build, smoke-test and push the image to the
  Gitea container registry on merge to main/dev, matching the hiddenden.cafe
  pattern (only REGISTRY_TOKEN required)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:51:58 +02:00
LatteandClaude Opus 4.8 e08ba42697 feat: assign issues to milestones on create/update (#22)
lint / lint (pull_request) Successful in 35s
test / test (pull_request) Successful in 35s
docker / docker-test (pull_request) Successful in 8s
test / test (push) Successful in 23s
lint / lint (push) Successful in 23s
docker / test (pull_request) Successful in 29s
docker / lint (pull_request) Successful in 35s
docker / docker-publish (pull_request) Has been skipped
Add a `milestone` argument to `create_issue` and `update_issue` accepting
either a numeric milestone id or a title (resolved case-insensitively against
open and closed milestones, with a clear error for unknown titles). On
`update_issue`, `milestone: 0` clears the milestone. A BeforeValidator rejects
booleans so they are not silently coerced to an id.

Gitea Projects (Kanban boards) were investigated for #22 and are intentionally
left unsupported: Gitea 1.26.2 exposes no project endpoints in its REST API.
Documented this in api-reference.md and refreshed the (stale) write-mode tool
list to cover all 16 write tools.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:36:01 +02:00
Latte f53e1a3a5a feat: add structured logging helpers and instrument get_issue (#14)
docker / test (pull_request) Successful in 29s
test / test (push) Successful in 38s
docker / lint (pull_request) Successful in 39s
lint / lint (push) Successful in 39s
docker / docker-test (pull_request) Successful in 12s
docker / docker-publish (pull_request) Has been skipped
lint / lint (pull_request) Successful in 28s
test / test (pull_request) Successful in 22s
Adds reusable, secret-safe logging helpers to `logging_utils`:
- `log_event(logger, level, event, **context)` emits a named event with a
  sanitized `context` mapping (sensitive keys masked as `***`).
- `log_nullable_field(...)` records whether a parsed field is None plus its
  runtime type, without dumping its contents.
- `sanitize_context(...)` is the shared masking primitive.

The JSON formatter now serializes a record's `context` into the payload.

`get_issue_tool` is instrumented at DEBUG (`get_issue.start`,
`get_issue.payload_shape`, `get_issue.field_check` for labels/assignees/user)
so the nullable-field parsing that caused #13 is diagnosable going forward.

Adds tests for the helpers, the formatter, and the get_issue instrumentation,
and documents the pattern in docs/observability.md.
2026-06-22 15:40:36 +02:00
Latte 478aee9bed fix: tolerate null labels/assignees/user in get_issue (#13)
docker / test (pull_request) Successful in 32s
docker / lint (pull_request) Successful in 39s
lint / lint (pull_request) Successful in 34s
test / test (pull_request) Successful in 32s
docker / docker-publish (pull_request) Has been skipped
test / test (push) Successful in 25s
lint / lint (push) Successful in 27s
docker / docker-test (pull_request) Successful in 11s
Gitea may return JSON null for an issue's `labels`, `assignees`, or
`user` fields. `dict.get(key, [])` returns None when the key is present
with a null value (the default is only used for missing keys), so the
list comprehensions raised `'NoneType' object is not iterable` for
otherwise-valid issues. Coalesce with `or []` / `or {}` so empty/null
collections normalize to empty results.

Adds a regression test covering all three null fields.
2026-06-22 10:43:43 +02:00
LatteandClaude Opus 4.8 b62ed098bf feat: add 13 read tools (PR files/commits, comments, branches, releases, milestones, org/status/languages/topics)
test / test (push) Successful in 1m13s
lint / lint (push) Successful in 1m14s
docker / docker-publish (pull_request) Has been skipped
docker / test (pull_request) Successful in 22s
docker / lint (pull_request) Successful in 29s
lint / lint (pull_request) Successful in 31s
test / test (pull_request) Successful in 21s
docker / docker-test (pull_request) Successful in 23s
Expands the read surface so the MCP can inspect more of Gitea:

- list_pull_request_files, list_pull_request_commits, list_issue_comments
- list_branches, get_branch
- get_release, get_latest_release, list_milestones
- get_commit_status
- list_org_repositories, list_organizations
- get_repo_languages, list_repo_topics

Each: arg schema (extra=forbid; GitRef on branch/sha fields), Gitea client
method with url-encoded path segments, bounded handler, MCP registration
(read-only), server wiring, docs, and parametrized success tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:43:03 +02:00
LatteandClaude Opus 4.8 7837ff43ad feat: add PR/release/branch/milestone/comment write tools
Adds six opt-in write tools (write-mode + policy + per-user permission still
enforced; no destructive or admin actions):

- create_pull_request (POST /pulls)
- create_release / edit_release (POST/PATCH /releases)
- create_branch (POST /branches; create only, no deletion)
- create_milestone (POST /milestones)
- edit_issue_comment (PATCH /issues/comments/{id})

Each: arg schema (extra=forbid, GitRef on branch/ref-like fields), Gitea client
method with url-encoded path segments, handler that surfaces auth errors, MCP
registration (write_operation=True), server wiring, docs, and success tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:38:25 +02:00
LatteandClaude Opus 4.8 c282ffe359 feat: complete label management (name->id resolution, update/remove)
Resolves the long-standing problem that label tools passed names while Gitea's
API requires numeric label ids.

- gitea_client: add _resolve_label_ids() helper; create_issue and add_labels now
  resolve label names to ids (case-insensitive) and raise a clear "Unknown
  label(s)" error instead of a generic 500.
- New tools: remove_labels (by name) and update_label (located by current name).
- Register both write tools and document the name-based label contract.
- Tests: resolver mapping + unknown-label error, add_labels id translation,
  update_label and remove_labels handlers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:34:35 +02:00
LatteandClaude Opus 4.8 f0db219ee8 feat: add create_label write tool
Adds a create_label write-mode tool so labels can be created in a repository
through the MCP server (previously there was no way to define labels, which
blocked attaching labels to issues). Follows the full tool checklist:

- arguments.py: CreateLabelArgs (name, hex color, optional description/exclusive),
  with extra=forbid and a hex-color pattern.
- gitea_client.py: create_label() POSTing to /repos/{owner}/{repo}/labels with
  url-encoded path segments.
- write_tools.py: create_label_tool handler; normalizes the color to a leading
  '#', bounds text output, and lets auth/authz errors surface.
- mcp_protocol.py: register create_label (write_operation=True).
- server.py: wire create_label into TOOL_HANDLERS.
- docs/api-reference.md: document create_label.
- tests: success path, color normalization, and invalid-color rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:24:33 +02:00
LatteandClaude Opus 4.8 e873d0325b feat: scope list_repositories to the authenticated user in service-PAT mode
docker / test (push) Successful in 28s
docker / lint (push) Successful in 33s
lint / lint (push) Successful in 35s
test / test (push) Successful in 33s
docker / docker-test (push) Successful in 10s
docker / docker-publish (push) Successful in 6s
Previously list_repositories was blocked in service-PAT mode because it has no
repository target for the per-user permission check, so users could not list
their repositories at all (the connector surfaced a generic error).

list_repositories now returns only the repositories the signed-in user owns or
contributes to, instead of everything the bot token can see:
- gitea_client.py: add list_user_repositories(login) — resolves the user id and
  queries /api/v1/repos/search with the uid filter.
- repository.py: list_repositories_tool uses the user-scoped path when a service
  PAT is configured and a user login is present; pure-OAuth mode still uses the
  user's own /user/repos.
- server.py: allow list_repositories through the service-PAT guard (it is scoped
  to the user in the handler); all other tools still require a repository target.
- README.md: document the new user-scoped behavior and its visibility caveat.

Tests: user-scoped client method (uid resolution + unknown user), PAT-mode tool
scoping, and conftest now clears the request context between tests to prevent
contextvar login leakage across files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:07:19 +02:00
LatteandClaude Opus 4.8 624a3c79ee fix: surface Gitea auth errors and document the service PAT
docker / test (push) Successful in 25s
test / test (push) Successful in 32s
lint / lint (push) Successful in 33s
docker / docker-publish (push) Successful in 6s
docker / lint (push) Successful in 30s
docker / docker-test (push) Successful in 10s
Two related issues made the connected MCP server return a bare "Internal
server error" for tools that need real Gitea API access (e.g.
list_repositories), while public-repo-by-path reads worked:

1. Gitea OIDC access tokens only carry openid/profile/email and cannot call
   the repository REST API, so pure-OAuth mode fails for most tools. A service
   PAT (GITEA_TOKEN) is required in practice; per-user permission is still
   enforced before each call, so this does not weaken authorization.
2. The tool handlers caught GiteaError broadly and re-raised it as RuntimeError.
   Because GiteaAuthenticationError/GiteaAuthorizationError subclass GiteaError,
   a clean 401/403 was masked as a generic internal error and the server's
   re-authorization guidance never fired.

Changes:
- read_tools.py / repository.py / write_tools.py: re-raise the auth/authz
  subclasses before the broad GiteaError catch so server.py returns actionable
  guidance instead of a generic 500.
- .env.example + README.md: document GITEA_TOKEN as a least-privilege bot PAT,
  explain why it's needed and that OAuth remains authoritative, and note that
  list_repositories is intentionally unavailable in service-PAT mode.
- tests: assert tool handlers propagate auth errors unwrapped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:47:10 +02:00
LatteandClaude Opus 4.8 b1bc726a95 fix: keep OAuth flow working on read-only container roots
docker / test (push) Successful in 29s
docker / lint (push) Successful in 34s
test / test (push) Successful in 32s
lint / lint (push) Successful in 36s
docker / docker-test (push) Successful in 8s
docker / docker-publish (push) Successful in 6s
The DCR client registry created its storage directory eagerly in __init__,
and DCR_STORAGE_PATH defaulted to /var/lib/aegis-mcp — a path that is neither
created in the image nor mounted as a writable volume. Under the hardened
read-only docker-compose, every /oauth/authorize, /oauth/token, and /register
call hit `mkdir('/var/lib/aegis-mcp')` on a read-only filesystem, raising an
unhandled OSError and returning a bare "Internal Server Error" during login.

- oauth_flow.py: defer the storage-dir mkdir from __init__ to _persist (the
  only write path). authorize/token only read the registry, so they no longer
  require a writable filesystem and stop 500-ing.
- docker/Dockerfile: create and chown /var/lib/aegis-mcp.
- docker-compose.yml + docker/docker-compose.yml: add a persistent
  aegis-mcp-data volume mounted at /var/lib/aegis-mcp so DCR registrations
  survive restarts.
- .env.example: document DCR_STORAGE_PATH and set PUBLIC_BASE_URL to the real
  MCP host.
- README.md: spell out exact values (Gitea host, MCP host, callback URL, MCP
  URL) and add a "required writable volumes" section explaining the cause of
  the login 500.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:28:52 +02:00
LatteandClaude Opus 4.8 2d95e89035 fix: prevent path traversal via Gitea ref/sha/base/head parameters
test / test (push) Successful in 20s
lint / lint (push) Successful in 22s
docker / lint (pull_request) Successful in 33s
docker / test (pull_request) Successful in 25s
test / test (pull_request) Successful in 38s
lint / lint (pull_request) Successful in 40s
docker / docker-test (pull_request) Successful in 15s
docker / docker-publish (pull_request) Has been skipped
The ref-like tool arguments (ref, sha, base, head) were only length-limited
and were interpolated unencoded into Gitea API URL paths (get_tree,
get_commit_diff, compare_refs). Because httpx collapses ".." path segments
(RFC 3986), a crafted value such as "../../../../owner/repo/contents/secret"
escaped the declared owner/repo prefix. In service-PAT mode this allowed a
user authorized on one repository to read arbitrary repositories the service
token could reach, and in OAuth mode it bypassed the policy engine's
per-repository rules (which never see ref values).

Two defense layers:
- arguments.py: add _validate_git_ref / GitRef that rejects ".." path
  segments, leading "/", backslashes, null bytes, control chars, whitespace,
  and "?"/"#", while preserving legitimate slash refs (feature/foo, v1.2.3).
  This is what actually closes the traversal.
- gitea_client.py: defense-in-depth urllib.parse.quote() on owner/repo
  (safe="") and ref/sha/base/head/filepath (safe="/") in every repo URL
  builder, mirroring the existing pattern in server.py.

Tests: negative cases for traversal/unsafe chars across all four fields,
positive cases for slash-containing refs, length-bound regression, and a
URL-layer confinement check. Full suite green (176 passed), coverage 85.64%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:57:52 +02:00
LatteandClaude Sonnet 4.6 b8217dce8a feat: harden OAuth state secret validation, DCR file permissions, and policy defaults
docker / test (pull_request) Successful in 24s
lint / lint (pull_request) Successful in 37s
lint / lint (push) Successful in 1m26s
test / test (push) Successful in 1m40s
test / test (pull_request) Successful in 34s
docker / lint (pull_request) Successful in 1m59s
docker / docker-test (pull_request) Successful in 14s
docker / docker-publish (pull_request) Has been skipped
- Enforce 32-char minimum on OAUTH_STATE_SECRET at startup (config.py)
- Write DCR client registry with owner-only (0o600) permissions before atomic replace
- Flip policy.yaml default write action from allow → deny
- Add CLAUDE.md with architecture, commands, and AGENTS.md contract summary
- Add .pre-commit-config.yaml mirroring `make lint` checks
- Update .gitignore: add .venv, .claude, .mypy_cache, .ruff_cache, .coverage.*
- Extend docs: audit log rotation guidance, OAUTH_STATE_SECRET and DCR_STORAGE_PATH notes
- Tests: short-secret rejection, 32-char acceptance, POSIX permission check for DCR store

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 14:13:22 +02:00
Latte 541124e92a feat: harden Claude MCP OAuth transport 2026-06-13 21:05:11 +02:00
LatteandClaude Opus 4.8 ed3130ef74 refactor: lifespan handlers, module-level imports, bounded scope cache
Replace deprecated @app.on_event startup/shutdown handlers with a FastAPI
lifespan context manager, move the inline hashlib/time imports in the auth
middleware to module top, and back the unbounded _api_scope_cache with a new
size- and TTL-bounded BoundedTTLCache utility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 18:39:06 +02:00
LatteandClaude Sonnet 4.6 71c993e4cd Use GITEA_TOKEN as service PAT for API calls in OAuth mode
docker / lint (push) Failing after 21s
docker / test (push) Failing after 17s
lint / lint (push) Failing after 22s
test / test (push) Failing after 17s
docker / docker-test (push) Has been skipped
docker / docker-publish (push) Has been skipped
Gitea OIDC access_tokens only carry OIDC scopes and cannot call the
Gitea REST API. Fall back to GITEA_TOKEN (service PAT) for actual tool
execution when configured, while OIDC still handles user identity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 17:06:28 +00:00
Latte bf35a0c712 Enhance OAuth metadata endpoints and update authorization server URLs in responses
test / test (push) Failing after 19s
docker / lint (pull_request) Failing after 21s
lint / lint (pull_request) Failing after 21s
lint / lint (push) Failing after 1m29s
docker / test (pull_request) Failing after 16s
test / test (pull_request) Failing after 18s
docker / docker-test (pull_request) Has been skipped
docker / docker-publish (pull_request) Has been skipped
2026-03-04 16:54:36 +00:00
Latte 2f9750dcce Return explicit error for tokens lacking scopes
lint / lint (push) Successful in 24s
test / test (push) Successful in 19s
docker / lint (pull_request) Successful in 23s
docker / test (pull_request) Successful in 18s
lint / lint (pull_request) Successful in 24s
test / test (pull_request) Successful in 20s
docker / docker-test (pull_request) Successful in 39s
docker / docker-publish (pull_request) Has been skipped
2026-02-27 19:55:01 +01:00
Latte fc93b8d29e Fix Prometheus metric f-string and add YAML helper
lint / lint (push) Successful in 23s
test / test (push) Failing after 20s
2026-02-27 16:08:17 +01:00
Latte c79cc1ab9e Add PUBLIC_BASE_URL and refine OAuth scopes
docker / lint (push) Has been cancelled
docker / test (push) Has been cancelled
docker / docker-build (push) Has been cancelled
lint / lint (push) Has been cancelled
test / test (push) Has been cancelled
2026-02-25 20:49:08 +01:00
Latte 59e1ea53a8 Add OAuth2/OIDC per-user Gitea authentication
docker / lint (push) Has been cancelled
docker / test (push) Has been cancelled
docker / docker-build (push) Has been cancelled
lint / lint (push) Has been cancelled
test / test (push) Has been cancelled
Introduce a GiteaOAuthValidator for JWT and userinfo validation and
fallbacks, add /oauth/token proxy, and thread per-user tokens through
the
request context and automation paths. Update config and .env.example for
OAuth-first mode, add OpenAPI, extensive unit/integration tests,
GitHub/Gitea CI workflows, docs, and lint/test enforcement (>=80% cov).
2026-02-25 16:54:01 +01:00
Latte a00b6a0ba2 update 2026-02-14 18:18:34 +01:00
Latte ecc87cbb65 quick fix 2026-02-14 17:18:30 +01:00
Latte 8504a95a11 feat: add opt-in write access for all token-visible repos 2026-02-14 16:35:03 +01:00
Latte 5969892af3 feat: harden gateway with policy engine, secure tools, and governance docs 2026-02-14 16:06:43 +01:00
Latte d82fe87113 update 2026-02-11 18:16:00 +01:00
Ubuntu dd7bbd1f9a IT WORKS 2026-01-31 16:03:17 +00:00
Ubuntu 3c71d5da0a update 2026-01-31 15:55:22 +00:00
Latte 833eb21c79 quick fix 2026-01-31 14:23:51 +01:00
Latte 0a2a21cc52 feat: scope query param auth to MCP endpoints
Restrict api_key query parameter to /mcp/tools, /mcp/tool/call,
and /mcp/sse only. Updated documentation to reflect query param
usage for ChatGPT UI without header support.
2026-01-29 21:07:37 +01:00
Latte b990c6c527 feat: allow api_key query parameter for ChatGPT UI
ChatGPT UI lacks custom header support for MCP servers. Added
query parameter fallback (?api_key=) alongside Authorization
header to authenticate requests.

Updated tests to cover query param authentication.
2026-01-29 21:03:05 +01:00
Latte de0ae09fc4 fix: update structlog configuration for compatibility
Removed deprecated make_filtering_bound_logger() call that was
causing TypeError. Using structlog.BoundLogger directly instead.
2026-01-29 20:44:17 +01:00
Latte a0605eaa27 fix: change mcp_api_keys to string field to avoid JSON parsing
Pydantic was trying to parse List[str] as JSON from env vars.
Changed to use a string field (mcp_api_keys_raw) and parse manually
in model_validator, then expose as property.

This fixes the JSONDecodeError when reading MCP_API_KEYS from .env
2026-01-29 20:30:28 +01:00
Latte 0945f560ff fix: use model_validator for API keys validation
Changed from field_validator to model_validator to properly access
auth_enabled field during validation. This fixes the SettingsError
when parsing mcp_api_keys from environment variables.

Also improved handling of empty strings and None values.
2026-01-29 20:27:36 +01:00
Latte eeaad748a6 feat: add API key authentication system for ChatGPT Business
Implements comprehensive Bearer token authentication to ensure only
authorized ChatGPT workspaces can access the MCP server.

Core Features:
- API key validation with constant-time comparison
- Multi-key support for rotation grace periods
- Rate limiting (5 failures per IP per 5 min)
- Comprehensive audit logging of all auth attempts
- IP-based failed attempt tracking

Key Management:
- generate_api_key.py: Create secure 64-char keys
- rotate_api_key.py: Guided key rotation with backup
- check_key_age.py: Automated expiration monitoring

Infrastructure:
- Traefik labels for HTTPS and rate limiting
- Security headers (HSTS, CSP, X-Frame-Options)
- Environment-based configuration
- Docker secrets support

Documentation:
- AUTH_SETUP.md: Complete authentication setup guide
- CHATGPT_SETUP.md: ChatGPT Business integration guide
- KEY_ROTATION.md: Key rotation procedures and automation

Security:
- Read-only operations enforced
- No write access to Gitea possible
- All auth attempts logged with correlation IDs
- Failed attempts trigger IP rate limits
- Keys never logged in full (only hints)

Breaking Changes:
- AUTH_ENABLED defaults to true
- MCP_API_KEYS environment variable now required
- Minimum key length: 32 characters (64 recommended)

Migration:
1. Generate API key: make generate-key
2. Add to .env: MCP_API_KEYS=<generated-key>
3. Restart: docker-compose restart aegis-mcp
4. Configure ChatGPT with Authorization header

Closes requirements for ChatGPT Business exclusive access.
2026-01-29 20:05:49 +01:00