-
Notifications
You must be signed in to change notification settings - Fork 0
Security
Contextator is built for a specific deployment: one trusted operator, on a private network or behind a reverse proxy. Read this page before exposing it anywhere else.
The MCP endpoints (
/mcp/*) are unauthenticated by design. Anyone who can reach the port can search and read every project's documentation.
This is not an oversight. MCP clients have no interoperable way to carry credentials, so requiring a token would mean the product only worked with whichever client we picked. The answer is network position: keep the port private, or put authentication in front of it.
ADMIN_TOKEN protects the dashboard and the API. It does not protect /mcp/*.
-
Set
ADMIN_TOKENwhenever the dashboard is reachable by anyone but you. -
Set
SECRET_KEYto 32+ random characters (openssl rand -hex 32) before adding a private git repository or a Notion integration. Store it somewhere other than your database backup. -
Keep the port off the public internet, or terminate authentication in a reverse proxy — including
for
/mcp/*. -
Keep
ALLOWED_DOC_ROOTSnarrow. It is the boundary that decides which directories can be indexed. -
Mount documentation read-only (
:ro), as the shipped compose file does. - Scope tokens tightly. A git token needs read access to the documentation repositories, nothing more.
- Do not index secrets. Everything indexed is readable by every agent that can reach the endpoint.
| Area | Control |
|---|---|
| Dashboard and API | Optional ADMIN_TOKEN bearer, compared in constant time. /api/health is exempt so status stays visible |
| Webhook endpoint | Each git source has its own secret; the provider's signature is verified against the raw body before anything is queued |
| Stored tokens | Encrypted with AES-256-GCM under SECRET_KEY; never returned by the API, never shown again. Credentials pasted into a repository URL are stripped before storage |
| Filesystem access | Local source directories must resolve inside ALLOWED_DOC_ROOTS; .., escaping symlinks and non-directories are rejected. Git subdirectories are resolved inside the checkout |
read_document |
Serves only paths that were indexed for that project — never an arbitrary filesystem path — capped at 512 KB |
| Uploads and archives | Extracted into a scratch directory first, then copied with traversal rejection, dot-directory removal, portable-name checks, an extension filter and entry/size caps against decompression bombs |
Browser requests to /mcp/*
|
Origin validated (DNS-rebinding protection). Command-line clients send no Origin and are always allowed |
| Project isolation | Every query is scoped by project; a session issued for one project is rejected on another; deleting a project closes its sessions |
| Database | The embedded PostgreSQL listens on 127.0.0.1 inside the container and is not published |
| Process | The application runs as an unprivileged user; the entrypoint is root only long enough to fix volume ownership |
Dashboard files and the /about, /privacy, /cookies, /terms, /license pages |
Deliberately open. ADMIN_TOKEN gates /api/*, not the HTML: the lock screen hides your data, not the page that asks for the token. These pages are static, read nothing from the database and carry no secret |
Example with nginx Basic authentication, leaving the streaming transports intact:
location / {
auth_basic "Contextator";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:3444;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 1h;
}
# the webhook endpoint must stay reachable by the git host
location /api/webhooks/ {
auth_basic off;
proxy_pass http://127.0.0.1:3444;
}Note that most MCP clients cannot send Basic credentials, so this protects the dashboard more than it
serves agents. A mutual-TLS or VPN boundary is usually the better fit for /mcp/*.
| What | How |
|---|---|
ADMIN_TOKEN |
Change it and restart. Every dashboard session is asked for the new one |
| A git or Notion token | Edit the source and paste the new token; the old ciphertext is overwritten |
| A webhook secret | Regenerate in the source dialog, then update the repository settings |
SECRET_KEY |
Changing it invalidates every stored token — you must re-enter them. Plan for it |
POSTGRES_PASSWORD |
Applied only at cluster creation; change it later with ALTER USER and update .env — see Backup and Data
|
- Prompt injection through documentation. If a document says "ignore your instructions and…", the agent may read it. Contextator returns text faithfully; it does not sanitise intent. Treat your indexed corpus as trusted input.
-
Rate limiting. There is none on
/mcp/*or/api/*. The network boundary is the control. - Multi-user access control. There are no accounts, roles or per-project permissions. Everyone who can reach the dashboard is an administrator; everyone who can reach the MCP endpoints can read everything.
Report it privately to the repository owner rather than in a public issue.
Contextator · AGPL-3.0-or-later · Self-hosted MCP documentation server
Getting started
Your documentation
Using it
Operations