![[New Feature] Snowflake's Restricted Session Scope is Now Generally Available - Narrowing Effective Permissions Only During CoCo Usage](https://images.ctfassets.net/ct0aopd36mqt/wp-refcat-img-3610e3c1ff5961bdb7b464e17f8bf06d/90b168b240005ead852ec1d474bb74fb/snowflake-logo-1200x630-1.png?w=3840&fm=webp)
[New Feature] Snowflake's Restricted Session Scope is Now Generally Available - Narrowing Effective Permissions Only During CoCo Usage
This page has been translated by machine translation. View original
This is Kawabata.
On September 3, 2026, Restricted Session Scope (RSS) became generally available.
I will verify it with 4 use cases expected in practice using CoCo in Snowsight.
【Update】
I was selected as a finalist in the "RISING COMMUNITY LEADER OF THE YEAR" category (APJ slot) of the Snowflake Community Awards.
Please check the details at the link below.
Conclusion First
- RSS is a mechanism that narrows effective privileges only for "sessions where an agent is active" without changing any existing RBAC. Agents can only execute operations that are "permitted by both RBAC and RSS" — privileges never increase
- By excluding writes to the production database from the RSS allowed scope and prohibiting switching to privileged roles, agent sessions can be restricted to near read-only on a per-user basis
data writeonly targets data writes to tables and does not include DDL such as CREATE TABLE. If you want to allow table creation as well, addobject management- RSS, which narrows "what can be touched," and agent-aware masking, which narrows "what values are visible after being touched," are complementary and can be layered in the same session
- In this verification, we confirmed that even with the same user and same role, INSERT to production tables, DDL, and role switching are rejected in CoCo's agent sessions, while they execute according to RBAC privileges in worksheets
Overall Picture
RSS covers "the scope of what can be touched while an agent is running" within Snowflake's governance features.
| Mechanism | Control Axis | Unit |
|---|---|---|
| RBAC | Who can do what | Role |
| Feature Policy | Which objects can be created | Containers such as DB |
| Masking / Projection / Aggregation Policies | Which values in touched data are visible | Column / Table |
| RSS | Which operations / ranges can be touched when using an agent | Session (when agent is active) |
Based on this positioning, this article proceeds in the following order.
- Previous challenges
- What is RSS
- Verification
- Closing
Previous Challenges
Snowflake's access control is based on RBAC. Assign privileges to roles, assign roles to users. Since this mechanism determines privileges at the "who" level, the available privileges are the same whether the same user operates from a worksheet as a person or operates via an agent.
If an analytics role designed for human use has INSERT or CREATE TABLE attached, CoCo can perform the same operations. Furthermore, CoCo starts sessions with the user's default role, but if instructed to "switch roles" during a conversation, it can execute USE ROLE. Although a confirmation dialog appears, there was no way to prohibit this in advance.
There is also a method to narrow "visible values" using a masking policy conditioned on IS_AGENT_ACTIVATED, but this is about "after touching."
I have verified this below, so please check it if you are curious about the details.
RSS is what can narrow "the scope of what can be touched" itself, only when an agent is active.
What is RSS
RSS is a mechanism that defines the privilege ceiling for agents. Custom RSS can be defined in YAML, and Snowflake predefined scopes can also be referenced by name. It does not change the user's RBAC at all. In sessions where an agent is active, only operations permitted by both RBAC and RSS can be executed. If either one does not permit it, that operation cannot be performed.
Definition Structure
The RSS definition consists of 2 sections: privilege_scopes, which defines operations and target scope, and role_scopes, which defines available roles.
privilege_scopes:
allowed_privileges:
- privileges: [<group privilege>, ...]
account: [all] # or databases: [...] / schemas: [...]
- privileges: [data write]
databases: [SANDBOX_DB, USER$] # USER$ resolves to the session user's personal DB
role_scopes:
blocked_roles: [ACCOUNTADMIN, SYSADMIN] # or allowed_roles
blocked_secondary_roles: [ACCOUNTADMIN] # or allowed_secondary_roles
allow_role_switching: false # default is true
For privileges, you write the following group privileges rather than individual privilege names.
| Group Privilege | Target | Official Documentation Definition |
|---|---|---|
data read |
All containers | SELECT from tables, views, streams, stages, and workspaces |
data write |
All containers | Data writes to tables (does not include DDL) |
object discovery |
All containers | Object discovery via SHOW, etc. (does not read data) |
compute usage |
Account | Use of warehouses and compute pools |
program usage |
All containers | Execution of UDFs, stored procedures, and Streamlit |
grant management |
All containers | GRANT / REVOKE |
object management |
All containers | Full control of non-sensitive objects |
full management |
Account | All operations within the account |
What you want to keep in mind here is that data write is "data writes to tables" and does not include DDL such as CREATE TABLE.
How to Specify
To use RSS, specify RSS in the AGENT_RESTRICTED_SESSION_SCOPE of a session policy and attach that policy to an account or user. There are 3 ways to specify it.
-- (a) Reference a Snowflake predefined scope by name
CREATE SESSION POLICY sp_a
AGENT_RESTRICTED_SESSION_SCOPE = 'SNOWFLAKE$DATA_READ';
-- (b) Reference a custom RSS object by fully qualified name
CREATE RESTRICTED SESSION SCOPE gov.policies.my_scope AS $$ ... $$;
CREATE SESSION POLICY sp_b
AGENT_RESTRICTED_SESSION_SCOPE = 'GOV.POLICIES.MY_SCOPE';
-- (c) Embed YAML inline
CREATE SESSION POLICY sp_c
AGENT_RESTRICTED_SESSION_SCOPE = $$
privilege_scopes:
allowed_privileges:
- privileges: [data read]
account: [all]
$$;
-- Apply to the entire account or per user
ALTER ACCOUNT SET SESSION POLICY sp_a;
ALTER USER jsmith SET SESSION POLICY sp_b;
There are 3 types of Snowflake predefined scopes.
| Scope Name | Permitted Operations | Prohibited Operations |
|---|---|---|
SNOWFLAKE$DATA_READ |
data read across the entire account |
Writes, DDL, UDFs, stored procedures |
SNOWFLAKE$DATA_READ_WITH_AI |
data read + object discovery + USAGE for AI-related objects (Agent, MCP Server, Cortex Search service) |
Writes, DDL, stored procedures |
SNOWFLAKE$DATA_READ_PROGRAM_USAGE |
data read + program usage (execution of UDFs, stored procedures, Streamlit apps) |
Writes, DDL |
For SNOWFLAKE$DATA_READ_WITH_AI, there is a discrepancy in UDF handling between the official documentation body text (which reads as including UDF USAGE) and the appendix YAML definition (only Agent, MCP Server, Cortex Search service). In this verification, UDF calls were rejected (described later).
When RSS is Applied
RSS is applied only when an agent is active (agent-activated) in the session. As situations where SYS_CONTEXT('SNOWFLAKE$CURRENT', 'IS_AGENT_ACTIVATED') returns TRUE, the official documentation lists the following.
- Snowflake native agents: Cortex Agents, lite agents via each CoCo client (CLI / Desktop / Snowsight), CoWork, calls via REST API
- External agents: Connections via Snowflake-managed MCP Server, users of type
SERVICE_AGENT, OAuth security integrations withIS_AGENTIC = TRUE
In sessions that do not go through an agent, such as worksheets or dbt, RSS is ignored.
Which RSS is applied can be confirmed within the session using the following function.
SELECT SYS_CONTEXT('SNOWFLAKE$CURRENT', 'ACTIVE_RESTRICTED_SESSION_SCOPES');
Limitations
These are specifications described in the Considerations section of the official documentation.
Limitations
- RSS is an upper limit on existing RBAC and does not add privileges
- Once RSS is activated for agent activity, the RSS cannot be changed, removed, or elevated during that session. To apply a different scope, start a new session (for CoCo, a new conversation)
- If there is a user-level session policy, only its
AGENT_RESTRICTED_SESSION_SCOPEis used. It is not merged with account-level policies - If the session policy does not have
AGENT_RESTRICTED_SESSION_SCOPE, no scope is applied. For non-agent operations, this setting is ignored - Warehouse usage is implicitly permitted regardless of privilege scope
blocked_rolesis an exact match on role name. It does not traverse the role hierarchy to block inherited sources, nor does it strip privileges already inherited by the current primary role- An RSS referenced by fully qualified name from a session policy cannot be dropped until the reference is removed
- The feature for users to manage their own RSS (user-managed RSS) in CoCo CLI / CoCo Desktop is in Private Preview as of September 4, 2026. The RSS via session policy covered in this article is GA
Prerequisites
- Snowflake: Enterprise edition, AWS Tokyo region
- Required privileges
- Creating RSS:
CREATE RESTRICTED SESSION SCOPEprivilege on the target schema. Modification requiresMODIFYorOWNERSHIPon the RSS, deletion requiresOWNERSHIP - Creating session policy:
CREATE SESSION POLICYprivilege on the target schema - Applying session policy:
APPLY SESSION POLICYprivilege on the account
- Creating RSS:
Since the following SQL uses CREATE OR REPLACE, any databases, schemas, or tables with the same name will have their contents replaced. Please execute in a dedicated verification account or with dedicated object names.
All verifications are performed with ACCOUNTADMIN, and the session policy is applied only to my own user. Please note that executing ALTER ACCOUNT SET SESSION POLICY on a shared account will affect all users who use CoCo.
Verification Approach
For all use cases, the same SQL is executed in both CoCo (agent session) and worksheet (non-agent session), and we compare that RSS only acts on the agent. Results are shown in 4 columns — "RBAC," "RSS," "CoCo," and "Worksheet" — and we confirm that "only operations permitted by both RBAC and RSS pass through." CoCo and the worksheet use the same user, same default role, and same warehouse, with only the SQL content matched for comparison.
Verification Environment
We prepare the following 3 databases and a verification role that CoCo uses at startup.
KAWABATA_RSS_PROD_DB: Production-equivalent DB. Uses only the dedicated verification schemaKAWABATA_RSS_SALESKAWABATA_RSS_SANDBOX_DB: Sandbox DB. The agent's write destinationKAWABATA_RSS_GOV_DB: Governance DB. Storage location for RSS and policies
USE ROLE ACCOUNTADMIN;
CREATE OR REPLACE DATABASE KAWABATA_RSS_PROD_DB; -- Production equivalent
CREATE OR REPLACE SCHEMA KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES;
CREATE OR REPLACE DATABASE KAWABATA_RSS_SANDBOX_DB; -- Agent's write destination
CREATE OR REPLACE SCHEMA KAWABATA_RSS_SANDBOX_DB.WORK;
CREATE OR REPLACE DATABASE KAWABATA_RSS_GOV_DB; -- Storage for RSS and policies
CREATE OR REPLACE SCHEMA KAWABATA_RSS_GOV_DB.POLICIES;
We insert 20 rows of fictitious data including PII columns (customer_name, email) into the production-equivalent orders table. The note column is a verification marker, and rows inserted by the agent are identified by 'RSS_TEST'.
INSERT statement for sample data (click to expand)
CREATE OR REPLACE TABLE KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS (
order_id INT,
customer_name STRING,
email STRING,
order_date DATE,
product STRING,
amount NUMBER(10,2),
region STRING,
note STRING
);
-- 20 rows of fictitious EC order data (all email addresses are example.com)
INSERT INTO KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS VALUES
( 1, 'Taro Sato', 'sato.taro@example.com', '2026-08-01', 'Wireless Mouse', 2500.00, 'Kanto', 'SEED'),
( 2, 'Hanako Suzuki', 'suzuki.hanako@example.com', '2026-08-01', 'USB-C Hub', 4800.00, 'Kansai', 'SEED'),
( 3, 'Ken Takahashi', 'takahashi.ken@example.com', '2026-08-02', 'Monitor 27inch', 32000.00, 'Kanto', 'SEED'),
( 4, 'Misaki Tanaka', 'tanaka.misaki@example.com', '2026-08-02', 'Keyboard', 9800.00, 'Chubu', 'SEED'),
( 5, 'Daisuke Ito', 'ito.daisuke@example.com', '2026-08-03', 'Webcam', 6500.00, 'Kyushu', 'SEED'),
( 6, 'Sakura Watanabe', 'watanabe.sakura@example.com', '2026-08-03', 'Headset', 8900.00, 'Kanto', 'SEED'),
( 7, 'Sho Yamamoto', 'yamamoto.sho@example.com', '2026-08-04', 'Laptop Stand', 3900.00, 'Kansai', 'SEED'),
( 8, 'Aya Nakamura', 'nakamura.aya@example.com', '2026-08-04', 'Wireless Mouse', 2500.00, 'Tohoku', 'SEED'),
( 9, 'Takuya Kobayashi','kobayashi.takuya@example.com','2026-08-05', 'Docking Station', 18000.00, 'Kanto', 'SEED'),
(10, 'Megumi Kato', 'kato.megumi@example.com', '2026-08-05', 'USB-C Hub', 4800.00, 'Chubu', 'SEED'),
(11, 'Naoki Yoshida', 'yoshida.naoki@example.com', '2026-08-06', 'Monitor 27inch', 32000.00, 'Kansai', 'SEED'),
(12, 'Yu Yamada', 'yamada.yu@example.com', '2026-08-06', 'Keyboard', 9800.00, 'Hokkaido', 'SEED'),
(13, 'Haruka Sasaki', 'sasaki.haruka@example.com', '2026-08-07', 'Webcam', 6500.00, 'Kanto', 'SEED'),
(14, 'Ryo Yamaguchi', 'yamaguchi.ryo@example.com', '2026-08-07', 'Headset', 8900.00, 'Kyushu', 'SEED'),
(15, 'Rina Matsumoto', 'matsumoto.rina@example.com', '2026-08-08', 'Laptop Stand', 3900.00, 'Kansai', 'SEED'),
(16, 'Makoto Inoue', 'inoue.makoto@example.com', '2026-08-08', 'Wireless Mouse', 2500.00, 'Kanto', 'SEED'),
(17, 'Ayaka Kimura', 'kimura.ayaka@example.com', '2026-08-09', 'Docking Station', 18000.00, 'Chubu', 'SEED'),
(18, 'Yuto Hayashi', 'hayashi.yuto@example.com', '2026-08-09', 'USB-C Hub', 4800.00, 'Tohoku', 'SEED'),
(19, 'Yui Saito', 'saito.yui@example.com', '2026-08-10', 'Monitor 27inch', 32000.00, 'Kanto', 'SEED'),
(20, 'Yosuke Shimizu', 'shimizu.yosuke@example.com', '2026-08-10', 'Keyboard', 9800.00, 'Kansai', 'SEED');
We also create the write destination table on the sandbox side and the user-defined function (UDF) and stored procedure used for program usage determination.
CREATE OR REPLACE TABLE KAWABATA_RSS_SANDBOX_DB.WORK.AGENT_NOTES (
note_id INT AUTOINCREMENT,
note STRING,
created_at TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);
CREATE OR REPLACE FUNCTION KAWABATA_RSS_GOV_DB.POLICIES.RSS_ECHO(v STRING)
RETURNS STRING
AS $$ 'echo: ' || v $$;
CREATE OR REPLACE PROCEDURE KAWABATA_RSS_GOV_DB.POLICIES.RSS_PING()
RETURNS STRING
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
BEGIN
RETURN 'pong from ' || CURRENT_ROLE();
END;
$$;
Verification Role and RBAC-side Privileges
We grant SELECT and INSERT on the target tables in production-equivalent and sandbox (also DELETE for production-equivalent), and CREATE TABLE on the target schemas to the verification role KAWABATA_RSS_ANALYST. We keep RBAC in a state where both can be written to, so we can see how much RSS can restrict.
CREATE OR REPLACE ROLE KAWABATA_RSS_ANALYST;
GRANT USAGE ON WAREHOUSE WH_SI_JP TO ROLE KAWABATA_RSS_ANALYST;
-- Production equivalent: SELECT / INSERT / DELETE + CREATE TABLE
GRANT USAGE ON DATABASE KAWABATA_RSS_PROD_DB TO ROLE KAWABATA_RSS_ANALYST;
GRANT USAGE, CREATE TABLE ON SCHEMA KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES TO ROLE KAWABATA_RSS_ANALYST;
GRANT SELECT, INSERT, DELETE ON TABLE KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS TO ROLE KAWABATA_RSS_ANALYST;
-- Sandbox: SELECT / INSERT + CREATE TABLE
GRANT USAGE ON DATABASE KAWABATA_RSS_SANDBOX_DB TO ROLE KAWABATA_RSS_ANALYST;
GRANT USAGE, CREATE TABLE ON SCHEMA KAWABATA_RSS_SANDBOX_DB.WORK TO ROLE KAWABATA_RSS_ANALYST;
GRANT SELECT, INSERT ON TABLE KAWABATA_RSS_SANDBOX_DB.WORK.AGENT_NOTES TO ROLE KAWABATA_RSS_ANALYST;
-- Governance: Execute UDF / stored procedure only
GRANT USAGE ON DATABASE KAWABATA_RSS_GOV_DB TO ROLE KAWABATA_RSS_ANALYST;
GRANT USAGE ON SCHEMA KAWABATA_RSS_GOV_DB.POLICIES TO ROLE KAWABATA_RSS_ANALYST;
GRANT USAGE ON FUNCTION KAWABATA_RSS_GOV_DB.POLICIES.RSS_ECHO(STRING) TO ROLE KAWABATA_RSS_ANALYST;
GRANT USAGE ON PROCEDURE KAWABATA_RSS_GOV_DB.POLICIES.RSS_PING() TO ROLE KAWABATA_RSS_ANALYST;
GRANT ROLE KAWABATA_RSS_ANALYST TO USER KAWABATA_TOMOHIRO;
Since CoCo in Snowsight starts sessions with the user's default role, we temporarily replace the default role with the verification role only during verification. If the secondary role is ALL, the privileges of all roles the user has will be mixed into the RBAC side, so we leave it empty.
ALTER USER KAWABATA_TOMOHIRO SET DEFAULT_ROLE = KAWABATA_RSS_ANALYST;
ALTER USER KAWABATA_TOMOHIRO SET DEFAULT_SECONDARY_ROLES = ();
Let's Try It
Without RSS, the Agent Uses Full RBAC Privileges
First, we have CoCo execute each operation without a session policy attached.
We instructed CoCo as follows and had it execute the SQL one statement at a time without modifying it. The order_date of the test row contains CURRENT_DATE() at execution time, so the value changes depending on the execution date.
Please execute the following SQL statements one by one without modification,
and summarize the success/failure and results of each statement,
and if an error occurs, the full error message in a table.
If an error occurs, please continue to the next statement without stopping.
SELECT
CURRENT_ROLE() AS CURRENT_ROLE,
CURRENT_SECONDARY_ROLES() AS CURRENT_SECONDARY_ROLES,
SYS_CONTEXT('SNOWFLAKE$CURRENT', 'IS_AGENT_ACTIVATED') AS IS_AGENT_ACTIVATED,
SYS_CONTEXT('SNOWFLAKE$CURRENT', 'AGENT_TYPE') AS AGENT_TYPE;
SELECT COUNT(*) FROM KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS;
INSERT INTO KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS
VALUES (901, 'Test Taro', 'test901@example.com', CURRENT_DATE(), 'Test Item', 1.00, 'Test', 'RSS_TEST');
CREATE TABLE KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.AGENT_BASELINE (id INT);
INSERT INTO KAWABATA_RSS_SANDBOX_DB.WORK.AGENT_NOTES (note) VALUES ('P0 baseline');
SELECT KAWABATA_RSS_GOV_DB.POLICIES.RSS_ECHO('P0');
CALL KAWABATA_RSS_GOV_DB.POLICIES.RSS_PING();

All 7 statements succeeded. CURRENT_ROLE is KAWABATA_RSS_ANALYST, IS_AGENT_ACTIVATED is TRUE, and even in the agent session, INSERT to production and CREATE TABLE pass as per RBAC. This is the state without RSS.
Production Read-Only, Writes Only to Sandbox
The first use case is "production read-only + sandbox writable," which is also in the official documentation examples. We allow data read and program usage for the entire account, and limit data write to the sandbox DB and personal DB.
USE ROLE ACCOUNTADMIN;
CREATE OR REPLACE RESTRICTED SESSION SCOPE KAWABATA_RSS_GOV_DB.POLICIES.RSS_PROD_READONLY
COMMENT = 'UC-A: Production read-only. Writes only to sandbox and personal DB'
AS $$
privilege_scopes:
allowed_privileges:
- privileges: [data read, object discovery, program usage, compute usage]
account: [all]
- privileges: [data write]
databases: [KAWABATA_RSS_SANDBOX_DB, USER$]
$$;
CREATE OR REPLACE SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_PROD_READONLY
AGENT_RESTRICTED_SESSION_SCOPE = 'KAWABATA_RSS_GOV_DB.POLICIES.RSS_PROD_READONLY';
-- Apply only to my own user
ALTER USER KAWABATA_TOMOHIRO SET SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_PROD_READONLY;
Note that warehouse usage is implicitly permitted regardless of privilege scope, so compute usage is only explicitly stated here for explanation purposes and works without it.
The created RSS can be confirmed with DESC to view the YAML directly. On the session policy side, AGENT_RESTRICTED_SESSION_SCOPE is displayed in addition to the conventional timeout and secondary role items.
DESC RESTRICTED SESSION SCOPE KAWABATA_RSS_GOV_DB.POLICIES.RSS_PROD_READONLY;
DESC SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_PROD_READONLY;

Open a new conversation in CoCo and have it execute the following SQL.
SELECT COUNT(*) FROM KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS;
INSERT INTO KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS
VALUES (902, 'Test Jiro', 'test902@example.com', CURRENT_DATE(), 'Test Item', 1.00, 'Test', 'RSS_TEST');
CREATE TABLE KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.AGENT_T1 (id INT);
INSERT INTO KAWABATA_RSS_SANDBOX_DB.WORK.AGENT_NOTES (note) VALUES ('P1 UC-A');
CREATE TABLE KAWABATA_RSS_SANDBOX_DB.WORK.AGENT_T1 (id INT);
SELECT KAWABATA_RSS_GOV_DB.POLICIES.RSS_ECHO('P1');
CALL KAWABATA_RSS_GOV_DB.POLICIES.RSS_PING();

| Operation | RBAC | RSS | CoCo | Worksheet |
|---|---|---|---|---|
| SELECT production | Permitted | data read (account) |
Success | Success |
| INSERT production | Permitted | No data write for production |
Rejected | Success |
| CREATE TABLE production | Permitted | None | Rejected | Success |
| INSERT sandbox | Permitted | data write (sandbox) |
Success | Success |
| CREATE TABLE sandbox | Permitted | data write does not include DDL |
Rejected | Success |
| UDF / stored procedure | Permitted | program usage |
Success | Success |
The error upon rejection explicitly states which RSS was the cause.
SQL access control error: Insufficient privileges to operate on table 'ORDERS'.
Restricted session scope KAWABATA_RSS_GOV_DB.POLICIES.RSS_PROD_READONLY does not include
INSERT access to TABLE KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS.
The verification role remains able to write to production at the RBAC level, and only writes via agent are stopped — exactly as intended.
Of course, executing it from a worksheet succeeds.

data write and object management Are Different Things
data write is "data writes to tables" and does not include DDL such as CREATE TABLE. If you want to allow the agent to create tables in the sandbox, add object management. Existing RSS can be merged with YAML fragments using ADD. The official documentation syntax is ADD AS $$ ... $$, but this caused an unexpected 'AS' syntax error in the verification account, so we removed AS and executed it.
ALTER RESTRICTED SESSION SCOPE KAWABATA_RSS_GOV_DB.POLICIES.RSS_PROD_READONLY ADD $$
privilege_scopes:
allowed_privileges:
- privileges: [object management]
databases: [KAWABATA_RSS_SANDBOX_DB]
$$;

Here is the result of executing CREATE TABLE in a new conversation.

Only CREATE TABLE in the sandbox changed to success, and INSERT and CREATE TABLE to production remain rejected. Since RSS changes are not reflected in already open conversations, always verify in a new conversation.
Stopping Switching to Privileged Roles
The second use case addresses the "role switching" challenge mentioned at the beginning. We block ACCOUNTADMIN and others with role_scopes and prohibit role switching itself with allow_role_switching: false. We also stop enabling secondary roles with blocked_secondary_roles.
ALTER RESTRICTED SESSION SCOPE KAWABATA_RSS_GOV_DB.POLICIES.RSS_PROD_READONLY ADD $$
role_scopes:
blocked_roles: [ACCOUNTADMIN, SYSADMIN, SECURITYADMIN]
blocked_secondary_roles: [ACCOUNTADMIN, SYSADMIN, SECURITYADMIN]
allow_role_switching: false
$$;

In a new conversation, we have it try switching the primary role and enabling secondary roles in sequence. We click Allow on all Allow / Deny dialogs that CoCo presents, and observe whether the RSS side stops them.
USE ROLE ACCOUNTADMIN;
SELECT CURRENT_ROLE();
USE ROLE SYSADMIN;
SELECT CURRENT_ROLE();
USE ROLE KAWABATA_DBT_DEV_ROLE; -- Not a blocked target, but role switching is prohibited
SELECT CURRENT_ROLE();
USE SECONDARY ROLES ALL;
SELECT CURRENT_SECONDARY_ROLES();
USE SECONDARY ROLES ACCOUNTADMIN;
SELECT CURRENT_SECONDARY_ROLES();


Even after clicking Allow in CoCo, all USE ROLE and USE SECONDARY ROLES commands were rejected with the following error. The primary role remained KAWABATA_RSS_ANALYST and the secondary roles remained empty and unchanged. Switching to KAWABATA_DBT_DEV_ROLE, which is not a blocked target, was also stopped by allow_role_switching: false.
Current session is restricted. USE ROLE not allowed.
blocked_roles is an exact match on role name and does not traverse the role hierarchy.
If there is a custom role that inherits ACCOUNTADMIN, you need to explicitly write that role name as well.
Comparing the 3 Predefined Scopes
Without writing your own YAML, you can reference Snowflake's 3 predefined scopes by name from a session policy. Apply each one to a user in turn and have CoCo execute the same SQL to observe the differences.
SELECT COUNT(*) FROM KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS;
INSERT INTO KAWABATA_RSS_SANDBOX_DB.WORK.AGENT_NOTES (note) VALUES ('P4 DATA_READ');
SELECT KAWABATA_RSS_GOV_DB.POLICIES.RSS_ECHO('P4');
CALL KAWABATA_RSS_GOV_DB.POLICIES.RSS_PING();
CREATE TABLE KAWABATA_RSS_SANDBOX_DB.WORK.AGENT_T3 (id INT);
SHOW TABLES IN SCHEMA KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES;
Since only one session policy can be assigned to a user at a time, swapping requires UNSET followed by SET.
CREATE OR REPLACE SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_DATA_READ
AGENT_RESTRICTED_SESSION_SCOPE = 'SNOWFLAKE$DATA_READ';
CREATE OR REPLACE SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_DATA_READ_WITH_AI
AGENT_RESTRICTED_SESSION_SCOPE = 'SNOWFLAKE$DATA_READ_WITH_AI';
CREATE OR REPLACE SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_DATA_READ_PROGRAM_USAGE
AGENT_RESTRICTED_SESSION_SCOPE = 'SNOWFLAKE$DATA_READ_PROGRAM_USAGE';
ALTER USER KAWABATA_TOMOHIRO UNSET SESSION POLICY;
ALTER USER KAWABATA_TOMOHIRO SET SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_DATA_READ;
-- Similarly UNSET → SET for WITH_AI / PROGRAM_USAGE below
SNOWFLAKE$DATA_READ: data read only, across the entire account

SNOWFLAKE$DATA_READ_WITH_AI: data read + object discovery + USAGE on AI-related objects (Agent, MCP Server, Cortex Search service)

SNOWFLAKE$DATA_READ_PROGRAM_USAGE: data read + program usage (execution of UDFs, stored procedures, and Streamlit apps)

A summary of the results for all three scopes is as follows.
| Operation | DATA_READ |
DATA_READ_WITH_AI |
DATA_READ_PROGRAM_USAGE |
|---|---|---|---|
| SELECT | Success | Success | Success |
| SHOW TABLES | Success | Success | Success |
| INSERT to sandbox | Denied | Denied | Denied |
| UDF call | Denied | Denied | Success |
| CALL stored procedure | Denied | Denied | Success |
UDF calls were denied even with SNOWFLAKE$DATA_READ_WITH_AI. While the main body of the official documentation contains wording that appears to include UDFs, it is believed that only USAGE on AI-related objects is permitted, as specified in the appendix YAML definition. It is recommended to perform actual testing on your target account when using this feature.
Note that CREATE TABLE failed all 3 times due to a name conflict with a previously created table (Object already exists), so it has been excluded from this table.
Inline YAML
We also tested a method of writing YAML directly into the session policy without creating an RSS object. In the AGENT_RESTRICTED_SESSION_SCOPE row of DESC SESSION POLICY, the YAML body itself is displayed as-is instead of an RSS name.
CREATE OR REPLACE SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_INLINE
AGENT_RESTRICTED_SESSION_SCOPE = $$
privilege_scopes:
allowed_privileges:
- privileges: [data read]
databases: [KAWABATA_RSS_PROD_DB]
$$;
DESC SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_INLINE;

Masking Policies and RSS
Finally, we layer the existing masking policy that "masks only when an agent is active" together with RSS in the same session.
Since RSS controls "the scope of what can be accessed" and masking controls "the values visible after access," the two should not conflict but rather complement each other.
-- IS_AGENT_ACTIVATED returns either VARCHAR or BOOLEAN depending on whether Behavior Change Bundle 2026_06 is enabled, absorbed with ::BOOLEAN
CREATE OR REPLACE MASKING POLICY KAWABATA_RSS_GOV_DB.POLICIES.MASK_EMAIL_FOR_AGENT
AS (val STRING) RETURNS STRING ->
CASE
WHEN SYS_CONTEXT('SNOWFLAKE$CURRENT', 'IS_AGENT_ACTIVATED')::BOOLEAN = TRUE
THEN REGEXP_REPLACE(val, '^[^@]+', '***')
ELSE val
END;
ALTER TABLE KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS
MODIFY COLUMN email SET MASKING POLICY KAWABATA_RSS_GOV_DB.POLICIES.MASK_EMAIL_FOR_AGENT;
-- Revert the session policy to UC-A's RSS_PROD_READONLY
ALTER USER KAWABATA_TOMOHIRO UNSET SESSION POLICY;
ALTER USER KAWABATA_TOMOHIRO SET SESSION POLICY KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_PROD_READONLY;
Output IS_AGENT_ACTIVATED, CURRENT_ROLE(), and email simultaneously from both CoCo and the worksheet, then execute an INSERT to production.
SELECT
SYS_CONTEXT('SNOWFLAKE$CURRENT', 'IS_AGENT_ACTIVATED') AS IS_AGENT_ACTIVATED,
CURRENT_ROLE() AS CURRENT_ROLE,
order_id, customer_name, email
FROM KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS
ORDER BY order_id
LIMIT 3;
INSERT INTO KAWABATA_RSS_PROD_DB.KAWABATA_RSS_SALES.ORDERS
VALUES (903, 'Test Saburo', 'test903@example.com', CURRENT_DATE(), 'Test Item', 1.00, 'Test', 'RSS_TEST');
Masking Policy


INSERT


With the same user and same role, the agent alone is in a state where it "cannot write to production and cannot see email addresses," confirming that both layers of defense are operating simultaneously.
Checking Applied Status
The applied status can be verified using POLICY_REFERENCES. We tested both the method of looking up from the policy name as shown in the official documentation examples, and the reverse lookup method from the user side.
-- Check applied targets from the policy side
SELECT POLICY_NAME, POLICY_KIND, REF_ENTITY_NAME, REF_ENTITY_DOMAIN, POLICY_STATUS
FROM TABLE(KAWABATA_RSS_GOV_DB.INFORMATION_SCHEMA.POLICY_REFERENCES(
POLICY_NAME => 'KAWABATA_RSS_GOV_DB.POLICIES.SP_AGENT_PROD_READONLY'));

-- Reverse lookup from the user side
SELECT POLICY_NAME, POLICY_KIND, REF_ENTITY_NAME, REF_ENTITY_DOMAIN, POLICY_STATUS
FROM TABLE(KAWABATA_RSS_GOV_DB.INFORMATION_SCHEMA.POLICY_REFERENCES(
REF_ENTITY_NAME => 'KAWABATA_TOMOHIRO', REF_ENTITY_DOMAIN => 'USER'));

Looking up from the policy name returns 1 row for the applied user, while looking up in reverse from the user side returns 2 rows: the network policy and the session policy attached to that user.
Limitations and Notes
Limitations and Notes (click to expand)
Specifications documented in the official documentation
- RSS is a ceiling on top of existing RBAC and does not add permissions. Even if permitted by RSS, operations cannot be executed without RBAC permissions
- Once an RSS is activated for agent activity, it cannot be changed, removed, or elevated during that session. After modifying RSS, verify in a new conversation
- The
ALTER RESTRICTED SESSION SCOPE ... ADD AS $$...$$syntax in the official documentation resulted in a syntax error on the verification account. It succeeded withADD $$...$$withoutAS(REMOVE AShas not been verified) - A user-level session policy completely overwrites the account-level policy and is not merged
blocked_rolesperforms an exact match on role names and does not traverse the role hierarchy. It also does not strip inherited permissions- RSS that is being referenced cannot be DROPped. First remove the reference from the session policy side
- User-managed RSS in CoCo CLI / CoCo Desktop was in Private Preview as of September 4, 2026
Behavior confirmed during this verification (as of September 4, 2026)
SYS_CONTEXT('SNOWFLAKE$CURRENT', 'ACTIVE_RESTRICTED_SESSION_SCOPES')resulted in aninvalid propertyerror when referenced from a non-agent worksheet. Since the official documentation states it returns NULL when RSS is not active, this is believed to be a difference due to the release version or client at the time of verification. The return value in an agent session was not confirmed in this articleIS_AGENT_ACTIVATEDreturns VARCHAR in environments where Behavior Change Bundle 2026_06 is disabled. Use::BOOLEANto handle this within policies- Creating a table in a personal database (
USER$) was denied on the verification account with060119: Tables cannot currently be created in a personal database.Write permission toUSER$was not actually tested in this article
Items not covered in the verification
- Priority between account-level and user-level session policies (did not execute
ALTER ACCOUNTdue to shared account) - Paths other than CoCo in Snowsight, such as CoCo CLI / CoCo Desktop, MCP Server, and SERVICE_AGENT users
- Impact of applying RSS on query performance and costs
Closing Remarks
"What to prevent agents from doing" had previously been a binary choice: either separate roles, or detect after the fact. RSS places a third option in between: "restrict the operations available only when acting as an agent, while keeping the same role as a human."
I felt it is a great feature for governance in support of AI utilization.
I hope this article serves as a useful reference for someone!
