docs: rewrite README and docs for the full-access design, generate tool inventory
This commit is contained in:
1 parent
68bd8700dc
commit
7c7cfc05ff
13 files changed
+1693
-1026
No files matched your search
+68
-18
@@ -1,24 +1,74 @@
|
||||
# Hardening
|
||||
# Hardening checklist
|
||||
|
||||
## Application Hardening
|
||||
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.
|
||||
|
||||
- Secure defaults: localhost bind, write mode disabled, policy-enforced writes.
|
||||
- Strict config validation at startup.
|
||||
- Redacted secret handling in logs and responses.
|
||||
- Policy deny/allow model with path restrictions.
|
||||
- Non-leaking production error responses.
|
||||
## Decide what to expose
|
||||
|
||||
## Container Hardening
|
||||
- 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.
|
||||
|
||||
- Non-root runtime user.
|
||||
- `no-new-privileges` and dropped Linux capabilities.
|
||||
- Read-only filesystem where practical.
|
||||
- Explicit health checks.
|
||||
- Separate dev and production compose profiles.
|
||||
## Treat the credentials as keys to the instance
|
||||
|
||||
## Operational Hardening
|
||||
- `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`.
|
||||
|
||||
- Rotate API keys regularly.
|
||||
- Minimize Gitea bot permissions.
|
||||
- Keep policy file under change control.
|
||||
- Alert on repeated policy denials and auth failures.
|
||||
## 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.
|
||||
- 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.
|
||||
Reference in new issue
Block a user