Unity Catalog Governance
01 / 15

Unity Catalog
Governance

Four layers of access control. One enforcement model.
The Challenge

Once identity is resolved, what can each user actually see?

Your AI platform answers questions from live data, but not every user should see every answer. Sales reps should only see their own region's pipeline. Partners should see aggregated metrics but never individual customer records. Auditors need read access to compliance tables but nothing else.

These rules must apply consistently whether the question comes from a Genie space, a Vector Search query, an agent workflow, or a direct SQL call. Unity Catalog is the single enforcement layer that makes this possible: row filters, column masks, attribute-based access control, all resolved from the caller's identity.

This deck shows you the four layers of data access control and how they compose.

Foundation

The UC Hierarchy

Every securable object lives in a three-level namespace. Permissions propagate downward.

Three-Level Namespace

catalog.schema.table

-- Example
applied_ai_gov.sales.deals

Tables, views, functions, volumes, models, and connections all live inside this hierarchy.

Grant Propagation

GRANT on catalogApplies to all schemas and tables within
GRANT on schemaApplies to all tables within
GRANT on tableApplies to that table only

Grants are additive. A GRANT at the catalog level gives access to everything below. There is no implicit access: every identity starts with zero privileges.

Explicit-only model: No access without a GRANT. Owners have full control. Metastore admins can manage all objects.

Access Control

Four Complementary Layers

UC evaluates all four layers in sequence before returning any data. Each answers a different question.

1. Workspace Restrictions

WHERE can they access?

Workspace bindings limit which workspaces see specific catalogs. Isolates dev/staging/prod.

2. Privileges & Ownership

WHO can access WHAT?

GRANTs on securable objects. Hierarchical inheritance. Owners have full control.

3. ABAC Policies

WHAT data based on tags?

Governed tags + policy functions enforce access dynamically at scale. Define once, apply to thousands.

4. Table-Level Filtering

WHAT rows/columns?

Row filters, column masks, and dynamic views control visibility at query time per identity.

All four layers evaluate in sequence. The user receives fully governed data: workspace verified, privileges checked, ABAC policies enforced, rows filtered, columns masked, all actions audited.

Layers 1 + 2

Workspace Restrictions & Privileges

Workspace Bindings

Control which workspaces can see specific catalogs, external locations, and storage credentials.

Use caseRestrict production data to production workspaces only
ScopeCatalogs, external locations, storage credentials
OverrideTakes precedence over user-level permissions

A user with SELECT on a table still cannot access it from a workspace that is not bound to the catalog.

Privileges & GRANTs

Explicit permission model. No implicit access.

-- Grant read access
GRANT SELECT ON TABLE
  catalog.schema.deals
  TO `east_sales`;

-- Grant at schema level
GRANT USE SCHEMA ON SCHEMA
  catalog.sales
  TO `all_sales`;

Hierarchical: catalog → schema → table. Owners have full control over their objects.

Layer 4a

Row Filters: How They Work

Row filters automatically restrict which rows a user sees. Applied at query time by the SQL engine.

The Mechanism

A SQL function is bound to a table. UC injects the function as a WHERE clause on every query. The user never sees filtered rows and does not know they exist.

-- Create the filter function
CREATE FUNCTION sales_filter(region STRING)
RETURN
  is_member('admin')
  OR region = 'WEST'
    AND is_member('west_sales');

-- Apply to table
ALTER TABLE deals
  SET ROW FILTER sales_filter
  ON (region);

Key Properties

SilentFiltered rows are invisible. No error, no indication they exist.
AutomaticEnforced by the SQL engine on every query. No app code changes.
Identity-awarecurrent_user() and is_member() resolve at query time.
Same queryDifferent users run the same SQL, see different rows.

One filter per table. If you need multiple filter conditions, combine them in a single function using OR/AND logic.

Row Filters

current_user() vs is_member()

Two identity functions, different propagation behavior. Choose based on your token pattern.

current_user()

Returns the calling user's email from the active SQL token.

-- Filter: only see your own rows
WHERE owner = current_user()
OBOReturns the human's email. Propagates through Genie, Agent Bricks, MCP chains.
M2MReturns the SP's application ID.
Best forPer-user row ownership, individual audit trails.

is_member()

Returns TRUE if the executing identity belongs to a workspace group.

-- Filter: role-based access
WHERE is_member('west_sales')
  AND region = 'WEST'
M2MEvaluates the SP's group membership. Works well when you control the SP.
OBOEvaluates the human's groups. Works as expected.
Best forRole-based filtering, federation patterns.

OBO pattern: prefer current_user(), which propagates the human identity end-to-end. Federation pattern: use is_member(), where role-based SPs map to workspace groups.

Layer 4b

Column Masks: How They Work

Column masks transform sensitive values at query time. Users see the row but with masked column values.

The Mechanism

A SQL function is bound to a specific column. UC replaces the raw value with the function's output on every query.

-- Create the mask function
CREATE FUNCTION mask_ssn(ssn STRING)
RETURN
  CASE
    WHEN is_member('admins')
      THEN ssn
    ELSE '***-**-' || RIGHT(ssn, 4)
  END;

-- Apply to column
ALTER TABLE customers
  ALTER COLUMN ssn
  SET MASK mask_ssn;

Key Properties

TransformationDoes not hide rows. Replaces the column value with a masked version.
Per-columnEach column gets its own mask function. Multiple columns can be masked independently.
ChainableRow filter + column mask can apply to the same table. Filter runs first, then mask.
Return typeMust match the original column type. STRING in, STRING out.

Analyst sees

***-**-6789

