[New Feature] Data Movement Policies are now generally available, so I tried controlling data unloading, downloading, and fetching

[New Feature] Data Movement Policies are now generally available, so I tried controlling data unloading, downloading, and fetching

Snowflake's data exfiltration prevention feature "Data Movement Policies" became generally available in August 2026. This article provides a detailed explanation through hands-on verification, covering everything from policy creation to application at the account and tag levels, block and alert behavior, and violation monitoring.
2026.08.26

This page has been translated by machine translation. View original

This is Kawabata.

On August 19, 2026, Data Movement Policies became generally available (GA).
Operations such as unloading, downloading, displaying on screen, and fetching data via programs or agents can be controlled in three levels: "allow / allow with alert / block."

This article covers everything from creating policies to applying them to tags and accounts, verifying block and alert behavior, and checking violations in monitoring views.

https://docs.snowflake.com/en/user-guide/data-movement-policies

【Update】
I was selected as a finalist in the "RISING COMMUNITY LEADER OF THE YEAR" category, APJ slot, of the Snowflake Community Awards.
Please see the link below for details.

https://dev.classmethod.jp/articles/snowflake-community-awards-finalist-activities-review/

Overview of Data Movement Policies

A Data Movement Policy is a data exfiltration prevention feature that controls movement and fetch operations that could lead to data being taken out. There are six types of movement types that can be controlled.

TYPE Target
COPY_INTO_EXTERNAL_STAGE Unloading to an external stage
COPY_INTO_INTERNAL_STAGE Unloading to an internal stage
SNOWSIGHT_UI Data access originating from Snowsight (worksheets, etc.)
UI_DOWNLOAD Query result downloads on the relevant Snowsight screen
PROGRAMMATIC_FETCH Fetching via drivers / connectors / SnowSQL / Snowflake CLI / SQL API / stored procedures
AGENT_ACCESS Data access via agents / MCP clients

For a single statement, one primary movement type is evaluated. It is classified in the fixed order of COPY_INTO_EXTERNAL_STAGE > COPY_INTO_INTERNAL_STAGE > AGENT_ACCESS > SNOWSIGHT_UI > PROGRAMMATIC_FETCH, and UI_DOWNLOAD alone is evaluated independently after the query completes. Fetches within stored procedures are classified as PROGRAMMATIC_FETCH even when called from Snowsight.

Data Sharing and replication are not included in the list of supported movement types. The focus of control is "unloading, downloading, and extraction via programs or agents."

Two-layer structure of Rules and Policies

The components are divided into two layers.

  • Data Movement Rule: Defines a restriction for one movement type using a SQL expression that returns MAX_ROWS
  • Data Movement Policy: Bundles multiple Rules into ENFORCE_RULES (block on excess) and ALERT_RULES (allow on excess but record violation), then applies them to a tag or account

The meaning of the MAX_ROWS return value is as follows:

Return value Meaning
NULL Unlimited (allow)
0 Block
Positive integer Maximum number of rows to allow

Within the expression, SYS_CONTEXT can be used to reference the role, movement type, and connection path (Snowflake CLI, SQL API, etc.), allowing conditional branching such as "allow only specific roles" or "limit row count for specific paths."

Application scope and priority

Policies are not applied directly to objects, but rather via tags or to the entire account. When multiple policies apply, the more granular scope takes priority.

  1. Column-level tag
  2. Table-level tag
  3. Schema-level tag
  4. Database-level tag
  5. Account level (baseline)

Prerequisites

  • Enterprise Edition or higher
  • Creating Rules / Policies: CREATE DATA MOVEMENT RULE / CREATE DATA MOVEMENT POLICY privileges on the schema
  • Applying to tags / accounts: APPLY DATA MOVEMENT POLICY privilege on the account

Testing was conducted between August 20 and 26, 2026, on an account in the AWS Tokyo region.

Preparation

Create a DB for testing, a dummy salary table (3,000 rows), a tag, and an analyst role.

Preparation
USE ROLE ACCOUNTADMIN;

