Snowflake敏感数据导出治理:DMP与自定义护栏
DataHot 速览
文章介绍在 Snowflake 中治理健康相关数据对外共享的敏感数据导出。方案结合原生 Data Movement Policies(需 Enterprise Edition 或更高)与基于 Snowflake SQL 的自定义导出服务,用于阻止风险字段组合并动态抑制小计数(如 5、10、20 阈值)。两层统一写入审计轨迹,并由 Cortex Agent 用自然语言回答治理问题,同时防止 AI 代理读取受监管数据。原型数据均为合成数据。
为什么值得关注:对数据从业者,展示如何用 Snowflake 原生策略加自定义护栏控制敏感数据导出、审计和 AI Agent 访问,适合数据治理落地参考。
本文目录 19 节
译文
AI 逐段翻译如何使用 Snowflake 原生控制措施以及一个小的配置驱动扩展,阻止高风险导出、抑制小单元格,并让 AI 智能体远离受监管数据。

引言
与外部合作伙伴共享健康相关数据的组织面临一种熟悉的矛盾。数据之所以有价值,正是因为它细粒度,但一旦数据离开平台,这种细粒度就会带来重识别风险。当数据分布在数十个下游合作伙伴中,而这些合作伙伴越来越多地使用 AI 智能体来探索数据时,治理就不能再依赖人工审查,也不能依赖信任每个消费者会正确行事。
本文描述了一个为常见场景构建的原型:一个数据提供方与外部合作伙伴网络共享健康相关数据。该提供方的治理要求具有行业典型性:
- 阻止以可能重识别个人的方式组合字段的导出。
- 抑制小计数,阈值可能因合作伙伴而异(例如 5、10 或 20)。
- 由中央治理团队对每个合作伙伴应用一致的治理。
- 维护被阻止、告警或批准内容的完整审计追踪。
- 允许特定、已批准的角色进行例外处理。
- 阻止 AI 智能体读取敏感数据。
该解决方案结合了两层。第一层是 Snowflake 数据移动策略(DMP),这是一项原生功能,可根据通过标签附加到数据上的规则来限制、告警或阻止数据移动。第二层是一个小的 自定义导出服务,使用 Snowflake SQL 编写,用于处理 DMP 本身无法满足的要求:字段组合规则和动态小单元格抑制。两层都写入同一个审计追踪,并且一个 Cortex Agent 位于该审计追踪之上,用自然语言回答治理问题。
原型中的所有数据均为合成数据。
注意: 数据移动策略需要 Snowflake Enterprise Edition 或更高版本。请查阅 Snowflake 文档 以了解您账户中的当前可用性和行为。
架构概览
该原型组织在单个数据库内的五个模式中:

四个角色代表所涉及的人物角色:

下图展示了这些部分如何组合在一起:每个请求都会经过原生数据移动策略,受治理的导出还会额外经过自定义护栏层,并且两层都会将各自的决策记录到同一个审计追踪中。

第 1 层:原生数据移动策略
数据移动策略由三个构建块组成:规则、策略 和 标签。
规则
一条规则最多管理一种 移动类型,并返回该移动可携带的最大行数。返回值为 0 会阻止该移动,正整数设置上限,NULL 则不施加限制。支持的移动类型包括:

