Pro ยท Lesson 11 of 13

Governance and Horizon

Roles, database roles, masking, row access, tags, access history, and Horizon Catalog.

Sign in to save your place.

Picture it this way

A role is a badge. Grants are what the badge opens. A database role is a badge that only works inside one database, which you then pin onto an account role. A masking policy is a stamp that replaces a column when the badge is wrong. A row access policy throws away rows before that stamp is applied. Horizon Catalog is the desk that keeps those rules, including when another engine reads an Iceberg table.

Account roles and database roles

Snowflake access is role-based on every edition. A user has roles. A role has grants. ACCOUNTADMIN, SYSADMIN, and SECURITYADMIN exist so that loading data and writing policy are not the same badge. Secondary roles can be active in a session. Policies often call IS_ROLE_IN_SESSION so a secondary role still counts.

A database role lives in one database. You grant it to an account role. That keeps a schema's privileges from leaking across databases. In a catalog-linked database, database roles are one of the object types you are allowed to create.

CREATE DATABASE ROLE LEARN_DB.READER_DB;
GRANT USAGE ON DATABASE LEARN_DB TO DATABASE ROLE LEARN_DB.READER_DB;
GRANT USAGE ON SCHEMA LEARN_DB.PUBLIC TO DATABASE ROLE LEARN_DB.READER_DB;
GRANT SELECT ON ALL TABLES IN SCHEMA LEARN_DB.PUBLIC TO DATABASE ROLE LEARN_DB.READER_DB;
GRANT DATABASE ROLE LEARN_DB.READER_DB TO ROLE SYSADMIN;

Masking, row access, and tags

Masking policies and row access policies require Enterprise Edition or higher. A masking policy returns a possibly rewritten value for a column. A row access policy returns true or false for whether the row is visible. When both exist, Snowflake evaluates the row access policy first. The same column cannot appear in both policy signatures.

If a row policy looks up a mapping table, put that table in the same database as the protected table. That placement matters when the policy calls IS_DATABASE_ROLE_IN_SESSION.

Object tags exist on Standard Edition. The edition matrix says some tagging features require Enterprise. Tag-based masking needs the masking feature, so it needs Enterprise or higher. Tags are how you mark a column as an email address once and attach a policy to the tag.

CREATE OR REPLACE MASKING POLICY mask_email AS (val STRING) RETURNS STRING ->
  CASE
    WHEN IS_ROLE_IN_SESSION('PII_READER') THEN val
    ELSE '***'
  END;

Access history and Horizon

SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY is an Enterprise feature. It records which user read which columns. Account Usage views are not live. Snowflake documents a latency, so a query you ran a minute ago may be missing. Use Snowsight Query History for that query, and Account Usage for a later audit.

Snowflake Horizon Catalog is the governance surface over those controls. The Horizon docs describe policy enforcement, tags, access history, and Trust Center checks for risky grants. Horizon can also enforce masking and row access when an external engine such as Spark, Trino, or PyIceberg reads an Iceberg table through the Horizon Iceberg REST catalog. The external engine receives a scan plan that already applied the policy. Writes from those engines are limited to policy shapes the Scan Plan API documents, and other shapes can return HTTP 403.

Business Critical adds private connectivity, Tri-Secret Secure, and customer-managed keys. Do not claim those on a Standard trial. Differential privacy, aggregation policies, and projection policies are also Enterprise-and-above features and are separate from a basic mask.

Who sees the email column

Access flows from user to role to table, and a policy on the column decides what each role sees.

  1. Asha (user)A person who logs in
  2. ANALYST roleHolds the privileges
  3. customers.emailColumn with a masking policy
  4. HorizonPolicies, tags and access history

Step 1 of 3Privileges are granted to roles, never straight to people. Asha gets access by being granted the role.

GRANT SELECT ON TABLE customers TO ROLE analyst;
GRANT ROLE analyst TO USER asha;

Lab: a database role, then a mask if your edition allows it

The database role works on Standard. The masking policy fails on Standard with an edition error. That failure is the lesson. Enterprise trials can run the policy and then attach it to a column with ALTER TABLE ... MODIFY COLUMN ... SET MASKING POLICY.

USE DATABASE LEARN_DB;
CREATE DATABASE ROLE IF NOT EXISTS LEARN_DB.READER_DB;
GRANT USAGE ON SCHEMA LEARN_DB.PUBLIC TO DATABASE ROLE LEARN_DB.READER_DB;
GRANT SELECT ON TABLE LEARN_DB.PUBLIC.ORDERS TO DATABASE ROLE LEARN_DB.READER_DB;

CREATE OR REPLACE MASKING POLICY mask_email AS (val STRING) RETURNS STRING ->
  CASE
    WHEN IS_ROLE_IN_SESSION('PII_READER') THEN val
    ELSE '***'
  END;

Concept summary

Knowledge check

Which edition adds masking policies?

When both policies exist, which runs first?

What does Horizon Catalog add for Iceberg readers outside Snowflake?

Official sources

Checked against Snowflake documentation in October 2026. Product behavior still depends on your edition, cloud, and region.