CREATE OR REPLACE DATABASE DMP_TEST_DB;
CREATE SCHEMA DMP_TEST_DB.GOVERNANCE;
CREATE SCHEMA DMP_TEST_DB.HR;

-- Dummy salary table (3,000 rows)
CREATE OR REPLACE TABLE DMP_TEST_DB.HR.EMPLOYEE_SALARY AS
SELECT
  SEQ4() + 1                                        AS EMP_ID,
  'EMP_' || LPAD(TO_VARCHAR(SEQ4() + 1), 5, '0')    AS EMP_NAME,
  'emp' || TO_VARCHAR(SEQ4() + 1) || '@example.com' AS EMAIL,
  UNIFORM(4000000, 12000000, RANDOM())              AS SALARY,
  DECODE(MOD(SEQ4(), 4), 0, 'SALES', 1, 'HR', 2, 'ENGINEERING', 3, 'FINANCE') AS DEPARTMENT
FROM TABLE(GENERATOR(ROWCOUNT => 3000));

-- Table without tags for priority verification (100 rows)
CREATE OR REPLACE TABLE DMP_TEST_DB.HR.OFFICE_LOCATION AS
SELECT
  SEQ4() + 1 AS LOCATION_ID,
  'OFFICE_' || TO_VARCHAR(SEQ4() + 1) AS LOCATION_NAME
FROM TABLE(GENERATOR(ROWCOUNT => 100));

CREATE OR REPLACE TAG DMP_TEST_DB.GOVERNANCE.PII_TAG;

-- Analyst role for testing
CREATE OR REPLACE ROLE DMP_ANALYST;
GRANT USAGE ON WAREHOUSE COMPUTE_WH TO ROLE DMP_ANALYST;
GRANT USAGE ON DATABASE DMP_TEST_DB TO ROLE DMP_ANALYST;
GRANT USAGE ON ALL SCHEMAS IN DATABASE DMP_TEST_DB TO ROLE DMP_ANALYST;
GRANT SELECT ON ALL TABLES IN DATABASE DMP_TEST_DB TO ROLE DMP_ANALYST;
GRANT ROLE DMP_ANALYST TO USER <test user>;

Creating Rules

Create a Rule for each movement type. Using SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') to check the executing role, ACCOUNTADMIN is unrestricted (NULL) while all other roles are restricted.

USE SCHEMA DMP_TEST_DB.GOVERNANCE;

-- Block all roles except ACCOUNTADMIN from unloading to internal stage
CREATE OR REPLACE DATA MOVEMENT RULE R_COPY_INTERNAL_BLOCK
  TYPE = 'COPY_INTO_INTERNAL_STAGE'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') = 'ACCOUNTADMIN' THEN NULL ELSE 0 END
  );

-- Programmatic fetch: unlimited for ACCOUNTADMIN / up to 1,000 rows for others
CREATE OR REPLACE DATA MOVEMENT RULE R_PROG_FETCH_LIMIT
  TYPE = 'PROGRAMMATIC_FETCH'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') = 'ACCOUNTADMIN' THEN NULL ELSE 1000 END
  );

-- Block Snowsight download button for all roles except ACCOUNTADMIN
CREATE OR REPLACE DATA MOVEMENT RULE R_UI_DOWNLOAD_BLOCK
  TYPE = 'UI_DOWNLOAD'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') = 'ACCOUNTADMIN' THEN NULL ELSE 0 END
  );

-- Block Snowsight worksheet result display for all roles except ACCOUNTADMIN
CREATE OR REPLACE DATA MOVEMENT RULE R_SNOWSIGHT_BLOCK
  TYPE = 'SNOWSIGHT_UI'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') = 'ACCOUNTADMIN' THEN NULL ELSE 0 END
  );

-- Block all agent / MCP client access
CREATE OR REPLACE DATA MOVEMENT RULE R_AGENT_BLOCK
  TYPE = 'AGENT_ACCESS'
  MAX_ROWS AS () RETURNS INTEGER
  -> (0);