由于规则主体是 SQL 表达式,它可以通过 SYS_CONTEXT 读取会话上下文。这就是基于角色的例外实现方式。以下规则阻止除已批准导出角色之外的所有角色将健康数据导出到外部存储:
CREATE OR REPLACE DATA MOVEMENT RULE PHI_EXT_STAGE_BLOCK
TYPE = 'COPY_INTO_EXTERNAL_STAGE'
MAX_ROWS AS () RETURNS INTEGER
-> (
CASE
WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') IN ('APPROVED_EXPORT_ROLE') THEN NULL
ELSE 0
END
)
COMMENT = 'Block PHI exports to external storage except for the approved export role';规则也可以限制量,而不是直接阻止。此规则允许合作伙伴分析人员在 Snowsight 中最多预览 50 行健康数据:
CREATE OR REPLACE DATA MOVEMENT RULE PHI_SNOWSIGHT_CAP
TYPE = 'SNOWSIGHT_UI'
MAX_ROWS AS () RETURNS INTEGER
-> (
CASE
WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE')
IN ('GOVERNANCE_ADMIN', 'APPROVED_EXPORT_ROLE') THEN NULL
ELSE 50
END
);而此规则让 AI 智能体完全远离健康数据:
CREATE OR REPLACE DATA MOVEMENT RULE PHI_AGENT_BLOCK
TYPE = 'AGENT_ACCESS'
MAX_ROWS AS () RETURNS INTEGER
-> (
CASE
WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE')
IN ('GOVERNANCE_ADMIN', 'APPROVED_EXPORT_ROLE') THEN NULL
ELSE 0
END
)
COMMENT = 'AI agents and MCP clients may not read PHI';对于每条语句,Snowflake 都会按固定优先级顺序恰好分配一种主要移动类型:外部暂存区、内部暂存区、智能体访问、Snowsight,然后是以编程方式获取。因此,即使 AI 智能体发出 COPY INTO,仍会作为复制操作受到治理。
策略
一个策略将规则分为两个层级。强制执行规则会阻止操作,而 告警规则允许操作并记录事件。一个策略在每个层级中每种移动类型最多可包含一条规则。
CREATE OR REPLACE DATA MOVEMENT POLICY PHI_DMP
ENFORCE_RULES = (
PHI_EXT_STAGE_BLOCK,
PHI_INT_STAGE_BLOCK,
PHI_AGENT_BLOCK,
PHI_UI_DOWNLOAD_BLOCK,
PHI_SNOWSIGHT_CAP,
PHI_PROG_FETCH_CAP
)
ALERT_RULES = (PHI_SNOWSIGHT_ALERT)
COMMENT = 'Export guardrails for modeled health data';在原型中,健康数据(PHI)的策略是严格的,而消费者标识符(PII)的策略会阻止外部导出和智能体访问,但对批量编程拉取仅 告警。因此,严格程度可以按数据类别进行调整。
标签
策略不会直接附加到表上。它们附加到 标签,而标签应用于表或列。这让保护跟随数据,而不是依赖数据当前所在的对象。
CREATE OR REPLACE TAG PHI_TAG
ALLOWED_VALUES 'PHI_MODELED'
PROPAGATE = ON_DEPENDENCY_AND_DATA_MOVEMENT;
ALTER TAG PHI_TAG SET DATA MOVEMENT POLICY PHI_DMP;
-- PII_TAG is created the same way (ALLOWED_VALUES 'DIRECT_ID', 'QUASI_ID') and carries PII_DMP
ALTER TABLE RAW.HEALTH_INSIGHTS SET TAG PHI_TAG = 'PHI_MODELED';
ALTER TABLE RAW.CONSUMERS MODIFY COLUMN email SET TAG PII_TAG = 'DIRECT_ID';有两个细节值得强调:
- 携带数据移动策略的标签必须启用传播。 尝试将策略附加到 PROPAGATE = NONE 的标签会失败,并报错指出只有 ON_DATA_MOVEMENT 或 ON_DEPENDENCY_AND_DATA_MOVEMENT 有效。在实践中这是可取的:由带标签数据派生的表,例如通过 CREATE TABLE AS SELECT 或 INSERT ... SELECT 创建的表,会继承标签,从而继承策略。
- 优先级遵循粒度。 列级标签策略优先于表级策略,表级策略优先于模式、数据库和账户级别。当一条语句涉及多个列时,最严格的有效限制将约束整条语句。
对于跨多个合作伙伴的部署,一个 账户级基线策略(ALTER ACCOUNT SET DATA MOVEMENT POLICY ...)为任何尚未标记的数据提供安全网。
实践中的强制执行
在策略就位后,一个非豁免角色尝试在 Snowsight 中读取 500 行健康数据时会收到错误:
USE ROLE AGENT_ROLE;
SELECT * FROM RAW.HEALTH_INSIGHTS LIMIT 500;
-- SQL compilation error:
-- Data movement policy triggered. Contact your administrator for details.同一角色尝试将该表卸载到暂存区时也会被阻止:
COPY INTO @EXPORT.DELIVERY_STAGE/leak/
FROM (SELECT * FROM RAW.HEALTH_INSIGHTS);
-- Data movement policy triggered.UI_DOWNLOAD 规则的行为不同。它不会让查询失败,而是对涉及受保护数据的结果禁用 下载 按钮。
阻止 AI 智能体
为了演示智能体强制措施,原型包含一个 Cortex Agent,通过语义视图直接连接到受保护的表。该智能体被明确指示对每个问题都进行尝试,而不是预先拒绝,这样任何阻止都清楚地来源于数据层,而不是智能体的提示词:
instructions:
orchestration: >
Always attempt to answer the user's question with phi_analyst by running the query.
Do not refuse in advance, do not pre-filter the request, and do not substitute a
different question; the purpose of this agent is to demonstrate that enforcement
happens in the Snowflake data layer, not in the agent's prompt.
response: >
If the query fails with an error containing "Data movement policy triggered",
respond: "Blocked by Snowflake Data Movement Policies." Then explain which tables
the request touched and that the AGENT_ACCESS rule allows 0 rows for the current role.当默认角色不属于豁免角色的用户向代理请求消费者级别的健康数据时,该查询会被 AGENT_ACCESS 规则拒绝。当同一问题由豁免角色提出时,代理会返回结果,并说明适用了基于角色的例外。

