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.
76 lines
4.1 KiB
Markdown
76 lines
4.1 KiB
Markdown
# 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](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.
|