-- ALERT: record violation if programmatic fetch exceeds 500 rows (still allow)
CREATE OR REPLACE DATA MOVEMENT RULE R_PROG_FETCH_ALERT
  TYPE = 'PROGRAMMATIC_FETCH'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') = 'ACCOUNTADMIN' THEN NULL ELSE 500 END
  );

Creating a Policy

Bundle the Rules into ENFORCE_RULES and ALERT_RULES.

CREATE OR REPLACE DATA MOVEMENT POLICY DMP_PII_POLICY
  ENFORCE_RULES = (R_COPY_INTERNAL_BLOCK, R_PROG_FETCH_LIMIT, R_UI_DOWNLOAD_BLOCK, R_SNOWSIGHT_BLOCK, R_AGENT_BLOCK)
  ALERT_RULES = (R_PROG_FETCH_ALERT)
  COMMENT = 'PII guard: block unload/download/agent, fetch <= 1000, alert > 500';

DESCRIBE DATA MOVEMENT POLICY DMP_PII_POLICY;

2026-08-26_10h12_57

Trying It Out

Applying to a tag: a propagation mode that includes data movement is required

Set the DMP on the created tag. If the tag's propagation mode remains at the default (PROPAGATE = NONE) when the policy is set, an error (503509) is returned and the operation is rejected, so set a propagation mode that includes data movement first.

ALTER TAG DMP_TEST_DB.GOVERNANCE.PII_TAG SET PROPAGATE = ON_DEPENDENCY_AND_DATA_MOVEMENT;
ALTER TAG DMP_TEST_DB.GOVERNANCE.PII_TAG SET DATA MOVEMENT POLICY DMP_TEST_DB.GOVERNANCE.DMP_PII_POLICY;

-- Apply tag to the salary column
ALTER TABLE DMP_TEST_DB.HR.EMPLOYEE_SALARY MODIFY COLUMN SALARY
  SET TAG DMP_TEST_DB.GOVERNANCE.PII_TAG = 'salary';

Checking the association between the policy and tag using POLICY_REFERENCES shows it is ACTIVE.

SELECT POLICY_NAME, REF_ENTITY_NAME, REF_ENTITY_DOMAIN, POLICY_STATUS
FROM TABLE(DMP_TEST_DB.INFORMATION_SCHEMA.POLICY_REFERENCES(
  REF_ENTITY_NAME => 'DMP_TEST_DB.GOVERNANCE.PII_TAG', REF_ENTITY_DOMAIN => 'TAG'));

2026-08-26_10h35_17

Block behavior via tag

After the table tag takes effect, try each operation as DMP_ANALYST. First, unloading to an internal stage is blocked.

-- Run as DMP_ANALYST
COPY INTO @~/dmp_test/ FROM DMP_TEST_DB.HR.EMPLOYEE_SALARY;

2026-08-26_10h47_34

The programmatic fetch row limit (1,000 rows) behaved exactly as expected at the boundary values.

-- Run as DMP_ANALYST
SELECT * FROM DMP_TEST_DB.HR.EMPLOYEE_SALARY LIMIT 1001;  -- Blocked
SELECT * FROM DMP_TEST_DB.HR.EMPLOYEE_SALARY LIMIT 1000;  -- Success (exactly at the limit)

2026-08-26_23h07_59

2026-08-26_23h09_24

Applying to an account: enforced immediately

Next, verify a baseline policy for the entire account. Create a Rule that blocks all programmatic fetches for roles other than ACCOUNTADMIN, and apply it to the account.

CREATE OR REPLACE DATA MOVEMENT RULE R_BASELINE_PROG_FETCH_BLOCK
  TYPE = 'PROGRAMMATIC_FETCH'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') = 'ACCOUNTADMIN' THEN NULL ELSE 0 END
  );

CREATE OR REPLACE DATA MOVEMENT POLICY DMP_ACCOUNT_BASELINE
  ENFORCE_RULES = (R_BASELINE_PROG_FETCH_BLOCK);

