Security and privacy
The invariants that protect a deployment, and the controls on data leaving it.
Tenant isolation#
- Every resource carries the owning user's id, and ownership is checked on every read and write path.
- Filesystem paths are derived from
data/users/{user_id}/. - Every user-scoped bus event carries
user_id; the SSE fan-out filters on it. An event without it reaches every connected client. - A module receives only its caller's log, an ownership-checked second log, and a result cache scoped to
(log_id, module_id). - The only cross-account read is an explicit, read-only dashboard share.
- A foreign user's resource is indistinguishable from a missing one (
404).
Authentication and roles#
Login is mandatory and delegated to Keycloak. The web app keeps a JWT-only session (Auth.js v5) with token rotation; the API validates every Bearer token against the realm's JWKS endpoint and inserts a users row the first time it sees a subject. If the session cookie outgrows the browser's 4 KB limit, SESSION_STORE_DIR moves session state server-side.
Signing out ends the Keycloak session, not just the web app's: /logout redeems the session's refresh token at Keycloak, which revokes every token of it and kills the single-sign-on cookie in the browser. Every sign-in also forces a credentials prompt (prompt=login, passed on to the university IdP), so a second person on the same browser is never signed in as the first. A university session that outlives the logout is outside MATE's reach; whether it re-prompts depends on the IdP honouring prompt=login.
| Role | Grants |
|---|---|
| default realm roles | Normal use. |
admin | Admin pages and routes, cross-user job control, module policies, the metadata export, the admin MCP toolset. |
Roles arrive in the access token, so a change takes effect at the next refresh rather than the next logout.
Credentials#
| Credential | Lifetime | Scope |
|---|---|---|
| Session JWT | 24 h, rotated | The web app. |
Personal access token (mate_pat_…) | Until revoked | Explicit scopes, never admin, only its owner's data. |
| OAuth access token (MCP) | Keycloak lifetimes | Scopes plus, for the admin toolset, the admin realm role. |
Tokens are stored as hashes with a display prefix; the secret is shown once. Independently of the credential, each user must opt in to external data access before MCP tools return their data.
The data wall#
Raw event rows, distinct column values, OCEL object rows, and file downloads are structurally unavailable to MATE AI and to every MCP tool. It is enforced by construction — restricted event-log accessors on AI-invoked contexts — not by prompt instructions.
Usage analytics#
Capture is optional and local, stored in the same OCEL-shaped schema the platform uses for process analysis, so the platform's own usage data can be analysed with the same methods. Client events (page views, clicks, web vitals, errors) and server events (every authenticated request, named by route template) are covered by a master switch plus granular toggles for form values, keyboard, and pointer movement. USER_TRACKING_ONBOARDING decides whether tracking starts on, off, or forced on with the opt-out hidden. Exports are NDJSON, OCEL 2.0 JSON, or OCEL 2.0 SQLite, and nothing leaves the host.
Routes deliberately avoid ad-blocker trigger words: /usage, /sync, /insights instead of /analytics, /events, /track.
Resource limits on a shared host#
| Setting | Bounds |
|---|---|
WORKER_CONCURRENCY | Concurrent jobs (1–8). |
MODULE_PROCESS_POOL_SIZE | Concurrent CPU-offload processes. |
MAX_OFFLOADS_PER_USER | One account's share of that pool — set it below the pool size. |
DUCKDB_THREADS / DUCKDB_MEMORY_LIMIT | One query's CPU and memory. |
| Container memory limits | The production overlay caps api at 8 GB, web at 768 MB, Keycloak at 1 GB, each database at 512 MB, the proxy at 128 MB. |
Hardening checklist#
- [ ] Rotate
AUTH_SECRETandKEYCLOAK_CLIENT_SECRETbefore any non-local deployment. - [ ] Replace the seeded realm user's password; grant
admindeliberately, never by default. - [ ] Keep
DEMO_MODEoff everywhere except a disposable workshop host. - [ ] Keep
MCP_ENABLED=falseunless you need it; prefer PATs over broad OAuth grants. - [ ] Set
MAX_OFFLOADS_PER_USER,DUCKDB_THREADS, and container memory limits on a shared host. - [ ] Back up both
data/and the metadata database, and verify a restore once.