Skip to content

Multi-tenancy & security

Evo.ai is multi-tenant by construction: a request can only ever read and write its own tenant's data, and the tenant id comes only from a verified token.

No model is trained on your data

This is usually the first question, so: nothing you index is ever used to train a model. No model learns from your records, and no model retains them between questions.

What Evo.ai does is indexing, which is a different operation. Records are split into passages and converted into vectors that make "find the passages about this" fast. At question time the relevant few are retrieved and handed to a language model along with the question. The model composes an answer from what it was handed and keeps nothing. It is byte-for-byte the same model before and after — and the same after you delete everything.

Three consequences worth stating plainly:

  • Deletion is real. Remove a record from its source and the next sync removes it from the index. There is no model to un-train, and no residue.
  • Your data cannot surface in another tenant's answers. Passages live in that tenant's own collection and are only ever retrieved for its own questions — see Tenant isolation.
  • Indexing runs locally. Embeddings are computed in-process (fastembed) or by a local Ollama, so the full record set never leaves the deployment even when the answering model is a cloud provider. What a cloud provider sees is one question and the handful of passages retrieved for it.

Choosing a local model means nothing leaves at all. See Configuration.

Tenant isolation

Isolation is enforced two ways at once:

  1. Physical separation — a distinct Qdrant collection per (tenant, collection).
  2. Metadata filtering — a tenant_id filter on every query, so even a shared physical space could not return another tenant's chunks.

The tenant_id is read from verified JWT claims — never from a request body or header a caller could set.

Authentication

All ingest, query, config, and source endpoints require Authorization: Bearer <token>.

  • Dev mode (DEV_MODE=true): any token maps to tenant dev. For local development only.
  • Production: the token is verified against EVOPLATFORM_JWKS_URL (optionally checking JWT_AUDIENCE and JWT_ISSUER), and tenant_id is read from the claims. If auth can't be satisfied the API fails closed.

The token's tenant id and the tenant id the data is indexed under aren't always the same — for example, an EvoPlatform tenant UUID versus an evo.ehs site UUID. A tenant link maps one to the other.

  • Without a link, a caller stays in its own namespace — default deny, never another tenant's data.
  • With a link, the caller reads/writes the linked data tenant's index.
  • Changing a link takes effect on the next request — no re-indexing, since the vectors are stored under the data tenant either way.

Links are managed through the admin API (below) and require a platform-admin token.

Service keys

Trusted backends authenticate with service keys rather than user tokens. Mint one with POST /admin/service-keys; the plaintext key is returned once and only its SHA-256 hash is stored. Rotate by minting the same name again; revoke with DELETE /admin/service-keys/{name}.

Admin API

Cross-tenant administration lives under /admin and requires a token with platform_admin in its claims:

MethodPathPurpose
GET/admin/tenant-linksList tenant links
PUT/admin/tenant-links/{platform_tenant_id}Grant a platform tenant access to a data tenant's index
DELETE/admin/tenant-links/{platform_tenant_id}Revoke a link (falls back to the tenant's own namespace)
GET/admin/service-keysList service keys
POST/admin/service-keysMint or rotate a service key (plaintext returned once)
DELETE/admin/service-keys/{name}Revoke a service key

Encryption at rest

Provider API keys (per-tenant model config) and connector configs (URLs, API tokens) are encrypted at rest with Fernet, keyed by SECRET_KEY. SECRET_KEY is required in production; a fixed dev key is used only when it is unset in dev mode. Secrets are never echoed back by the API.

Prompt-injection resistance

The assistant policy instructs the model to treat retrieved context strictly as data and never to follow instructions embedded inside it, and the relevance gate refuses off-topic or injection-style questions before the model is ever called. See Querying and Configuration.

Analytics SQL safety

The evo.ehs analytics layer runs LLM-written SQL, so it is fenced by a SELECT-only role, a read-only transaction, an empty search_path, keyword and schema-prefix rejection, a row cap, and a statement timeout — with any failure falling back to vector retrieval. Details in Connectors.

Documentation hub for Evomedia.net LLC products.