ALTER ACCOUNT SET DATA MOVEMENT POLICY DMP_TEST_DB.GOVERNANCE.DMP_ACCOUNT_BASELINE;

If an account-level policy is already set, append FORCE at the end to replace it.

Caution: Make sure the Rules in the policy applied to the account include a condition that excludes the admin role used for recovery (ACCOUNTADMIN in this example). If MAX_ROWS = 0 is set for all roles, fetches from your own session will also be blocked. It is safe to keep ALTER ACCOUNT UNSET DATA MOVEMENT POLICY; handy for recovery.

Immediately after applying, querying from the Snowflake CLI as the DMP_ANALYST role was instantly blocked.

-- Run as DMP_ANALYST
SELECT * FROM DMP_TEST_DB.HR.OFFICE_LOCATION LIMIT 5;

2026-08-26_11h40_23

Since the error is returned as a SQL compilation error, the query is not executed and the warehouse is presumably not consumed. When MAX_ROWS = 0, blocking is uniform regardless of row count, and aggregate queries that return only one row result in the same error.

-- Run as DMP_ANALYST
SELECT COUNT(*) FROM DMP_TEST_DB.HR.OFFICE_LOCATION;

2026-08-26_11h41_36

For ACCOUNTADMIN, the Rule returns NULL (unlimited), so the same query succeeds.

Adding Rules to an already-applied policy

Rules can also be added later to a policy already applied to an account using ALTER. Two ENFORCE Rules for Snowsight and one ALERT Rule for unloading were added.

-- Snowsight worksheet display: up to 100 rows for roles other than ACCOUNTADMIN
CREATE OR REPLACE DATA MOVEMENT RULE R_BASELINE_SNOWSIGHT_LIMIT
  TYPE = 'SNOWSIGHT_UI'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') = 'ACCOUNTADMIN' THEN NULL ELSE 100 END
  );

-- Snowsight download button: block for roles other than ACCOUNTADMIN
CREATE OR REPLACE DATA MOVEMENT RULE R_BASELINE_UI_DOWNLOAD_BLOCK
  TYPE = 'UI_DOWNLOAD'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') = 'ACCOUNTADMIN' THEN NULL ELSE 0 END
  );

-- ALERT: record violation if unloading to internal stage exceeds 50 rows
CREATE OR REPLACE DATA MOVEMENT RULE R_BASELINE_COPY_ALERT
  TYPE = 'COPY_INTO_INTERNAL_STAGE'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE WHEN SYS_CONTEXT('SNOWFLAKE$SESSION', 'ROLE') = 'ACCOUNTADMIN' THEN NULL ELSE 50 END
  );

ALTER DATA MOVEMENT POLICY DMP_ACCOUNT_BASELINE
  ADD ENFORCE_RULES = (R_BASELINE_SNOWSIGHT_LIMIT, R_BASELINE_UI_DOWNLOAD_BLOCK);
ALTER DATA MOVEMENT POLICY DMP_ACCOUNT_BASELINE
  ADD ALERT_RULES = (R_BASELINE_COPY_ALERT);

2026-08-26_15h30_50

ALERT_RULES: allow the operation while recording it as a violation

ALERT allows the operation to succeed even when the threshold is exceeded, and records it as a violation. When DMP_ANALYST unloads 100 rows (exceeding the threshold of 50) to an internal stage, the operation succeeds.

-- Run as DMP_ANALYST
COPY INTO @~/dmp_alert/ FROM DMP_TEST_DB.HR.OFFICE_LOCATION;

2026-08-26_15h37_02

Monitoring: DATA_MOVEMENT_VIOLATIONS view

Violations are recorded in the SNOWFLAKE.ACCOUNT_USAGE.DATA_MOVEMENT_VIOLATIONS view. This covers both queries blocked by ENFORCE and operations allowed by ALERT. The following checks for records that match ALERT violations; ENFORCE blocks can be confirmed in the same way using the ENFORCED_POLICY column. Since TIMESTAMP and other fields may not be populated immediately after recording, NULLS FIRST is added.

