![[New Feature] Data Movement Policies are now generally available, so I tried controlling data unloading, downloading, and fetching](https://images.ctfassets.net/ct0aopd36mqt/wp-refcat-img-3610e3c1ff5961bdb7b464e17f8bf06d/90b168b240005ead852ec1d474bb74fb/snowflake-logo-1200x630-1.png?w=3840&fm=webp)
[New Feature] Data Movement Policies are now generally available, so I tried controlling data unloading, downloading, and fetching
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.
【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.
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) andALERT_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.
- Column-level tag
- Table-level tag
- Schema-level tag
- Database-level tag
- Account level (baseline)
Prerequisites
- Enterprise Edition or higher
- Creating Rules / Policies:
CREATE DATA MOVEMENT RULE/CREATE DATA MOVEMENT POLICYprivileges on the schema - Applying to tags / accounts:
APPLY DATA MOVEMENT POLICYprivilege 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;

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'));

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;

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)


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 = 0is set for all roles, fetches from your own session will also be blocked. It is safe to keepALTER 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;

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;

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);

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;

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;

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


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;

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;

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.

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.

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);

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 = 0blocks 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_MOVEMENTorON_DATA_MOVEMENT(leaving it as NONE results in an error rejection) - One primary movement type is assigned per statement.
COPY INTOvia 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_ROWSis applied to the entire statement (see the priority section for details) - Unloading with
PARTITION BYis always blocked while DMP is applied, regardless of the Rule's content. This block was not recorded inDATA_MOVEMENT_VIOLATIONS(measured) UI_DOWNLOADcan only be specified inENFORCE_RULESand does not appear in violation records (DATA_MOVEMENT_VIOLATIONS)UI_DOWNLOADtargets 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 forDATA_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!
