Security model
Moʻolelo's security model has three pillars:
- Source-system authority is canonical. The platform never grants access the source system would not grant. It can be more restrictive, never less.
- Every authorization decision is auditable. The audit log records who asked, what they asked for, what was returned, and the source-system permission that allowed it.
- Workload identity is verifiable. Service-to-service traffic uses SPIFFE-issued identities and mutual TLS; no service can impersonate another.
Source-system authority is canonical
When you search a Confluence space, the result set is whatever the calling user could see in Confluence. Same for SharePoint, GitHub, and any other source.
Moʻolelo can apply additional restrictions — a tenant may decide that certain tags are admin-only regardless of source-system permission — but it cannot relax source-system permissions. A page that's restricted in Confluence is restricted in Moʻolelo.
To make this work at search latency, Moʻolelo extracts each source's access control list at ingestion time and projects it into a per-record authority chain. Authorization decisions are made by OpenFGA over those projections; lookups are sub-millisecond.
When a per-source permission cannot be expressed in OpenFGA's relationship model — typically because it depends on runtime context — Moʻolelo falls back to a per-source policy hook that re-checks the live permission. This is rare in practice; most enterprise sources translate cleanly to OpenFGA tuples.
Authority chains
Every served result carries an authority chain — the trail of source-system permissions that justified the platform's authorization decision. A typical chain for a Confluence page might be:
SourceConfluence:acme {
policy: page-restriction-CONF-12345
granted_to: group:engineering
granted_at: 2026-04-12
reason: "User alice is in engineering"
}The chain is logged with every audit row. If a compliance reviewer asks "why did Moʻolelo show alice this page?", the answer is in the audit log.
Audit logging
Every served query produces one audit row containing:
- The calling user's identity (SPIFFE ID and canonical person ID).
- The query's tenant and region.
- The list of records returned, with their authority chains.
- The latency and the rank profile used.
- Any detokenize requests made to fulfill the query, with the regional vault's per-field decisions.
Audit rows are persisted both to the durable Iceberg audit_* tables and to a queryable ClickHouse replica. The Iceberg table is the source of truth — ClickHouse is a fast index for compliance dashboards.
Retention is configurable per tenant. The default is seven years.
Workload identity
Internal service-to-service traffic uses SPIFFE identities issued by a SPIRE server. Every service has a unique SPIFFE ID and presents a short-lived X.509 SVID for mutual TLS via Linkerd.
Operators don't manage SPIFFE-id rotation directly — SPIRE handles attestation and issuance based on Kubernetes node + pod attributes. The platform refuses to start a service whose SPIFFE attestation fails.
Secrets handling
- Source-system credentials (PATs, OAuth refresh tokens, API client secrets) live in HashiCorp Vault. The connector services dereference them at startup and on rotation; they never sit in environment variables or Kubernetes Secret objects in cleartext.
- Database passwords and similar operational secrets follow the same pattern.
- Personal-data tokens (regional vault tokens) are unrelated to the service-secret vault — they live in their own per-region vault scoped to PII.
License key
Every service refuses to start without a verified license key. Keys are signed JSON Web Tokens issued by Moʻolelo; verification is offline against an embedded ed25519 public key, so no service ever phones home.
A key carries:
- Tier:
community,enterprise, orregulated. - Issued-to: the customer name on the contract.
- Expiry: the date past which the platform stops accepting new ingests.
- Limits: max employees, max events per day, max regions.
Past expiry, the platform enters a 7-day grace window during which it logs warnings and surfaces an admin-UI banner but otherwise operates normally. After the grace window, ingestion stops; queries continue against existing data so you don't lose access during a key-renewal scramble.
See License key management for the operator-side flow.

