Classification & masking
Per-column data classification, mask strategies, row filters, and the Rego-backed policy engine that enforces them.
Every managed table carries a data classification at the table level and at the column level. Combined with mask strategies and row filters, this gives you fine-grained control over which user — or which workflow — sees which data.
Three layers of governance
| Layer | What it does | Granularity |
|---|---|---|
| Table classification | Marks the entire table at a sensitivity level — public, internal, confidential, restricted. | Per table |
| Column classification + mask strategy | Marks individual columns and decides whether they're shown, redacted, hashed, or denied per role. | Per column per role |
| Row filter | A Rego predicate that restricts which rows a caller can see (e.g. "only rows where org_unit matches the caller's department"). | Per row per caller |
All three are evaluated together at read time. There is no path to the data that bypasses them.
Table classification
Choose at table creation; can be changed by a workspace admin. The classifications map to who can use the table at all:
| Classification | Who can read the table |
|---|---|
| public | Any workspace member |
| internal | Workspace members with the data:read-internal grant (default for members) |
| confidential | Workspace admins and above |
| restricted | Workspace owners and org owners only |
Higher classifications also affect default mask strategies on the columns — see below.
Column classification & mask strategies
Each column can declare a classification of its own. When set, the column's effective sensitivity is the max of its own classification and the table's. A column-level restricted column inside an internal table is still treated as restricted.
For each (column, role) pair, you can declare a mask strategy:
| Strategy | What the role sees |
|---|---|
clear | The actual value |
redact | A fixed mask string ([REDACTED]) |
hash | A deterministic hash — useful for joining masked data across queries |
null | A NULL value as if the column didn't exist for them |
deny | The query fails entirely with a permission error |
Common patterns:
| Column type | Workspace member | Workspace admin | Workspace owner |
|---|---|---|---|
| PII (name, email) | redact | clear | clear |
| Salary / financials | null | null | clear |
| Internal IDs | clear | clear | clear |
| External secrets | deny | deny | clear |
Row filters
A row filter is a Rego predicate that decides whether a row is visible to a caller. Filters are written per role and per table. Example concepts:
- Tenant scope —
row.org_unit == caller.org_unit - Region scope —
row.region in caller.allowed_regions - Project scope —
row.project_id in caller.project_grants
The filter runs as part of the policy decision at read time. Rows that fail the filter are excluded from the result set; the caller has no signal that excluded rows existed at all.
The policy decision point
Classifications, masks, and filters are compiled into a per-tenant Rego bundle by the platform. Every read against a managed table evaluates that bundle. The bundle is rebuilt when:
- A classification is changed.
- A mask strategy is added or modified.
- A row filter is added or modified.
- A role definition changes.
Rebuilds are fast and apply on the next read; there is no waiting period.
OPA is the default policy decision point. A built-in fallback engine is also supported for deployments that prefer not to run OPA as a sidecar. Both consume the same bundle.
Audit
Every read against a managed table emits an RESOURCE_ACCESS event in the audit log with:
- The actor and their resolved role.
- The table ID and the columns requested.
- The row count returned (not the rows themselves).
- The mask strategies and row filters that applied.
For compliance audits, this answers "who saw what column when" without exposing the data itself.
Related
- Managed tables — table creation and lifecycle.
- Security → Authorization — the broader authorisation model these policies live inside.
- Audit logging — what gets recorded on every read.
- Compliance → GDPR — how column masking maps to data-minimisation requirements.