SELECT
  MOVEMENT_TYPE,
  USER_NAME,
  ALERTED_POLICIES,
  TIMESTAMP
FROM SNOWFLAKE.ACCOUNT_USAGE.DATA_MOVEMENT_VIOLATIONS
WHERE ALERTED_POLICIES IS NOT NULL
ORDER BY TIMESTAMP DESC NULLS FIRST
LIMIT 5;

2026-08-26_15h48_01

For auditing definition information, you can use the DATA_MOVEMENT_POLICIES / DATA_MOVEMENT_POLICY_RULES / DATA_MOVEMENT_RULE_REFERENCES views. DATA_MOVEMENT_POLICY_RULES even includes the Rule expression (FUNCTION_BODY).

Priority: tag policy takes precedence over the baseline

At this point, both the baseline "block all fetches for non-ACCOUNTADMIN" policy on the account and the tag policy "allow up to 1,000 rows" on the tagged table are in effect. Querying both tables as DMP_ANALYST allows us to verify the priority.

-- Run as DMP_ANALYST
SELECT * FROM DMP_TEST_DB.HR.EMPLOYEE_SALARY LIMIT 600;  -- Success
SELECT * FROM DMP_TEST_DB.HR.OFFICE_LOCATION LIMIT 5;    -- Blocked

2026-08-26_22h01_06

2026-08-26_22h01_57

A 600-row fetch from the tagged table should be blocked by the baseline, but the more granular table-tag policy (allow up to 1,000 rows) is selected instead and the operation succeeds. The table without a tag continues to have the baseline applied.

However, the tag policy does not simply override the baseline; the priority works by "selecting the most granular policy for each referenced column/object," and if multiple policies remain after selection, the strictest MAX_ROWS among them is applied to the entire statement.
In this case, since the entire table is the tag target, the effective policy was narrowed down to one on the tag side.

Note that running the same two SELECT statements in Snowsight produces the opposite results.
Since they are evaluated as SNOWSIGHT_UI, the tagged table is blocked by SNOWSIGHT_UI = 0 in the tag policy, while the untagged table is displayed within the baseline's 100-row limit.
Be aware that even though the priority is the same, the movement type being evaluated changes depending on the execution path.

SNOWSIGHT_UI / UI_DOWNLOAD: behavior in Snowsight

Testing was also done in Snowsight worksheets as the DMP_ANALYST role. Queries within the limit (100 rows) display results, and only the download button is disabled. Hovering over the button displays "Download is disabled: A data movement policy was triggered."

-- Run as DMP_ANALYST (50 rows <= limit of 100 rows)
SELECT * FROM DMP_TEST_DB.HR.OFFICE_LOCATION LIMIT 50;

2026-08-26_22h08_42

On the other hand, results exceeding the SNOWSIGHT_UI limit (100 rows) do not get truncated—the query itself errors out. The same "Data movement policy triggered." message displayed in the CLI appears in the results pane.

-- Run as DMP_ANALYST (300 rows > limit of 100 rows)
SELECT * FROM DMP_TEST_DB.HR.OFFICE_LOCATION;

2026-08-26_22h09_35

Since the behavior is "do not return results that exceed the limit" rather than "show up to the limit," when setting a positive integer for SNOWSIGHT_UI, the operation requires users to add a LIMIT clause themselves.

AGENT_ACCESS: controlling access via agents

Preparation

Creating an agent as preparation
USE ROLE ACCOUNTADMIN;

CREATE DATABASE IF NOT EXISTS KAWABATA_MART_DB;
CREATE SCHEMA IF NOT EXISTS KAWABATA_MART_DB.HR;

-- Dummy salary table
CREATE OR REPLACE TABLE KAWABATA_MART_DB.HR.EMPLOYEE_SALARY AS
SELECT
  SEQ4() + 1                                     AS EMP_ID,
  'EMP_' || LPAD(TO_VARCHAR(SEQ4() + 1), 5, '0') AS EMP_NAME,
  UNIFORM(4000000, 12000000, RANDOM())           AS SALARY,
  DECODE(MOD(SEQ4(), 4), 0, 'SALES', 1, 'HR', 2, 'ENGINEERING', 3, 'FINANCE') AS DEPARTMENT
