Skip to content

Roles and Audit

boudou edited this page Nov 9, 2026 · 9 revisions

Roles and Audit

Roles

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"]
Loading
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.

Audit log

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.

Read-only SQL

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"]
Loading
  • The tools are registered only when READONLY_DATABASE_URL is 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_program or pg_signal_backend, or cannot be checked within 5 seconds (for example, the database is unreachable; restart the server once it is up). The Docker strata user is a superuser, so it is refused.
  • strata_admin_health says which applies under sql_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.

Personal data

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

Untrusted content

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.

Clone this wiki locally