Files
AegisGitea-MCP/docs/hardening.md
T
Latte cf3d2e17d3 fix: fail closed on an unwritable audit log, bound the rate limiter and harden startup
The audit logger is opened at startup (exit 2 on failure) and obtained before
every call, which is refused with audit_unavailable otherwise. Audit records only
take validated path and sudo values and use the request id as correlation id. The
rate limiter evicts idle keys, caps tracked keys, audits the first excess per key
and window and uses the settings passed to the app. FORWARDED_ALLOW_IPS is now a
setting, and startup warns about a missing host allow-list and unavailable OAuth.
2026-10-14 16:24:51 +02:00

4.1 KiB

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.
  • 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 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).
  • 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.