Security model
This page describes how Mandatum keeps one workspace's data away from another, how it decides whether a call may run, and what it records. For the credentials themselves, read Authentication. It describes the guarantees our systems enforce. It does not replace your own review of the platform before you rely on it for regulated work.
Tenant isolation #
Every read that serves an API call is limited to the caller's workspace. Records that belong to another workspace are never returned, and a request for one is answered exactly as a request for a record that does not exist. Run ids, matter ids and webhook ids do not reveal whether they exist in another workspace.
Row-level security on workspace tables is a second layer for queries that run with the ordinary database client.
Service-role database access is confined to a short, named list of server-side modules, each limited to the tables it needs. Tool handlers never hold that access.
One chokepoint for authorisation #
Every REST route and the MCP endpoint call one authorisation function before doing anything else. It authenticates the bearer token, checks that the caller's workspace membership is still valid, applies rate limits, and returns the token's scopes. Tests fail the build if a route is added that does not call it exactly once.
The check is default-deny. A token without the scope a tool needs is refused. See Scopes and sharing.
Credentials #
- API keys and OAuth tokens are stored as hashes. A raw secret is shown once.
- Revocation takes effect on the next request.
- Authorisation codes are single use and expire after 5 minutes.
- PKCE is required for OAuth authorisation.
- Webhook signing secrets are encrypted at rest, and each delivery is signed with HMAC-SHA256 so a receiver can check it. See Webhooks and runs.
Protection against forged metadata and outbound requests #
- The OAuth discovery address in a
401challenge is built from the server's configured address, never from the request'sHostheader. A caller cannot make Mandatum advertise an address it chose. - Outbound requests that Mandatum makes on a caller's behalf, such as OAuth metadata discovery and webhook delivery, refuse private, loopback and link-local addresses. The address is checked again immediately before each delivery, after DNS resolution.
- Webhook URLs must be
https, with no embedded credentials or fragment. - Redirect addresses for OAuth are checked against loopback rules and the operator's allowlist, and anything else is refused.
Token passthrough #
Mandatum never forwards a caller's bearer token to another service. Outbound calls use the server's own credentials, limited to what the call needs.
Audit trail #
Tool calls, key lifecycle events (create, rotate, revoke and policy changes), analysis steps and OAuth revocations are written to an append-only audit log. The log is tenant-scoped and tamper-evident.
Tenancy. Each row records its workspace. Members read only the rows of workspaces they belong to. Anonymous callers read and write nothing. Rows with no workspace are system events, and only the service role can read them.
Append-only. UPDATE, DELETE and TRUNCATE are refused for every role, including the service role and the table owner. Changing history means dropping that protection first, and the verification below detects it.
One chain per workspace. Each workspace has its own hash chain, and system events form one more. Every hash is computed by the database, not supplied by the caller. Each row's hash covers the previous row's hash in the same chain, so changing or removing a row breaks every later link. Writers to one workspace queue on that chain's tip; writers to different workspaces do not wait on each other.
Verifying. A workspace owner or admin can check their own chain, from the app or the database, with verify_my_audit_chain. It returns whether the chain is intact, how many rows it checked, and the first row that fails, if any. A member who is not an owner or admin is refused. The compliance tools in the app use the same check.
Checkpoints. A full verification run by the platform records a checkpoint for each chain. Later checks walk only the rows added after it, so the cost of a check stays bounded. A full walk from the first row remains available.
Trust boundary. The chain is tamper-evident against everyone below the table owner. A database superuser could change a row and recompute the chain after it, and a full walk from the first row would catch that only if no checkpoint covers the change. The service role cannot do this: it cannot run the hash function, cannot disable the protection, and cannot rewrite history through the API.
Restores. Point-in-time recovery does not change the chain. A logical restore that fires the hash trigger recomputes every hash and starts new chains, which is a reset rather than a repair. Restores for recovery should keep the stored hashes, and the chain should be verified afterwards.
For each tool call the audit entry carries the request id, which is the value to quote when you report a problem. Read Usage and units for the metered side of the same calls, and Errors for the request id in error bodies.
What leaves Mandatum #
Tools return shared statute data and, for family-scoped tools, data about your workspace. Mandatum does not return other workspaces' data, database credentials, API keys or internal configuration. What reaches your model or client is described in Data egress.
Reporting a problem #
Report a suspected security issue to the address on the contact page. Do not include live credentials in the report.