# Security ## Authentication AegisGitea MCP uses bearer token authentication. Clients must include a valid API key with every tool call. ### How It Works 1. The client sends `Authorization: Bearer ` with its request. 2. The server extracts the token and validates it against the configured `MCP_API_KEYS`. 3. Comparison is done in **constant time** to prevent timing attacks. 4. If validation fails, the failure is counted against the client's IP address. ### Generating API Keys Use the provided script to generate cryptographically secure 64-character hex keys: ```bash make generate-key # or: python scripts/generate_api_key.py ``` Keys must be at least 32 characters long. The script also saves metadata (creation date, expiration) to a `keys/` directory. ### Multiple Keys (Grace Period During Rotation) You can configure multiple keys separated by commas. This allows you to add a new key and remove the old one without downtime: ```env MCP_API_KEYS=newkey...,oldkey... ``` Remove the old key from the list after all clients have been updated. --- ## Key Rotation Rotate keys regularly (recommended: every 90 days). ```bash make rotate-key # or: python scripts/rotate_api_key.py ``` The rotation script: 1. Reads the current key from `.env` 2. Generates a new key 3. Offers to replace the key immediately or add it alongside the old key (grace period) 4. Creates a backup of your `.env` before modifying it ### Checking Key Age ```bash make check-key-age # or: python scripts/check_key_age.py ``` Exit codes: `0` = OK, `1` = expiring within 7 days (warning), `2` = already expired (critical). --- ## Rate Limiting Failed authentication attempts are tracked per client IP address. | Setting | Default | Description | |---|---|---| | `MAX_AUTH_FAILURES` | `5` | Maximum failures before the IP is blocked | | `AUTH_FAILURE_WINDOW` | `300` | Rolling window in seconds | Once an IP exceeds the threshold, all further requests from that IP return HTTP 429 until the window resets. This is enforced entirely in memory — a server restart resets the counters. --- ## Audit Logging All security-relevant events are written to a structured JSON log file. ### Log Location Default: `/var/log/aegis-mcp/audit.log` Configurable via `AUDIT_LOG_PATH`. The directory is created automatically on startup. ### What Is Logged | Event | Description | |---|---| | Tool invocation | Every call to a tool: tool name, arguments, result status, correlation ID | | Access denied | Failed authentication attempts: IP address, reason | | Security event | Rate limit triggers, invalid key formats, startup authentication status | ### Log Format Each entry is a JSON object on a single line: ```json { "timestamp": "2026-02-13T10:00:00Z", "event": "tool_invocation", "correlation_id": "a1b2c3d4-...", "tool": "get_file_contents", "owner": "myorg", "repo": "backend", "path": "src/main.py", "result": "success", "client_ip": "10.0.0.1" } ``` ### Using Logs for Monitoring Because entries are newline-delimited JSON, they are easy to parse: ```bash # Show all failed tool calls grep '"result": "error"' /var/log/aegis-mcp/audit.log | jq . # Show all access-denied events grep '"event": "access_denied"' /var/log/aegis-mcp/audit.log | jq . ``` --- ## Access Control Model AegisGitea MCP does **not** implement its own repository access control. Access to repositories is determined entirely by the Gitea bot user's permissions: - If the bot user has no access to a repository, it will not appear in `list_repositories` and `get_repository_info` will return an error. - Grant the bot user the minimum set of repository permissions needed. **Principle of least privilege:** create a dedicated bot user and grant it read-only access only to the repositories that the AI needs to see. --- ## Network Security Recommendations - Run the MCP server behind a reverse proxy (e.g. Traefik or nginx) with TLS. - Do not expose the server directly on a public port without TLS. - Restrict inbound connections to known AI client IP ranges where possible. - The `/mcp/tools` endpoint is intentionally public (required for ChatGPT plugin discovery). If this is undesirable, restrict it at the network/proxy level. --- ## Container Security The provided Docker image runs with: - A non-root user (`aegis`, UID 1000) - `no-new-privileges` security option - CPU and memory resource limits (1 CPU, 512 MB) See [Deployment](deployment.md) for details.