Admin sees

123-45-6789

Column Masks

is_member() in OBO Chains

A critical distinction when column masks use is_member() in different token patterns.

M2M: is_member() works as expected

When a service principal executes the SQL directly, is_member() evaluates the SP's workspace group membership. You control group assignment explicitly.

-- SP is in 'admins' group
is_member('admins') => TRUE
-- Full SSN returned

OBO: is_member() evaluates the service layer

In Genie or Agent Bricks OBO flows, is_member() evaluates the service layer's identity, not the calling user's groups. This is a known behavior.

-- Workaround: use allowlist table
CASE
  WHEN EXISTS (
    SELECT 1 FROM role_allowlist
    WHERE email = current_user()
      AND role = 'admin'
  ) THEN ssn
  ELSE '***-**-' || RIGHT(ssn, 4)
END

Rule of thumb: For masks in OBO chains, replace is_member() with a current_user() lookup against a UC-governed allowlist table.

Layer 3

ABAC with Governed Tags

Attribute-Based Access Control uses tags to enforce dynamic, scalable policies without per-object permissions.

Governed Tags: Classification

Account-level tags with enforced naming rules. Applied to catalogs, schemas, tables, or columns. Tags inherit downward.

-- Tag a table
ALTER TABLE customer_data
  SET TAGS ('sensitivity' = 'high',
            'region' = 'EMEA');

-- Tags alone don't enforce access
-- They classify data for policies

ABAC Policies: Enforcement

Policies reference tags to dynamically control access. One policy can govern thousands of tables.

-- Policy: high-sensitivity tables
-- require compliance-team access
CREATE ACCESS POLICY high_sens_policy
  ON TABLE
  WHEN TAG 'sensitivity' = 'high'
  RESTRICT TO GROUP 'compliance_team';

Tags classify. Policies enforce. Change a tag and access changes instantly. No need to update permissions on individual objects.

ABAC

Why ABAC at Scale

Without ABAC

1,000 tables1,000 GRANT statements per role. Manual, error-prone.
New tableMust remember to add GRANTs. Miss one and data leaks or access breaks.
Policy changeUpdate hundreds of GRANTs. Hope you got them all.
AuditPiecing together who has access requires scanning every object.

With ABAC

1,000 tables1 policy + tags. Applied automatically.
New tableTag it. Policy applies instantly.
Policy changeUpdate one policy definition. Instant propagation.
AuditQuery tags and policies. Full visibility.

Scalable

One policy covers thousands of tables.

Dynamic

Re-tag a table to change access instantly.

Auditable

Tags + policies = full governance lineage.

Combined

Row Filters + Column Masks Together

Both can apply to the same table. The row filter runs first, then column masks apply to surviving rows.

Example: Sales Deals Table

-- Row filter: region-based access
CREATE FUNCTION deals_row_filter(region STRING)
RETURN
  is_member('admin') OR
  (region = 'WEST' AND is_member('west_sales')) OR
  (region = 'EAST' AND is_member('east_sales'));

ALTER TABLE deals SET ROW FILTER deals_row_filter ON (region);

-- Column mask: hide margin from non-finance
CREATE FUNCTION mask_margin(val DOUBLE)
RETURN CASE WHEN is_member('finance') THEN val ELSE NULL END;

ALTER TABLE deals ALTER COLUMN margin_pct SET MASK mask_margin;

West Sales Rep Sees

DealRegionAmountMargin
Acme CorpWEST$50KNULL
Beta IncWEST$30KNULL

EAST rows filtered out. Margin masked.

Finance Admin Sees

DealRegionAmountMargin
Acme CorpWEST$50K42%
Beta IncWEST$30K38%
East CoEAST$45K51%

All rows visible. Real margins shown.

Decision Guide

When to Use Which Pattern

Restrict catalogs to specific workspaces
WORKSPACE BINDINGS
Control who can SELECT/MODIFY objects
GRANTS
100s of tables, same access pattern
ABAC + GOVERNED TAGS
Users see only their own rows
ROW FILTER current_user()
Role-based row visibility
ROW FILTER is_member()
Mask PII (SSN, credit card, PHI)
COLUMN MASK
Dynamic tag-based masks at scale
ABAC + MASK UDF
External users, no Databricks accounts
FEDERATION role SPs + is_member()

Most production deployments combine multiple layers. Workspace bindings for environment isolation. GRANTs for baseline access. ABAC for scale. Row filters + column masks for fine-grained control.

Enforcement

All Enforced by the SQL Engine

Not application code. Not middleware. The SQL engine itself enforces every layer.

No Code Changes

Applications run the same SQL. Governance is invisible to the app layer. Add a row filter and every query automatically respects it.

Cannot Bypass

Row filters and column masks are injected by the engine. No SQL trick, no API shortcut, no SDK workaround can skip them. The only override is ownership or admin privilege.

Full Audit Trail

Every query is logged with the identity that executed it, the governance policies that fired, and the data that was returned. System tables provide the full lineage.

Same table. Same query. Different identity. Different data.

This is the core principle of UC governance. It works identically for Genie, Agent Bricks, MCP servers, notebooks, dashboards, and any tool that executes SQL through a warehouse.

Summary

Unity Catalog Governance

4Complementary access control layers, evaluated in sequence
2Identity functions: current_user() for OBO, is_member() for federation
1SQL engine enforces everything, with no application code changes
ABAC scales to thousands of tables with governed tags + policies

Workspace bindings + GRANTs + ABAC + Row Filters + Column Masks

Defense in depth. Every layer adds protection. All audited.

github.com/bhavink/applied-ai-governance