Skip to main content

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.

LayerDecidesEnforced
Tenant isolationWhich rows exist at all for this callerIn the database
PermissionsWhether this caller may perform this actionAt the request boundary
ABAC policiesWhether this caller may see this particular recordOn the record, when loaded
Field maskingWhether this caller may see this particular valueWhen 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 home screen 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.

Permissions resolve live, not from the token

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:

ConditionExample
Entity typeDeny access to one type of record
Attribute valueDeny records carrying a particular classification
RelationshipAllow only records related to the caller
ConsentAllow 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.

ABAC policies never gate policy administration

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)