Snowflake applies protection at query time through policies attached to columns and tables. The data stored is unchanged, and what a user sees depends on their role.
Dynamic data masking
A masking policy is a function that decides what to return for a column:
CREATE MASKING POLICY email_mask AS (val STRING) RETURNS STRING ->
CASE WHEN CURRENT_ROLE() IN ('PII_READER') THEN val
ELSE REGEXP_REPLACE(val, '.+@', '*****@') END;
ALTER TABLE customers MODIFY COLUMN email SET MASKING POLICY email_mask;A user with the PII_READER role sees asha@example.com. Everyone else sees *****@example.com. The same query returns different results by role, and no copy of the table is needed.
Row access policies
These filter rows for each user. A common design uses a mapping table:
CREATE ROW ACCESS POLICY region_rap AS (region STRING) RETURNS BOOLEAN ->
EXISTS (SELECT 1 FROM security.user_regions m
WHERE m.role_name = CURRENT_ROLE() AND m.region = region);The regional sales manager role only sees rows for their region. Adding a region for a manager is an insert into the mapping table, not a code change.
Tag-based masking
Instead of attaching a policy to each column by hand, you tag columns (for example pii = 'email') and attach a masking policy to the tag. Every column carrying the tag is masked automatically. This scales much better when you have hundreds of tables.
Secure views
A secure view hides its definition and prevents some optimizer behaviours that could leak data through query plans. Use it when sharing a restricted slice of a table, especially across accounts.
Remember
Policies add some query overhead, and a policy can block features on tables (for example, some clone or sharing combinations). Test the performance of heavily filtered tables. And test the policy for each role, including admin roles, because ownership does not automatically bypass it.