Skip to main content

Row-Level Security

Row-level security (RLS) controls which rows a user can see in query results. After a query passes through RBAC, ABAC, and query-level security, RLS applies a final filter to every row in the result set. Rows that fall outside the user's permitted scope are handled according to the configured filter type.

How RLS Works

RLS rules are defined on a per-dataset or per-field basis. Each rule specifies:

  1. A condition that determines which rows the user is authorized to see.
  2. A filter type that determines how unauthorized rows are handled.

Filter Types

Datafi supports four RLS filter types. Each serves a different use case depending on how much information you want to reveal about the existence or shape of unauthorized data.

Filter TypeBehaviorOutput ExampleUse Case
SkipOmits the row entirely from resultsRow not presentThe user should not know the row exists
BlankReturns the row with empty string values for restricted fields""The user can see the row exists but not its content
MaskReturns the row with asterisk-masked values"***"The user should know data exists but cannot read it
RandomReturns the row with random character values"xK9mQ2"Preserve data shape for testing without revealing real values

Skip

The Skip filter removes unauthorized rows from the result set entirely. The user has no indication that the row exists.

{
"filter_type": "Skip",
"condition": {
"field": "region",
"operator": "eq",
"value": "{{user.region}}"
}
}

Example result:

idnameregion
1Aliceus-west
3Carolus-west

Rows for other regions are silently omitted.

tip

Use Skip when the existence of a row is itself sensitive information, such as records related to ongoing investigations or embargoed data.

Blank

The Blank filter returns all rows but replaces restricted field values with empty strings.

{
"filter_type": "Blank",
"condition": {
"field": "department",
"operator": "eq",
"value": "{{user.department}}"
},
"apply_to": ["salary", "bonus"]
}

Example result for a user in the engineering department:

idnamedepartmentsalarybonus
1Aliceengineering12000015000
2Bobfinance
3Carolengineering11500012000

Mask

The Mask filter replaces restricted field values with a fixed mask string, typically ***.

{
"filter_type": "Mask",
"condition": {
"field": "clearance",
"operator": "gte",
"value": "{{user.clearance}}"
},
"apply_to": ["ssn", "dob"],
"mask_char": "*",
"mask_length": 3
}

Example result:

idnamessndob
1Alice******
2Bob123-45-67891990-01-15
info

Mask is the most commonly used filter type for PII fields. It clearly communicates that data exists but is protected.

Random

The Random filter replaces restricted values with randomly generated characters that match the original data's length and type.

{
"filter_type": "Random",
"condition": {
"field": "project",
"operator": "in",
"value": "{{user.projects}}"
},
"apply_to": ["customer_name", "account_id"]
}

Example result:

idcustomer_nameaccount_id
1xK9mQpLw7829461
2Acme Corp1234567

Random values are regenerated on each query -- the same row will produce different random values across requests.

RLS Conditions

RLS conditions use dynamic variables resolved at query time. You can reference user attributes using the {{user.*}} syntax.

VariableDescriptionExample
{{user.id}}The authenticated user's IDusr_abc123
{{user.region}}The user's assigned regionus-west
{{user.department}}The user's departmentengineering
{{user.clearance}}The user's clearance level3
{{user.projects}}List of projects the user belongs to["alpha", "beta"]
{{user.tenant_id}}The user's tenant identifiertenant_001

Condition Operators

OperatorDescription
eqEquals
neqNot equals
gt / gteGreater than / greater than or equal
lt / lteLess than / less than or equal
inValue is in a list
containsField contains substring

Combining RLS with Other Layers

RLS operates as the final governance layer. It does not replace RBAC, ABAC, or query-level security -- it complements them.

Best Practices

  1. Choose the right filter type for the sensitivity level. Use Skip for highly sensitive rows, Mask for PII fields, and Blank or Random when the data shape matters more than the content.
  2. Avoid overly complex conditions. Keep RLS conditions simple and testable. Complex nested conditions are harder to audit.
  3. Test with multiple user profiles. Verify that each user role sees the expected subset of rows.
  4. Document your RLS rules. Maintain a record of which datasets have RLS applied and what conditions are in effect.