Skip to content

Security and data

Cortex is a self-hosted application. All data stays on your infrastructure unless you explicitly configure external connections (GenAI providers, optional embedding models).


Cortex uses dual authentication on the REST API and a separate mechanism on the MCP server.

MethodHow it worksTypical consumer
Bearer JWT (Supabase)JWT signed by Supabase, verified server-side with SUPABASE_JWT_SECRET. Carries user email and role in app_metadata.Web UI, human users
X-API-KeyStatic key set via CORTEX_API_KEY env var. Checked in the X-API-Key header.CI pipelines, scripts, services

The middleware checks Bearer JWT first, then falls back to API key. If both are absent, the request is rejected with 401.

An email whitelist (ALLOWED_EMAILS env var) restricts which Supabase users can access the API. An empty whitelist disables JWT login entirely.

MethodHow it worksTypical consumer
OAuth 2.0Full authorization flow with a PostgreSQL-backed provider. Discovery at /.well-known/oauth-authorization-server.Claude.ai, ChatGPT, Cursor
X-API-KeySame static key as the REST API.Local development, testing

The OAuth routes are mounted before the bearer auth middleware, allowing the authorization flow to complete before token verification kicks in.


Cortex enforces a four-level role hierarchy on both API surfaces:

RoleLevelPermissions
reader0Read catalogue, search, browse, health check
contributor1All reader permissions + draft workspace artefacts, deposit memory, delete documents
producteur2All contributor permissions + ingest blocs, profiles, governance atoms
admin3Full access including configuration, audit trail, reindex

On the REST API, the role comes from the JWT’s app_metadata.role field (mapped to the Cortex hierarchy) or defaults to the API key’s configured role.

On the MCP server, every tool declares a minimum required role in the TOOL_MIN_ROLES map. Before a tool handler executes, the session’s role is checked. Insufficient role results in an immediate rejection.


GenAI provider API keys are encrypted with AES-256-GCM before storage:

PropertyDetail
AlgorithmAES-256-GCM
Key sourceENCRYPTION_KEY environment variable (base64-encoded, 32 bytes)
IVUnique per encryption operation (random 12 bytes)
Auth tagGCM authentication tag stored alongside ciphertext
StoragePostgreSQL provider_credentials table
Masked viewOnly prefix (4 chars) + mask + suffix (4 chars) returned to API/UI

If ENCRYPTION_KEY is missing or malformed, the API refuses to start.

SurrealDB and PostgreSQL credentials are managed through environment variables. They are never stored in Cortex’s own database.


DataStorage
Catalogue (atoms, blocs, profiles)SurrealDB
Memory entries (decisions, patterns, errors)SurrealDB
Documents and libraryPostgreSQL + MinIO
Full-text search indexMeiliSearch
Provider credentials (encrypted)PostgreSQL
Audit trail and eventsPostgreSQL
Embedding model weightsOllama (local)
Embeddings (vectors)SurrealDB
DestinationData sentWhen
GenAI provider (Google, OpenAI, Anthropic)The decrypted API key + whatever the consuming application sendsOnly when a service calls getDecrypted() and uses the key
Ollama (if remote)Text to embedOnly if Ollama is configured on a remote host

Cortex itself sends no telemetry, no analytics and no machine identifier to any external service.


The REST API’s CORS origin is controlled by the CORS_ORIGIN environment variable. Default: http://localhost:8080 (the UI).

The MCP server allows origins listed in MCP_CORS_ORIGINS. Default: https://claude.ai,https://chatgpt.com. Override with a comma-separated list for additional LLM clients.


By default, all services bind to 127.0.0.1 (localhost only). The Docker Compose example makes this explicit:

ports:
- "127.0.0.1:9096:9096" # REST API
- "127.0.0.1:3001:3001" # MCP server

The UI service (cortex_ui) binds to 0.0.0.0:8080 to allow LAN access. For production, put a reverse proxy with TLS in front.


  1. Set strong, unique passwords for SurrealDB, PostgreSQL, MeiliSearch and MinIO.
  2. Generate a proper ENCRYPTION_KEY with openssl rand -base64 32 and store it securely.
  3. Use a reverse proxy (nginx, Caddy, Traefik) with TLS termination in front of the API and MCP server.
  4. Restrict ALLOWED_EMAILS to known users if you use Supabase JWT authentication.
  5. Set CORTEX_API_KEY to a strong random value and rotate it periodically.
  6. Limit CORS_ORIGIN and MCP_CORS_ORIGINS to the exact origins that need access.
  7. Back up SurrealDB, PostgreSQL and MinIO regularly (see 08-deployment).