# Hardening checklist This server is full access by design (ADR 0001): whoever can call it has the power of the configured Gitea token, which for an admin token means everything. The server keeps a few invariants (audit, secret redaction, bounded output, validated input, fail-closed HTTP authentication), but it does not decide what is safe. This checklist is how you do that. ## Decide what to expose - Create the Gitea token with the narrowest scopes that do the job. Full access needs a site-admin token with `all`; a read-only assistant needs only read scopes. The token is the hard limit, because Gitea enforces it. - Prefer per-user OAuth to a shared admin token whenever more than one person connects: each user then acts under their own Gitea permissions and admin tools fail for non-admins. - Narrow what is listed: `READ_ONLY=true`, `TOOLSETS=...`, `DISABLED_TOOLS=...`. Hide what the assistant never needs: `admin`, `actions` (secrets), `hook`, `user` (tokens, keys) and the delete tools are the sensitive ones. `gitea_api_request` can reach any endpoint, so on a restricted profile drop `misc` or disable it. - Keep `OAUTH_SCOPES` as narrow as the use case allows. - Do not use Sudo-capable tokens for clients that do not need to impersonate users. ## Treat the credentials as keys to the instance - `GITEA_TOKEN` and `MCP_BEARER_TOKEN` are equivalent in power on HTTP. Generate the bearer token with `openssl rand -hex 32`, store it in a secret manager or a mode 0600 env file, rotate it when someone leaves, and never put it in a URL (it only goes in the `Authorization` header). - `TOKEN_ENCRYPTION_KEY` protects the stored per-user tokens. Keep it separate from the data volume backups. `GITEA_OAUTH_CLIENT_SECRET` likewise. - Use `setup`, which writes the env file with mode 0600 and never prints secrets. The server loads `./.env` only while it is a regular file you own with mode 0600; keep it elsewhere with `--env-file` or `AEGIS_ENV_FILE`. - For a public OAuth deployment set `OAUTH_REDIRECT_ALLOWLIST` (for example `https://claude.ai/,https://chat.mistral.ai/`), so only the hosted clients you use can register, and leave `MCP_BEARER_TOKEN` and `GITEA_TOKEN` unset when nobody needs the admin identity. - Do not paste tokens into chat with the assistant, and do not commit `.env`. ## Network and transport - Keep the default bind `127.0.0.1`. Publish the container port on `127.0.0.1` and put a reverse proxy with TLS in front. A non-loopback bind refuses to start without authentication; do not work around it. - Terminate TLS with a valid certificate. `PUBLIC_BASE_URL` must be https. - Set `ALLOWED_HOSTS` to exactly the host names you serve. Requests with other `Host` values are rejected. - Restrict who can reach the endpoint at the proxy or firewall too (VPN, allow list), as a second layer in front of bearer authentication. - Set `FORWARDED_ALLOW_IPS` to the proxy address only (not `*`), so a client cannot spoof its IP. - Leave `RATE_LIMIT_PER_MINUTE` on. Put request-size and connection limits at the proxy. - Use https for `GITEA_URL`. Plain http sends the token unencrypted (setup warns about it). ## Container and host - Run the image as the non-root user, `read_only: true` with a `tmpfs` for `/tmp`, `cap_drop: ALL` and `no-new-privileges`, as the compose snippet in [deployment.md](deployment.md) does. - Mount only the data volume. Give it to the service account alone (mode 0700). - Pin the image by digest in production, and update regularly. - Do not run the stdio server with a token that is broader than the user's own needs on a shared machine: any process of that user can read the client configuration. ## Audit and monitoring - Ship the audit log off the host and validate the chain regularly ([audit-logging.md](audit-logging.md)). - Alert on repeated `forbidden` or `unauthorized` outcomes and on calls to admin, secret and delete tools. - Keep `LOG_LEVEL=INFO` and `EXPOSE_ERROR_DETAILS=false` in production. - Keep `SECRET_DETECTION_MODE=mask` (or `block`). Secret detection is a safety net with regular expressions, not a guarantee. ## Untrusted content - Keep tool-call confirmation enabled in the client for destructive tools. Prompt injection in issues, pull requests, comments and files is the realistic way a model is tricked into using the access it has. - Do not connect a write-capable, broad-scope profile to an assistant that also reads untrusted content from other sources. - Prefer separate profiles: a read-only one for browsing and triage, a write-capable one that you use deliberately.