Dynamic Data Masking with Column Masking Policies
A masking policy is a schema-level object created with CREATE MASKING POLICY that defines, via a SQL expression, how a column's value should be transformed based on the querying role, then attached to one or more columns with ALTER TABLE ... MODIFY COLUMN ... SET MASKING POLICY. Because the policy is evaluated dynamically at query time rather than by altering stored data, the same underlying value can appear fully visible to a privileged role and redacted or hashed to an unprivileged role, and the policy automatically applies everywhere the column is used, including views, joins, and cloned tables.
Cricket analogy: It's like a broadcaster's live feed showing full player biometric data to the team's own medical staff but blurring that same overlay to the general TV audience.
Row Access Policies for Row-Level Security
A row access policy is a schema-level object created with CREATE ROW ACCESS POLICY that defines a boolean expression evaluated per row, typically checking the current role or a session context against a mapping table, and is attached to a table with ALTER TABLE ... ADD ROW ACCESS POLICY. Rows for which the expression evaluates to false are silently excluded from query results for that role, so a single physical table can serve multiple business units or regions with each seeing only their own rows, again without maintaining separate filtered copies of the data.
Cricket analogy: It's like a single central fixtures database where each franchise's team management only sees rows for their own squad's matches, filtered live by their login, not by separate spreadsheets per team.
-- Column masking: hash SSNs for anyone except the privileged role
CREATE MASKING POLICY ssn_mask AS (val STRING) RETURNS STRING ->
CASE
WHEN CURRENT_ROLE() IN ('COMPLIANCE_ADMIN') THEN val
ELSE 'XXX-XX-' || RIGHT(val, 4)
END;
ALTER TABLE employees MODIFY COLUMN ssn
SET MASKING POLICY ssn_mask;
-- Row access policy: each region only sees its own rows
CREATE ROW ACCESS POLICY region_policy AS (region STRING) RETURNS BOOLEAN ->
CURRENT_ROLE() = 'GLOBAL_ANALYST'
OR EXISTS (
SELECT 1 FROM region_role_map
WHERE region_role_map.role_name = CURRENT_ROLE()
AND region_role_map.region = region
);
ALTER TABLE sales_by_region
ADD ROW ACCESS POLICY region_policy ON (region);Applying Governance Consistently Across Clones and Shares
Because masking and row access policies are evaluated at query execution time rather than baked into stored bytes, they travel with the object: a zero-copy clone of a masked table retains the same masking policy, and a table shared via Secure Data Sharing continues to enforce it for the consuming account's roles, provided the consumer maps its roles appropriately or the policy logic accounts for cross-account context. This makes policies the correct governance layer for combining with cloning and sharing features, since re-implementing row filters or masking logic in downstream views would be far more error-prone and easy to bypass.
Cricket analogy: It's like a stadium's access-control rule following the turnstile system to a brand-new satellite venue, rather than needing separate rules configured at every stadium.
Masking and row access policies can be inspected with SHOW MASKING POLICIES, SHOW ROW ACCESS POLICIES, and by querying the POLICY_REFERENCES table function, which lists every object a given policy is currently attached to — useful for auditing coverage before a compliance review.
A masking policy hides displayed values but does not prevent an unprivileged role from inferring sensitive data through aggregate functions, GROUP BY on masked-adjacent columns, or joins against other unmasked tables. Combine masking policies with row access policies and careful grant scoping for genuinely sensitive datasets rather than relying on masking alone.
- Masking policies dynamically transform column values at query time based on the querying role, without altering stored data.
- The same masked column can show full values to privileged roles and redacted values to others, consistently everywhere it's used.
- Row access policies define a boolean per-row expression, silently filtering out rows a role shouldn't see.
- A single physical table can serve many business units or regions safely under one row access policy.
- Policies are evaluated at query time, so they travel automatically with zero-copy clones and shared objects.
- POLICY_REFERENCES and SHOW commands let you audit exactly which objects a policy currently protects.
- Masking alone can be circumvented via aggregation or joins, so combine it with row access policies and tight grants.
Practice what you learned
1. When is a masking policy's transformation logic actually applied to a column's value?
2. What does a row access policy's boolean expression determine?
3. If you zero-copy clone a table that has a masking policy attached, what happens to the policy?
4. Which function/view helps you audit every object a specific policy is currently attached to?
5. Why is masking alone insufficient to fully protect sensitive data?
Was this page helpful?
You May Also Like
Roles and Access Control
How Snowflake's role-based access control model governs who can do what, using hierarchical roles, privilege grants, and secure ownership rather than per-user permissions.
Secure Data Sharing
How Snowflake lets one account share live, read-only data with another account instantly, without copying or moving any data between them.
Zero-Copy Cloning
How Snowflake's CLONE command creates instant, storage-free copies of tables, schemas, and databases by referencing existing micro-partitions rather than duplicating data.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics
ProgrammingPowerShell Study Notes
Programming · 30 topics