Repository navigation
Roles and Audit
Every tool requires a role. Each role can do everything the roles below it can. The prompt answer_from_documents needs no role: it reads no data, and the tools it names make their usual role checks.
flowchart LR
viewer["viewer<br/>ask, search, feedback"] --> operator["operator<br/>+ read-only SQL"] --> admin["admin<br/>+ layers, users, audit, reset"]
| Role | Can |
|---|---|
viewer |
Ask and search, check drafted answers (strata_verify), give feedback, open receipts, see who they are (strata_whoami), list layers and their status, see learning stats and server health, inspect the schema, vector stats and sample chunks |
operator |
Everything above, plus run read-only SQL (strata_db_query) and see query plans (strata_db_explain) |
admin |
Everything above, plus create, ingest, rebuild and delete layers, set a layer's archived patterns (strata_set_archived_patterns), reset learning, read the audit log and manage users. Layer listings show admins each layer's owner, root folder and archived patterns; other roles do not see them |
The server reads STRATA_USER and STRATA_ROLE once, when it starts; changing either takes a restart. stratakb add, stratakb receipt and the scripts read them each time they run.
A user record is created the first time strata_whoami is called, with the role from STRATA_ROLE. Tool access always follows the STRATA_ROLE the server started with; strata_admin_update_role changes only the stored role, and strata_admin_list_users lists the records.
On the stdio transport, roles separate duties; they don't enforce security. Whoever starts the server sets STRATA_ROLE. For real limits, give each person a database user with only the PostgreSQL privileges they need. SECURITY.md has the details.
The stratakb add command has no role check: whoever can reach the database with DATABASE_URL can add a folder.
Searches and changes to layers and documents are written to strata_core.audit_log, with the user, the tool, the parameters and the time. Admins query it with strata_admin_audit_log:
strata_admin_audit_log(user_id="alice", from_date="2026-11-01", limit=50)
strata_admin_audit_log(tool_name="strata_delete_knowledge_layer")
Parameters that look like secrets, such as API keys and passwords, are redacted before they are written.
strata_search and strata_ask store the first 200 characters of the question, with IBANs, BICs and account numbers replaced by placeholders. AUDIT_STORE_RAW_QUERIES=true keeps the question as written; LEARNING_STORE_RAW_QUERIES has no effect on the audit log.
Each stratakb add run writes one entry with the tool name cli:add, the folder, the run id and the counts per outcome (no file names). Its user is STRATA_USER when you set it, otherwise os: plus your login name, a name nobody verified.
strata_feedback and strata_reset_learning each write one entry. A feedback entry holds the interaction id and the rating, not the suggested query or layer; a reset entry names the layer, or says it reset everything. A feedback entry names only the extra signals that were stored, and a reset that failed in the database writes no entry (the reset runs as one transaction, so nothing was cleared).
Opening a receipt (strata_get_receipt, the strata://receipts/{receipt_id} resource, or stratakb receipt <id> with the tool name cli:receipt) writes a receipt_open entry with the receipt id and counts only; a refused open is audited too, and an id that is not rcpt_ plus 22 URL-safe characters is refused before any lookup and audited as malformed, with the id cut to 32 characters. Issuing one, when strata_verify passes a draft or strata_ask gives an extractive answer, writes receipt_issue the same way, and every strata_verify call that gets past the argument checks writes a verify entry with the result (verified, flagged, nothing_checked, refused for a passage that may not be read, or error when the receipt could not be issued), counts and the receipt id, never the draft. Receipts are kept for RECEIPT_RETENTION_DAYS (90 by default, 0 keeps them); a cleanup that deleted something writes receipt_prune as user system. A receipt can be opened by whoever it was issued to, by an admin, or by anyone who may read every layer it cites (today every viewer). Receipts are not readable through the read-only SQL role.
Each strata_apply_preset run writes one entry with the layer, whether it was created, updated or already up to date, the names of the settings that changed (never their values) and the number of documents and chunks ingested. A refused run writes none.
strata_set_archived_patterns writes a set_archived_patterns entry with the layer and the patterns. strata_apply_preset writes one too when a preset changes a layer's archived patterns, or creates a layer with some.
strata_db_query and strata_db_explain run each statement in a read-only transaction that is always rolled back. They never run as the owner role:
flowchart TD
s["Server starts"] --> u{"READONLY_DATABASE_URL<br/>set?"}
u -->|"no"| off["SQL tools not offered"]
u -->|"yes"| c{"Is that role safe?"}
c -->|"superuser, privileged,<br/>or can't be checked"| off
c -->|"yes"| on["SQL tools offered"]
- The tools are registered only when
READONLY_DATABASE_URLis set. - At startup the server removes them again when that role is a superuser, a member of a superuser role, a member of
pg_read_server_files,pg_write_server_files,pg_execute_server_programorpg_signal_backend, or cannot be checked within 5 seconds (for example, the database is unreachable; restart the server once it is up). The Dockerstratauser is a superuser, so it is refused. -
strata_admin_healthsays which applies undersql_tools.
Point READONLY_DATABASE_URL at the strata_readonly role from db/03_readonly_role.sql. That role can read five tables only:
| Table | Holds |
|---|---|
strata_core.knowledge_layers |
Layers and their settings |
strata_core.ingestion_runs |
Ingestion history |
strata_core.ingested_files |
The files tracked per layer |
strata_vectors.chunks |
The chunks and their vectors |
strata_vectors.documents |
The extracted text of each document version |
It cannot read the audit log, the users table or anything in strata_learning, and a table added later needs a GRANT by name. Existing databases get these grants when the server starts.
- Logs (stderr, JSON): IBANs and card numbers are masked.
-
Stored queries (learning): IBANs, BICs and account numbers are replaced with placeholders, unless
LEARNING_STORE_RAW_QUERIES=true. - Documents: ingested text is stored as is. Don't ingest documents that hold personal data you aren't allowed to keep.
-
Secret files: files whose names look like secrets (
.env, keys, certificates, credentials) are never opened or ingested. A document named after secrets, such ascredentials.txt, is still ingested, so check what you add; see Secret names. -
Where data goes: by default nothing leaves this machine except to the database and your client. See Where your data goes and
STRATA_LOCAL_ONLY.
Ingested documents are untrusted input, and their text reaches your MCP client. A document can contain instructions aimed at the model reading it. The server's answer contract tells clients that passage text is data to quote, not instructions to follow, but a client can ignore that. Only ingest sources you trust, and review what your client does with retrieved text.
Getting started
Using StrataKB
Reference
Help