Controlling access
Access control is enforced in four independent layers. Each is evaluated on every request, and no layer relies on another having done its job.
| Layer | Decides | Enforced |
|---|---|---|
| Tenant isolation | Which rows exist at all for this caller | In the database |
| Permissions | Whether this caller may perform this action | At the request boundary |
| ABAC policies | Whether this caller may see this particular record | On the record, when loaded |
| Field masking | Whether this caller may see this particular value | When the response is generated |
The layering is the design. A bug in one layer does not open the others, and the innermost layer is enforced by the database rather than by application code.
Tenant isolation
The administration section. Each layer described below is configured on its own screen here.
Every record belongs to exactly one tenant, and isolation is enforced in the database. A query that forgets to filter by tenant does not leak — it returns nothing.
This is why a cross-tenant request returns "not found" rather than "forbidden": the row is genuinely invisible, not merely refused.
Permissions
Authorization is expressed as permission codes in resource.action form —
entity.read, match-profile.update. Every endpoint declares the codes it
requires.
Roles bundle codes. Users hold roles directly or through groups, and their effective permissions are the union.
Effective permissions are resolved per request rather than baked into the caller's token. Granting or revoking access takes effect on the next request, not when the token happens to expire.
ABAC policies
Permissions answer "may this caller read entities?". ABAC policies answer "may this caller read this entity?" — using attributes of the caller, the record, and the request.
Four kinds of condition are enforced:
| Condition | Example |
|---|---|
| Entity type | Deny access to one type of record |
| Attribute value | Deny records carrying a particular classification |
| Relationship | Allow only records related to the caller |
| Consent | Allow only while a consent of a given type is active |
Rules are deny-overrides: a denial always wins, regardless of ordering. On top of a default-allow base, they act as a deny overlay on the permission layer.
A denial that would match every request is rejected when you save it, because it would lock the tenant out of its own data.
Access-control administration is deliberately exempt from ABAC policies. You can never write a rule that makes access control itself unmanageable.
Field masking
Masking redacts values in responses based on who is asking — full masking, partial masking, hashing, or omission.
Masking is applied when the response is generated, in one pass over every entity in it. That is what makes it unbypassable: there is no endpoint that returns the value unmasked, because masking is not implemented per endpoint.
It applies uniformly to entity attributes, display labels, search results, match comparison screens, and audit values.
Identity
Users and groups are provisioned from your identity provider, never created by hand. Sign-in is real federated authentication; there is no separate local password store and no development shortcut that behaves differently from production.
Next
Last verified against commit 315eb047 (2026-08-03)