FROM TABLE(GENERATOR(ROWCOUNT => 100));

-- Semantic view for Cortex Analyst
CREATE OR REPLACE SEMANTIC VIEW KAWABATA_MART_DB.HR.EMPLOYEE_SV
  TABLES (
    employees AS KAWABATA_MART_DB.HR.EMPLOYEE_SALARY
      PRIMARY KEY (EMP_ID)
      WITH SYNONYMS ('employee', 'staff')
      COMMENT = 'Employee salary information'
  )
  DIMENSIONS (
    employees.emp_name AS emp_name COMMENT = 'Employee name',
    employees.department AS department COMMENT = 'Department'
  )
  METRICS (
    employees.avg_salary AS AVG(salary) COMMENT = 'Average salary',
    employees.total_salary AS SUM(salary) COMMENT = 'Total salary'
  )
  COMMENT = 'For AGENT_ACCESS verification';

-- Create agent (with Cortex Analyst tool)
CREATE OR REPLACE AGENT KAWABATA_MART_DB.HR.DMP_AGENT_TEST
  WITH PROFILE = '{"display_name": "DMP AGENT_ACCESS Test"}'
  FROM SPECIFICATION $$
{
  "models": { "orchestration": "auto" },
  "instructions": { "response": "Please answer concisely in English about employee data." },
  "tools": [
    {
      "tool_spec": {
        "type": "cortex_analyst_text_to_sql",
        "name": "employee_analyst"
      }
    }
  ],
  "tool_resources": {
    "employee_analyst": { "semantic_view": "KAWABATA_MART_DB.HR.EMPLOYEE_SV" }
  }
}
$$;

Before applying the policy, output is returned as normal.
2026-08-26_22h36_03

Creating Rule / Policy and applying to the account

USE ROLE ACCOUNTADMIN;
CREATE SCHEMA IF NOT EXISTS KAWABATA_MART_DB.GOVERNANCE;
USE SCHEMA KAWABATA_MART_DB.GOVERNANCE;

-- Block only the target agent (NULL = unlimited for other agents)
CREATE OR REPLACE DATA MOVEMENT RULE R_AGENT_SCOPED_BLOCK
  TYPE = 'AGENT_ACCESS'
  MAX_ROWS AS () RETURNS INTEGER
  -> (
    CASE
      WHEN SYS_CONTEXT('SNOWFLAKE$DATA_MOVEMENT', 'AGENT_NAME') LIKE '%DMP_AGENT_TEST%' THEN 0
      ELSE NULL
    END
  )
  COMMENT = 'Block data access for the test agent only';

CREATE OR REPLACE DATA MOVEMENT POLICY DMP_AGENT_TEST_POLICY
  ENFORCE_RULES = (R_AGENT_SCOPED_BLOCK)
  COMMENT = 'AGENT_ACCESS verification (temporary)';

ALTER ACCOUNT SET DATA MOVEMENT POLICY KAWABATA_MART_DB.GOVERNANCE.DMP_AGENT_TEST_POLICY;

Since AGENT_NAME contains the fully qualified name of the called agent, using an exact full match in production prevents false matches.

Note that the AGENT_ACCESS verification was conducted on a different account from the previous sections. If testing on the same account, replace the already-applied policy using FORCE or UNSET it before applying. After testing, remove it with ALTER ACCOUNT UNSET DATA MOVEMENT POLICY;.

After applying the above, the same data question as before was sent to the target agent in Snowsight CoWork.

2026-08-26_22h50_25

It was confirmed that the data movement policy was applied and the query was rejected.

Unloading with PARTITION BY is unconditionally blocked

The official documentation states that "when DMP is enabled, unloading using PARTITION BY is always blocked." Testing this, even ACCOUNTADMIN—where the Rule returns NULL (unlimited) for all cases—was blocked.