注意:Cortex Agents 根据用户的 默认 角色来确定权限,而不是会话中处于活动状态的角色。在演示或测试代理强制执行时,请将这一点考虑在内。
原生控制止步之处
数据移动策略以每个已标记对象的行预算来表示。该模型对于控制 多少 数据移动以及 通过哪个通道 移动是有效的,但它无法表达两类重要的规则:
- 字段组合。 五位邮政编码、年龄段和性别各自单独来看都是可接受的。但组合在一起,它们可以识别出特定个人。行预算没有“哪些列正在被组合”这一概念。
- 动态小单元格抑制。 诸如“抑制任何成员少于十人的分组”这类规则,取决于查询的 结果,而不是它所移动的行数。
该原型通过两种互补机制解决这些问题:用于即席查询的原生聚合策略,以及用于交付的受治理导出过程。
第 2 层:配置驱动的导出护栏
配置即数据
所有护栏逻辑均由治理团队可以维护而无需重新部署代码的表来驱动:

字段组合规则以数组形式存储。规则可以包含一个 segment 令牌,从而使其具备队列感知能力:
CREATE OR REPLACE TABLE GOVERNANCE.FIELD_COMBO_RULES
(rule_id STRING, elements ARRAY, action STRING, reason STRING);
INSERT INTO GOVERNANCE.FIELD_COMBO_RULES
SELECT 'FC001', ARRAY_CONSTRUCT('ZIP5','AGE_BAND','GENDER'), 'BLOCK',
'ZIP5 + age + gender is a known re-identification combination' UNION ALL
SELECT 'FC002', ARRAY_CONSTRUCT('ZIP5','ETHNICITY','PAYER_TYPE'), 'BLOCK',
'Fine geography + ethnicity + payer is too identifying' UNION ALL
SELECT 'FC003', ARRAY_CONSTRUCT('SEG:RARE_DISEASE','ZIP5'), 'BLOCK',
'Rare-disease cohorts cannot be cut below ZIP3' UNION ALL
SELECT 'FC004', ARRAY_CONSTRUCT('SEG:ONCOLOGY','ETHNICITY'), 'BLOCK',
'Oncology x ethnicity requires privacy review';受治理的导出过程
合作伙伴不直接导出数据。相反,他们调用单个存储过程 SAFE_EXPORT,该过程执行六个步骤:
- 查找合作伙伴的配置和请求的 segment。
- 根据白名单验证请求的维度,该白名单会拒绝诸如 email 之类的直接标识符。
- 评估字段组合规则。
- 按请求的粒度聚合队列。
- 抑制任何低于合作伙伴最小规模的单元格。
- 可选地将结果交付到暂存区,但仅限于已获准交付的合作伙伴。
每条路径都会写入审计日志。
字段组合检查使用集合逻辑。如果请求包含任何规则的 全部 元素,则该请求会被阻止:
SELECT MIN(rule_id), MIN_BY(reason, rule_id)
INTO :combo_rule, :combo_reason
FROM GOVERNANCE.FIELD_COMBO_RULES
WHERE ARRAY_SIZE(
ARRAY_INTERSECTION(elements, ARRAY_APPEND(:dims, 'SEG:' || UPPER(:P_SEGMENT)))
) = ARRAY_SIZE(elements);小单元格抑制在聚合之后应用,使用为调用合作伙伴配置的阈值:
UPDATE EXPORT.EXPORT_RESULTS
SET suppressed = TRUE, member_count = NULL
WHERE request_id = :rid
AND member_count < :min_cell;由于该过程动态构建其聚合查询,注入安全性很重要。只有白名单列名才会进入动态 SQL,segment 谓词由治理团队拥有而不是由调用方提供,并且合作伙伴标识符作为绑定变量传递:
sql_txt := 'INSERT INTO EXPORT.EXPORT_RESULTS ' ||
'SELECT ''' || rid || ''', ?, OBJECT_CONSTRUCT_KEEP_NULL(' || obj_args || '), COUNT(*), FALSE ' ||
'FROM CURATED.COHORT_V WHERE ' || predicate ||
' GROUP BY ' || dim_list;
EXECUTE IMMEDIATE :sql_txt USING (P_PARTNER_ID);调用该过程很简单:
-- Coarse request: approved
CALL EXPORT.SAFE_EXPORT('PTR002', 'DIABETES', ['AGE_BAND','GENDER'], FALSE);
-- Quasi-identifier combination: blocked by FC001
CALL EXPORT.SAFE_EXPORT('PTR002', 'GLP1', ['ZIP5','AGE_BAND','GENDER'], FALSE);
-- Fine-grained cut for a partner with a minimum cell size of 20: approved with suppression
CALL EXPORT.SAFE_EXPORT('PTR003', 'RARE_DISEASE', ['ZIP3','ETHNICITY','PAYER_TYPE'], FALSE);在原型中,第三个请求产生了 320 个单元格,其中 57 个低于合作伙伴的阈值 20,并在返回任何内容之前被抑制。该过程的响应将此阈值称为 k,即最小单元格规模的常规名称:
{
"decision": "APPROVED_WITH_SUPPRESSION",
"min_cell_size": 20,
"total_cells": 320,
"suppressed_cells": 57,
"message": "57 of 320 cells suppressed (k < 20)"
}

两层叠加
该设计的一个重要特性是,自定义服务不会绕过原生控制。在过程内执行的语句仍会由数据移动策略针对 调用方的 会话进行评估。根据文档,即使该过程是从 Snowsight 调用的,存储过程内部的语句也会被归类为 PROGRAMMATIC_FETCH。在原型中,当同一过程在非豁免角色下从 AI 代理会话中调用时,原生 AGENT_ACCESS 规则阻止了它。因此,自定义层在不打开绕过现有控制的新路径的情况下增加了保护。
用于即席查询的原生小单元格保护
直接探索精选队列视图的合作伙伴会受到 聚合策略 的约束。行级读取会被直接拒绝,任何成员少于五人的分组都会被折叠为剩余分组:
CREATE OR REPLACE AGGREGATION POLICY GOVERNANCE.MIN_GROUP_5
AS () RETURNS AGGREGATION_CONSTRAINT ->
CASE
WHEN CURRENT_ROLE() IN ('GOVERNANCE_ADMIN', 'APPROVED_EXPORT_ROLE')
THEN NO_AGGREGATION_CONSTRAINT()
ELSE AGGREGATION_CONSTRAINT(MIN_GROUP_SIZE => 5)
END;
ALTER VIEW CURATED.COHORT_V
SET AGGREGATION POLICY GOVERNANCE.MIN_GROUP_5;
USE ROLE PARTNER_ANALYST;
SELECT consumer_id, zip5 FROM CURATED.COHORT_V LIMIT 5;
-- SQL compilation error: Aggregation policy violation: aggregation required.统一审计跟踪
两层都会产生证据。原生违规记录在 SNOWFLAKE.ACCOUNT_USAGE.DATA_MOVEMENT_VIOLATIONS 视图中,该视图捕获查询、被强制执行或发出警报的规则、用户以及涉及的表。自定义服务写入其自己的 EXPORT_AUDIT_LOG。单个视图将两者合并,并用产生每个事件的层来标记它。
ENFORCED_POLICY 和 ALERTED_POLICIES 列都是 VARCHAR,但在原型账户中它们包含 JSON。ENFORCED_POLICY 持有一个对象,ALERTED_POLICIES 持有一个对象数组,因为一条语句可以触发多个警报规则:
ENFORCED_POLICY: {"policy":"<db>.GOVERNANCE.PHI_DMP","rule":"<db>.GOVERNANCE.PHI_SNOWSIGHT_CAP"}
ALERTED_POLICIES: [{"policy":"<db>.GOVERNANCE.PHI_DMP","rule":"<db>.GOVERNANCE.PHI_SNOWSIGHT_ALERT"}]因此该视图解析这两列。它使用 TRY_PARSE_JSON,以便意外格式产生 NULL 而不是错误,并且对于仅警报事件,它会回退到第一个警报规则:
CREATE OR REPLACE VIEW GOVERNANCE.UNIFIED_AUDIT_V AS
SELECT 'NATIVE_DMP' AS control_layer,
v.timestamp AS event_ts,
v.user_name,
NULL::STRING AS partner_id,
v.movement_type AS event_type,
IFF(v.enforced_policy IS NOT NULL, 'BLOCKED', 'ALERTED') AS outcome,
COALESCE(TRY_PARSE_JSON(v.enforced_policy):rule::STRING,
TRY_PARSE_JSON(v.alerted_policies)[0]:rule::STRING) AS rule_name,
ARRAY_TO_STRING(
TRANSFORM(TRY_PARSE_JSON(v.alerted_policies)::ARRAY, a -> a:rule::STRING),
', ') AS alerted_rules,
v.query_id AS event_id
FROM SNOWFLAKE.ACCOUNT_USAGE.DATA_MOVEMENT_VIOLATIONS v
UNION ALL
SELECT 'CUSTOM_GUARDRAIL',
l.request_ts,
l.user_name,
l.partner_id,
'SAFE_EXPORT',
CASE WHEN l.decision LIKE 'BLOCKED%' THEN 'BLOCKED'
WHEN l.decision = 'APPROVED_WITH_SUPPRESSION' THEN 'SUPPRESSED'
WHEN l.decision = 'DELIVERY_DENIED' THEN 'DELIVERY_DENIED'
ELSE 'APPROVED' END,
COALESCE(l.matched_rule, l.decision),
NULL::STRING,
l.request_id
FROM GOVERNANCE.EXPORT_AUDIT_LOG l;由于文档仅将这些列描述为完全限定名称,在依赖此视图之前,请在自己的账户中确认格式:
SELECT enforced_policy, alerted_policies FROM SNOWFLAKE.ACCOUNT_USAGE.DATA_MOVEMENT_VIOLATIONS LIMIT 5应注意原生视图的两个特点。首先,延迟可能达到数小时,而自定义日志立即可用。其次,UI_DOWNLOAD 强制执行不会生成违规记录,因为它禁用下载按钮而不是拒绝查询。

位于审计跟踪之上的治理助手
由于审计数据不包含健康或个人信息,因此可以安全地将其暴露给 AI 助手。该原型包含一个覆盖审计事件、合作伙伴配置和每条规则目录的语义视图。它还包含一个 Cortex Agent,可回答诸如“哪些字段组合规则触发最频繁?”或“哪些合作伙伴正在触及他们的限制?”之类的问题。
该语义视图包含同义词和已验证查询,以便常见的治理问题能够得到一致回答:
AI_VERIFIED_QUERIES (
vq_combo_rules AS (
QUESTION 'Which field-combination rules fired most often?'
ONBOARDING_QUESTION TRUE
SQL 'SELECT e.rule_key, r.rule_description, COUNT(e.event_id) AS times_blocked
FROM __events e LEFT JOIN __rules r ON e.rule_key = r.rule_key
WHERE e.rule_key LIKE ''FC%''
GROUP BY e.rule_key, r.rule_description
ORDER BY times_blocked DESC NULLS LAST'
)
)这在单一平台内产生了一个有启发性的对比。治理助手被数据移动策略允许,因为它只读取元数据,而连接到健康数据的代理则被阻止。相同的策略纯粹基于底层数据的分类而产生不同的结果。
经验教训
此次构建中的若干观察结果很可能适用于任何类似实现。
AI 工具会话被归类为代理访问。 该原型是在 AI 编码助手的帮助下构建的。一旦 AGENT_ACCESS 规则就位,Snowflake 便阻止了该助手的语句,包括以管理员角色发出的语句,直到管理员角色被明确豁免为止。这是预期行为,它迫使人们就哪些角色可以通过 AI 工具管理敏感数据做出明确决定。
对于带有策略的标签,传播是强制性的。 设计标签时要考虑传播。派生表将继承保护,这通常是可取的,但在推广之前应当了解这一点。
行阈值按语句进行评估。 一个执意为之的用户原则上可以通过许多条各自低于上限的小语句来逐页翻阅数据。请将上限与警报规则配对使用,并监控审计跟踪中反复出现的小量拉取,或在受治理的服务中强制执行累计配额。
强制执行与记录是相互独立的。 文档指出,在极少数情况下,即使强制执行已发生,违规行为也可能未被记录。请将审计视图视为活动的证据,而非强制执行机制本身。
记住代理的默认角色行为。 代理使用用户的默认角色来评估权限,这会影响测试和你设计的例外情况。
结论
原生数据移动策略无需自定义代码即可覆盖典型导出治理计划的大部分内容:特定渠道的阻止和上限、警报、基于角色的例外、代理限制以及审计跟踪,所有这些都通过标签附加到数据上,使保护随之而行。其余需求,即语义字段组合规则和动态小单元格抑制,可以通过一个紧凑的、配置驱动的服务来解决,该服务在 Snowflake 内部运行,并同样受制于相同的原生控制。
其结果是一种分层设计,其中没有任何单一控制必须完美无缺,治理团队无需工程工作即可调整阈值和规则,并且每个决策都记录在一个地方。
如果你有兴趣在自己的环境中应用这种方法,或对数据移动策略有疑问,请联系你的 Snowflake 客户团队。
参考资料
- Snowflake 文档:数据移动策略
- CREATE DATA MOVEMENT RULE
- CREATE DATA MOVEMENT POLICY
- CREATE SEMANTIC VIEW
- 创建和管理 Cortex Agents
在 Snowflake 中治理敏感数据导出:数据移动策略与自定义护栏层 最初发表于 Snowflake Builders Blog: Data Engineers, App Developers, AI, & Data Science 于 Medium 上,人们正在那里通过高亮并回应这个故事来继续对话。
这篇内容对你有用吗?
反馈只用于改善内容筛选,不等同于收藏