Skip to content

Security

Muhammet Şafak edited this page Sep 18, 2026 · 1 revision

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 one thing to know

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/*.

Operator checklist

  1. Set ADMIN_TOKEN whenever the dashboard is reachable by anyone but you.
  2. Set SECRET_KEY to 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.
  3. Keep the port off the public internet, or terminate authentication in a reverse proxy — including for /mcp/*.
  4. Keep ALLOWED_DOC_ROOTS narrow. It is the boundary that decides which directories can be indexed.
  5. Mount documentation read-only (:ro), as the shipped compose file does.
  6. Scope tokens tightly. A git token needs read access to the documentation repositories, nothing more.
  7. Do not index secrets. Everything indexed is readable by every agent that can reach the endpoint.

What is protected, and how

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

Putting authentication in front

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/*.

Rotating things

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

Things Contextator does not defend against

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

Reporting a vulnerability

Report it privately to the repository owner rather than in a public issue.

Clone this wiki locally