-- Run as ACCOUNTADMIN
COPY INTO @~/dmp_part/ FROM (
  SELECT LOCATION_NAME, LOCATION_ID FROM DMP_TEST_DB.HR.OFFICE_LOCATION
) PARTITION BY (LOCATION_NAME);

2026-08-26_22h11_45

Simply having a policy applied to the account triggers the block regardless of the Rule's content. Environments with existing unload pipelines that use PARTITION BY need to assess the impact before introducing DMP.

Limitations and Caveats

Here is a summary of things to watch out for when deploying, based on testing results as of August 26, 2026, and the official documentation.

Limitations and Caveats
  • Column tag alone could not be confirmed to enforce in this test environment. Table tags enforced within 1 minute, and account-level application was immediate
  • MAX_ROWS = 0 blocks at compile time (001003), and exceeding the positive integer limit blocks at execution time (100168) (measured)
  • Queries that exceed the limit are judged after execution, so the warehouse is likely consumed
  • Tags to which DMP is applied must have PROPAGATE = ON_DEPENDENCY_AND_DATA_MOVEMENT or ON_DATA_MOVEMENT (leaving it as NONE results in an error rejection)
  • One primary movement type is assigned per statement. COPY INTO via an agent is evaluated as COPY_INTO_* rather than AGENT_ACCESS, so COPY_INTO_* Rules are also needed to stop unloading
  • For statements where multiple policies are in effect, the strictest MAX_ROWS is applied to the entire statement (see the priority section for details)
  • Unloading with PARTITION BY is always blocked while DMP is applied, regardless of the Rule's content. This block was not recorded in DATA_MOVEMENT_VIOLATIONS (measured)
  • UI_DOWNLOAD can only be specified in ENFORCE_RULES and does not appear in violation records (DATA_MOVEMENT_VIOLATIONS)
  • UI_DOWNLOAD targets include downloads from Workspace results, notebook cells, Query History, etc. Streamlit apps, VS Code extension, HTML Export, and CoWork are not targeted
  • Only one Rule of the same movement type can be included in the same section of the same policy
  • Since row count threshold evaluation is per statement, exfiltration via split queries cannot be detected
  • Violation records are best-effort with no completeness guarantee (actual reflection in measured violations took approximately 5–10 minutes)
  • View reflection delays: up to 3 hours for DATA_MOVEMENT_VIOLATIONS / DATA_MOVEMENT_RULE_REFERENCES, and up to 2 hours for DATA_MOVEMENT_POLICIES / DATA_MOVEMENT_POLICY_RULES
  • Cross-region sharing protection is not supported

Closing Thoughts

In addition to controls over "how data is presented" such as masking, I found it great to be able to declaratively control "exfiltration" itself using SQL expressions.
Also, the fact that it can be applied to AGENT_ACCESS seems to broaden the range of use cases.

I hope this article is helpful to someone!


Snowflake World Tour Tokyo 2026に参加しませんか?

Snowflakeの国内最大級イベント「Snowflake World Tour Tokyo」が2026年9月10日(木)・11日(金)にグランドプリンスホテル新高輪にて開催されます。
最新のAI・データ活用事例やライブデモを体感できる無料イベントです。

Snowflake World Tour Tokyoイベントに参加する


Snowflake Community Awards ファイナリストに選出されました

DevelopersIO で Snowflake 記事を執筆している かわばた が、Snowflake Community Awards「RISING COMMUNITY LEADER OF THE YEAR」部門・APJ枠のファイナリストに選ばれました。
最終選考の30%はコミュニティ投票です。記事がお役に立っていたようでしたら、9月15日(火)までにぜひ一票お願いします。フォームの「(4 of 6) RISING COMMUNITY LEADER OF THE YEAR」で Tomohiro Kawabata | Classmethod, Japan を選択、2分ほどで完了します。

投票フォームを開く


Snowflakeの導入支援はクラスメソッドに!

クラスメソッドでは Snowflake の導入を支援しております。
製品の詳細や支援の内容についてお気軽にお問い合わせください。

Snowflakeの詳細を見る

Share this article