3.2 Object Tagging, Tag-Based Masking & Audit Governance
Key Takeaways
- Object tags are first-class schema-level metadata objects with optional ALLOWED_VALUES that inherit hierarchically from Database to Schema to Table/View to Column.
- Tag-based masking policies bind a masking policy directly to a tag, automatically propagating masking logic to all existing and future tagged columns across an account matching the policy data type signature.
- Explicit column-level masking policies take strict precedence over tag-based masking policies, and direct column tags override table, schema, and database tags.
- SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY (Enterprise Edition) records direct and base objects and columns read by each query plus objects modified by writes, supporting column-level lineage; OBJECT_DEPENDENCIES and Snowsight lineage show upstream and downstream dependencies.
- Sensitive data classification (Enterprise Edition) assigns SNOWFLAKE.CORE.SEMANTIC_CATEGORY and PRIVACY_CATEGORY tags (IDENTIFIER, QUASI_IDENTIFIER, SENSITIVE) using classification profiles, the Trust Center, or SYSTEM$CLASSIFY; EXTRACT_SEMANTIC_CATEGORIES is a legacy function.
3.2 Object Tagging, Tag-Based Masking & Audit Governance
As data platforms scale to thousands of tables and millions of attributes, managing security policies column-by-column becomes unsustainable. Snowflake addresses this through Object Tagging and Tag-Based Masking Policies, combining declarative metadata classification with automatic security enforcement. Coupled with deep audit telemetry in ACCOUNT_USAGE.ACCESS_HISTORY, architects can implement end-to-end data governance, tracking data lineage and access across the enterprise.
Object Tagging Architecture
A Tag in Snowflake is a first-class, schema-level object that stores key-value pairs assigned to Snowflake securable objects. Tags facilitate data categorization, cost tracking, compliance auditing, and automated security policy enforcement.
-- Create a tag with restricted permissible values
CREATE OR REPLACE TAG governance_db.tags.confidentiality_level
ALLOWED_VALUES 'PUBLIC', 'INTERNAL', 'CONFIDENTIAL', 'RESTRICTED';
-- Assign a tag to an entire database
ALTER DATABASE raw_stage_db SET TAG governance_db.tags.confidentiality_level = 'INTERNAL';
-- Assign a tag to a specific column
ALTER TABLE sales_db.public.customers
MODIFY COLUMN tax_id SET TAG governance_db.tags.confidentiality_level = 'RESTRICTED';
Tag Inheritance Hierarchy
Tags follow Snowflake's hierarchical containment model. When a tag is set on a parent object, all child objects within that container automatically inherit the tag unless explicitly overridden at a lower level:
Account (Top Level)
└── Database Tag (Inherited by all Schemas, Tables, Columns)
└── Schema Tag (Overrides Database Tag)
└── Table / View Tag (Overrides Schema Tag)
└── Column Tag (Overrides Table Tag - Highest Precedence)
Architectural Note: While data containers (Databases, Schemas, Tables, Views, Columns) participate in hierarchical inheritance, non-containment objects like Virtual Warehouses, Users, and Roles can also be tagged for cost attribution and governance, but their tags do not cascade down to data objects.
Tag-Based Masking Policies
Tag-Based Masking eliminates the operational bottleneck of manually applying masking policies to individual columns. Instead of binding a policy to a column, the security administrator binds the masking policy to a Tag.
-- Create masking policy for strings
CREATE OR REPLACE MASKING POLICY governance_db.security_policies.mask_pii_string_tag
AS (val STRING) RETURNS STRING ->
CASE
WHEN IS_ROLE_IN_SESSION('LEGAL_COMPLIANCE') THEN val
ELSE '***MASKED_PII***'
END;
-- Create masking policy for numbers
CREATE OR REPLACE MASKING POLICY governance_db.security_policies.mask_pii_numeric_tag
AS (val NUMBER) RETURNS NUMBER ->
CASE
WHEN IS_ROLE_IN_SESSION('LEGAL_COMPLIANCE') THEN val
ELSE -999999
END;
-- Bind masking policies to the tag
ALTER TAG governance_db.tags.pii_type
SET MASKING POLICY governance_db.security_policies.mask_pii_string_tag,
MASKING POLICY governance_db.security_policies.mask_pii_numeric_tag;
Multi-Signature Tag Policies
Notice that a single Tag can hold multiple masking policies, provided each masking policy has a different data type signature (e.g., one for STRING, one for NUMBER, one for DATE). When a column is tagged:
- Snowflake evaluates the column's data type.
- Snowflake automatically assigns the matching masking policy from the tag's registered policy set.
- If no policy on the tag matches the column's data type, no masking policy is applied to that column.
Policy Precedence and Override Rules
The SnowPro Advanced: Architect exam frequently tests conflict resolution when multiple policies intersect on a single column. Snowflake resolves policy precedence using strict deterministic rules:
| Level | Scenario | Resolution |
|---|---|---|
| 1. Explicit Column Policy | A column has an explicit masking policy applied via MODIFY COLUMN ... SET MASKING POLICY, and the column (or its parent table) is tagged with a tag that has a masking policy. | Explicit column-level policy WINS. Tag-based masking is completely bypassed. |
| 2. Column Tag Policy | A column is directly tagged with a tag containing a masking policy, and its parent table/schema has a different tag containing a masking policy. | Column tag policy WINS. The lowest level in the containment hierarchy takes precedence. |
| 3. Table Tag Policy | A table has a tag with a masking policy, and the parent schema has a different tag with a masking policy. | Table tag policy WINS over schema and database tags for all untagged columns in that table. |
| 4. Tag Conflict at Same Level | Two different tags are assigned to the same column, and both tags have masking policies defined for the column's data type. | Compilation Error. Snowflake throws an error because two conflicting policies cannot apply to the same object at the same hierarchy level. |
Governance Telemetry: ACCESS_HISTORY and Object Lineage
Enterprise auditability requires proving not just who queried a table, but exactly which columns were accessed, whether data was derived through views, and how data moved between tables. The SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY view provides this capability.
Direct vs Indirect (Base) Column Access
ACCESS_HISTORY is an Enterprise Edition feature (latency up to about 3 hours, one year of history). It records access at a granular JSON level for every query executed in the account:
SELECT
query_id,
query_start_time,
user_name,
direct_objects_accessed,
base_objects_accessed,
objects_modified
FROM snowflake.account_usage.access_history
WHERE query_start_time >= DATEADD('day', -1, CURRENT_TIMESTAMP())
ORDER BY query_start_time DESC;
Understanding the JSON arrays inside ACCESS_HISTORY is critical:
direct_objects_accessed: Contains the objects explicitly referenced in the user's SQL text. If a user runsSELECT full_name FROM reporting_view, the direct object isreporting_view.base_objects_accessed: Contains the underlying physical tables and columns accessed to satisfy the query, tracing through views, dynamic tables, and UDFs. Even if the user queried a view,base_objects_accessedreveals the underlying physical table (customers.full_name).objects_modified: Records write lineage forINSERT,UPDATE,DELETE,MERGE, andCREATE TABLE AS SELECT (CTAS). It details the target table modified and the exact source columns used to populate it.
Querying Metadata and Lineage
To audit policy bindings and tag assignments across an entire Snowflake account:
-- Query all active policy references across the account
SELECT *
FROM snowflake.account_usage.policy_references
WHERE policy_kind = 'MASKING_POLICY';
-- Query tag assignments for tables and columns
SELECT *
FROM snowflake.account_usage.tag_references
WHERE tag_name = 'CONFIDENTIALITY_LEVEL';
-- Query object dependencies (upstream and downstream objects)
SELECT *
FROM snowflake.account_usage.object_dependencies
WHERE referencing_object_name = 'CUSTOMER_SUMMARY_MV';
Data Lineage and Dependencies
The blueprint expects you to know how to trace where data came from and what depends on it:
OBJECT_DEPENDENCIES(Account Usage) lists which objects reference which (for example, a view that depends on a table), so you can assess the impact of dropping or changing an object.ACCESS_HISTORY.OBJECTS_MODIFIEDcaptures column-level write lineage forINSERT ... SELECT,MERGE, andCREATE TABLE AS SELECT, showing which source columns populated which target columns.- Snowsight lineage visualizes upstream and downstream objects for a table or column in the object explorer, built from the same metadata.
| Governance View | Latency | Retention | Primary Use Case |
|---|---|---|---|
ACCOUNT_USAGE.ACCESS_HISTORY | Up to 45–180 minutes | 365 days (1 year) | Deep column-level read/write lineage, GDPR/HIPAA compliance access audits |
ACCOUNT_USAGE.POLICY_REFERENCES | Up to 120 minutes | 365 days | Account-wide inventory of all tables/columns governed by masking or row access policies |
ACCOUNT_USAGE.TAG_REFERENCES | Up to 120 minutes | 365 days | Audit trail of all tags assigned to databases, schemas, tables, and columns |
INFORMATION_SCHEMA.POLICY_REFERENCES | Real-time (0 latency) | Session/Current Scope | Operational checks when modifying policies; returns only objects within current database/schema |
Sensitive Data Classification
Sensitive data classification (Enterprise Edition) discovers personal data and tags it so governance controls can follow automatically. The current approaches are:
- Automatic classification with classification profiles — configured in the Trust Center (or with SQL), a profile controls how databases are classified, whether tags are applied automatically, and how system tags map to your own tags.
SYSTEM$CLASSIFY— a stored procedure you can call on a specific table, view, or materialized view, optionally assigning the recommended tags.- Legacy functions —
EXTRACT_SEMANTIC_CATEGORIESandASSOCIATE_SEMANTIC_CATEGORY_TAGSstill exist, but Snowflake recommends the newer methods.
Step 1: Classify an Object
-- Classify a table and apply the recommended system tags
CALL SYSTEM$CLASSIFY('hr_db.corp.employees', {'auto_tag': true});
A classification result conceptually looks like this for each column:
{
"base_salary": {
"privacy_category": "SENSITIVE",
"semantic_category": "SALARY"
},
"ssn": {
"privacy_category": "IDENTIFIER",
"semantic_category": "US_SSN"
},
"work_email": {
"privacy_category": "IDENTIFIER",
"semantic_category": "EMAIL"
}
}
Step 2: Understand Classification Taxonomy
Snowflake categorizes data into two core taxonomy dimensions:
PRIVACY_CATEGORY:IDENTIFIER: Data that directly identifies an individual (e.g., Name, Social Security Number, National ID, Phone Number, Email).QUASI_IDENTIFIER: Data that can uniquely identify an individual when combined with other attributes (e.g., Date of Birth, Gender, ZIP Code, Job Title).SENSITIVE: Highly confidential personal information that does not identify an individual by itself (e.g., Salary, Medical Diagnosis, Credit Score).
SEMANTIC_CATEGORY: The specific domain classification (e.g.,US_SSN,EMAIL,IBAN,NAME,IP_ADDRESS).
Step 3: Connect Classification Tags to Protection
Classification applies the system tags SNOWFLAKE.CORE.SEMANTIC_CATEGORY and SNOWFLAKE.CORE.PRIVACY_CATEGORY. A classification profile can also map those system tags to your own tags (for example, SEMANTIC_CATEGORY = 'NAME' → governance.tags.pii = 'Highly confidential'). Because tag-based masking policies attach to tags, newly classified columns can be protected without column-by-column DDL.
Architect Tip: Treat automated classification as a detection control, not proof of compliance. Review results, handle custom categories specific to your business, and keep a manual process for data the classifier cannot infer.
A table column 'tax_identifier' has an explicit Dynamic Data Masking policy 'mask_tax_id' applied directly. Subsequently, an administrator applies the tag 'pii_restricted' to the column. The 'pii_restricted' tag has a tag-based masking policy 'mask_general_pii' attached. When an unauthorized user queries 'tax_identifier', which masking policy will Snowflake evaluate?
An analytics engineer creates a secure view 'vw_customer_spend' over the base table 'fact_orders'. When a business user executes 'SELECT total_spend FROM vw_customer_spend', how are the accessed objects recorded in the SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY view?
An architect wants Snowflake to discover columns containing personal data in a new HR database and automatically apply privacy tags so that existing tag-based masking policies protect them. Which approach reflects current Snowflake recommendations?