Data-Layer RBAC for Snowflake Applications: Owner's Rights and Restricted Caller's Rights
Moving from read-only BI dashboards to interactive applications changes the requirements for data governance. Applications can be capable of writeback, can execute user-specified workflows and may be integrated with AI to act on behalf of the user. This presents a common governance dilemma on how to balance application usability against safeguarding the data.
Role-based access control (RBAC) in application code is something that scales very poorly and complicates reasoning about security. Snowflake RBAC controls should be tied to the data layer so they work regardless of the data consumption path and are the single source of truth. On the other hand, some controls can only exist in an application (e.g. "display this edit box only to an admin"), but they should be paired with hard controls on the data layer.
This post provides an overview of how exactly Streamlit in Snowflake (SiS) and other applications work for data access and provides some recommendations on implementing RBAC that safeguards the data without being too restrictive.
Scope of the post
- Target audience is RBAC admins. This post covers the details of owner's and restricted caller's rights, and an architectural pattern for adopting either alongside a pre-existing RBAC approach.
- Two companion posts cover aspects that are skipped here. Implementing true data layer governance on Snowflake covers caller grants, their inherited form, and the application side for Streamlit and App Runtime; if caller grants are new to you, read that one first. AI-ready RBAC covers the RBAC model that is adopted on Snowflake's central account end to end, including entitlement tables and policy testing.
- There is no prescription for a specific role design. The post describes boundaries that exist regardless of the shape your RBAC takes, so it should apply equally to accounts organized around broad read-only roles, business-unit roles, per-object grants or database roles.
- The post focuses on the data-layer RBAC, not the application code. The points are applicable to Streamlit in Snowflake, Snowflake App Runtime and custom Snowpark Container Services (SPCS) services.
- Legacy warehouse runtime and the
READ SESSIONgrant are not covered, only the container runtime is covered as it's the only one that supports restricted caller's rights.
TL;DR
- Owner's rights applications run queries as an ephemeral service user with a
single role. Row-level security relying on
CURRENT_USER()will evaluate to the service user. - Restricted caller's rights (RCR) applications run queries as visiting users,
bounded by the visitor's privileges and the app owner's
CALLERgrants. - RCR requires visiting users to have
USAGEon an active warehouse; the app owner role needsCALLER USAGEon it. - Use mixed mode (RCR for reads, owner's rights for writes) for writeback applications.
Owner's rights and restricted caller's rights

Owner's rights mirrors the classic service principal pattern of application authentication: a single identity is used to access the data on behalf of the users. Data entry applications are the typical example: the user does not need direct access to the underlying data. Users are only supposed to interact with the data or maybe a subset of it through the application, but never directly with the underlying tables.
Restricted caller's rights are (effectively) caller's rights with a ceiling: queries run as the visiting user, but only with those of the visitor's privileges an administrator has explicitly granted to the application. They are close to the architecture of a central Jupyter notebook service. When users access the service, they can interact with different objects, depending on their privileges in the system.
Thus, a rough mental model is:
- Owner's rights effectively discards the visitor's identity, allowing the visitor to operate with the owner's privileges
- Restricted caller's rights pass the visitor's identity to the data layer, allowing policy controls such as row-level security to work. Grants to the application owner role clamp the effective privileges.
SiS on container runtime supports two rights modes: owner's rights and
restricted caller's rights. SiS applications can opt into using
either or both modes when creating a session, through which all SQL statements
are executed. The rights mode choice determines what data and which data containers a
user can see when they interact with those objects through the application;
that choice is made by the application developer.
Object access in Snowflake
Before talking about how the rights modes work in practice, let's take a look at how object access in Snowflake works in general.
An actor's (identity) interaction with an object (a table, a view, a row in a table) has two halves: what the actor can learn about it, and what the actor can change.
What an actor can learn
The objects live inside a container, typically inside a SCHEMA nested in a DATABASE. From coarsest to finest, the actor can learn the following things about the object:
-
That the container object exists: without
USAGEon the database and schema, the object is unknowable regardless of what is granted on the object itself.This allows admins to set up a pattern where
USAGEis mass-granted on applications that are deployed inside a SCHEMA that is private to a business unit. -
That the object exists: the fact that a view named
SSN_Vis there implies that the corresponding data is stored somewhere, which may itself be sensitive. -
The shape of the object: column names and types, via
DESCRIBEorINFORMATION_SCHEMA. -
The definition of the object: the view body or function source. A secure view separates the definition from the shape: the shape stays visible, the body (view definition) does not.
-
Aggregates over the rows: an aggregation policy lets an actor learn statistics over rows they can never retrieve individually; a join policy constrains how the object may be combined with others. What the actor gets depends on how the policy body is written.
-
The rows: a row access policy decides which rows exist for the actor.
-
The columns: a projection policy allows a column to be filtered and joined on without ever being returned in a result set.
-
The values: a masking policy returns the row and the column, but completely or partially redacts the cell.
While the above is not a strict sequence, container-level access needs to hold
for containing objects to be available. For example, USAGE on the containers has
to hold before anything inside them can be reached, and a row has to be visible
before a masking policy on one of its columns matters. Boundaries at the same
level are independent: a projection policy on one column and a masking policy on
another do not constrain each other.
What an actor can change
- The data – insert, update, delete rows.
- The object shape –
ALTERand, separately, transfer ofOWNERSHIP. - The object's existence –
CREATE,DROP,UNDROP. - Who else has access to the object –
OWNERSHIP,WITH GRANT OPTION,MANAGE GRANTS. An actor holding any of these can widen every boundary above for other actors.
Invocation and extraction
Two interaction types fall outside this categorization:
- Invocation. An actor may execute an object without reading it. A user having
USAGEon an executable lets them run the application without granting them the ability to view its source or the tables behind it. - Extraction. Reading a value once and copying it somewhere ungoverned are
different acts.
CREATE TABLE ... AS,COPY INTOa stage and resharing all move data past the boundary where the above policies apply. Note that the "ungoverned space" also includes the executable code itself.
Rights modes in practice
The dataset and a sample application below illustrate how the two modes function on top of the standard RBAC:
ANALYTICS_DB :: database SALES_SCH :: schema PIPELINE_TBL :: has a row access policy that establishes "only show row <=> rep_owner = CURRENT_USER()" REVENUE_TBL :: no policy, visible to all viewers APPS_DB :: database PUBLIC_SCH :: schema PIPELINE_EXPLORER :: the STREAMLIT object Roles PIPELINE_APP_OWNER_RL :: service role owning PIPELINE_EXPLORER object SALES_REP_RL :: the role of Alice, a sales rep APP_WH :: warehouse that runs queries from the application ADHOC_WH :: warehouse used by Alice
The code for PIPELINE_EXPLORER is:
"""Illustrate SQL results for calling same statements against owner and caller session.""" import streamlit as st # Create two sessions, one for owner and another for the caller owner_session = st.connection("snowflake").session() caller_session = st.connection("snowflake-callers-rights").session() # Produce permutations of owner and caller sessions, run SQL statements against those permutations for s1, s2, mode_name in ( (owner_session, owner_session, "Owner's rights"), (caller_session, caller_session, "Restricted caller's rights"), (caller_session, owner_session, "Mixed"), ): st.write(mode_name) st.dataframe(s1.sql("SELECT * FROM ANALYTICS_DB.SALES_SCH.PIPELINE_TBL")) # ① st.dataframe(s2.sql("SELECT * FROM ANALYTICS_DB.SALES_SCH.REVENUE_TBL")) # ②
When the PIPELINE_EXPLORER application is created, it becomes owned by the
PIPELINE_APP_OWNER_RL role. This role is determined by the primary role of the
user that executed CREATE STREAMLIT statement; secondary roles and other
attributes of that user are discarded.
When the application runs, Snowflake generates an internal service user with
SNOWFLAKE_SERVICE type and the name similar to STPLATSTREAMLIT.... This user
gets only the PIPELINE_APP_OWNER_RL role.
This example uses a very flat role hierarchy. In real-world scenarios, you don't need to grant privileges directly to the roles, you can grant a privilege to a role and then grant that role to the role that is ultimately granted to an actor. Using Database Roles minimizes the privilege sprawl.
Owner's Rights Mode

Both code lines ① and ② run on top of owner_session.
Minimal required privileges:
-
PIPELINE_APP_OWNER_RL:For data access:
USAGEonANALYTICS_DB,SALES_SCHSELECTonPIPELINE_TBL,REVENUE_TBLUSAGEon theAPP_WHwarehouse
If a data grant is missing, ① or ② fails with
Database 'ANALYTICS_DB' does not exist or not authorized, naming whichever level the role cannot reach.For app creation:
USAGEonAPPS_DB,PUBLIC_SCHCREATE STREAMLITonPUBLIC_SCH
-
SALES_REP_RL:USAGEonAPPS_DB,PUBLIC_SCHUSAGEonPIPELINE_EXPLORERSTREAMLITobject
SALES_REP_RL needs no grants on ANALYTICS_DB or the objects inside it.
When Alice accesses the application, she would not be able to see any results in
① since CURRENT_USER() evaluates to the ephemeral user with
PIPELINE_APP_OWNER_RL role. ② would show results since there is no check on
the data. In this mode, Alice does not need USAGE on the APP_WH warehouse.
Restricted Caller's Rights mode

Both code lines ① and ② run on caller_session.
Minimal required privileges:
-
PIPELINE_APP_OWNER_RL:For data access:
CALLER USAGEonANALYTICS_DB,SALES_SCH,CALLER SELECTonPIPELINE_TBLandREVENUE_TBLCALLER USAGEon the warehouse. No standard grants on the data are required.
For app creation:
USAGEonAPPS_DB,PUBLIC_SCHCREATE STREAMLITonPUBLIC_SCH
-
SALES_REP_RL:USAGEonAPPS_DB,PUBLIC_SCH,ANALYTICS_DB,SALES_SCHSELECTonPIPELINE_TBL,REVENUE_TBLUSAGEon the warehouseUSAGEonPIPELINE_EXPLORERSTREAMLITobject
Note that a caller grant is not a privilege in and of itself: it authorizes the application to run with a privilege the visitor already holds, so both CALLER GRANTs to the owner role and standard GRANTs to the visitor are required. The data layer governance post in the references covers caller grants and their inherited form in depth.
Since the application code does not have an explicit USE WAREHOUSE statement,
the application will try to use Alice's default warehouse. The ADHOC_WH
warehouse thus needs both grants:
- the owner role needs
CALLER USAGEon it, or the account-levelCALLER COMPUTE USAGE - the
SALES_REP_RLrole needs ordinaryUSAGEon it
If these privileges are missing, the queries will error out with "No warehouse in session".
Unlike the previous example, ① returns the visitor's rows, since CURRENT_USER()
evaluates to the visitor. ② now requires the visitor's role to hold access to the
underlying data.
Mixed Mode

In this example line ① runs on caller_session, ② on owner_session.
Minimal required privileges:
-
PIPELINE_APP_OWNER_RL:For data access:
USAGEandCALLER USAGEonANALYTICS_DB,SALES_SCHSELECTonREVENUE_TBL, which ② reads with owner's rightsCALLER SELECTonPIPELINE_TBL, which ① reads with the visitor's identityUSAGEandCALLER USAGEon the warehouse, since ② runs as the owner and ① as the visitor
For app creation:
USAGEonAPPS_DB,PUBLIC_SCHCREATE STREAMLITonPUBLIC_SCH
-
SALES_REP_RL:USAGEonAPPS_DB,PUBLIC_SCH,ANALYTICS_DB,SALES_SCH,ADHOC_WHSELECTonPIPELINE_TBLUSAGEonPIPELINE_EXPLORERSTREAMLITobject
In this example, ① will return results matching Alice's identity since
CURRENT_USER() evaluates to the visitor. ② uses owner's rights, so the
PIPELINE_APP_OWNER_RL needs to have proper privileges.
How RBAC concepts interact with rights modes
Let's consider the key invariants that the admin wants to uphold:
-
Policy enforcement follows the human.
When the data layer is aware of the human identity, it does not matter which tool is used to access the data. RCR is naturally architected to support this, while owner's rights disables user-level policies.
However, there are cases where the humans should not be able to access individual rows, but can see aggregated/pre-calculated data. This can be achieved with aggregation/projection policies combined with row-level security.
-
Audit trails
Compliance frameworks dictate that accesses of sensitive columns/rows must be traceable to the human operator.
Owner's rights breaks this, requiring application-side logging. Queries emitted by owner's rights sessions show up as "redacted" in the query history, so admins must rely on
ACCESS_HISTORYto examine which objects are being accessed. -
The application must not be able to query objects outside of its domain
In standard (unrestricted) caller's rights, the executable can run with the full rights of the caller. This may be fine (and even required for
sudo-type) stored procedures as they have a well-defined runtime behavior. Interactive applications have more surface and are thus more prone to malicious inputs.The Restricted part of RCR helps with enforcing this. It's important to note that Snowflake has no notion of negative grants; to express denial of a certain action, the admin should not grant the corresponding privilege.
-
Elevation of privileges must be controlled
In some cases, such as writeback applications, users might need to write records into tables without having direct access to them. RCR fundamentally cannot be this gate as it requires direct write access to the underlying object. This invariant is the main use case for the mixed mode defined earlier.
Changing session context
The release of high-level caller privileges also allowed
application code to call USE ROLE / USE SECONDARY ROLE statements, switch
database/schema contexts. This means that if an access policy relies on
CURRENT_ROLE, it may stop holding true in the middle of the application.
Due to the nature of the ephemeral owner user discussed above, for an owner's
rights application to successfully run USE ROLE SOME_RL, that SOME_RL must be
granted to the role owning the application (GRANT ROLE SOME_RL TO ROLE PIPELINE_APP_OWNER_RL).
A common approach is to use IS_ROLE_IN_SESSION when writing data access
policies, as this function is more resilient to role changes. Data access
policies may also rely on immutable session variables, similar to the approach
outlined in Multi-tenancy for Cortex Agents.
Confused deputy problem
When the data enters the application memory, it becomes controlled by the code logic and data-side RBAC no longer applies. To illustrate:
A developer wants to exfiltrate credit card numbers stored in a table in
plaintext (let's pretend PCI DSS does not exist). Data is behind a row-level
security policy, allowing only BILLING_ADMIN to see the full numbers.
That developer creates an application that runs SELECT against that table on
behalf of the visitor, keeps data in RAM and shows the data if and only if the
developer accesses the application. By tricking a user with BILLING_ADMIN role
into opening the application, the malicious developer is able to exfiltrate the
data.
CALLER SELECT should be granted to roles producing applications with a degree
of source code trust, or CI checks should be run to guard against this pattern.
In addition to displaying the data in UI, data can be transported out to an external stage or API endpoint through an external access integration (EAI).
USAGE on such pathways should be very tightly controlled and granted to roles
that really need that access.
The data may also be accidentally exposed if the query results are cached inside
the application with a global cache scope, so two visiting users will see each
other's query results. Specifically for Streamlit, a static code check during
deployment should examine calls to st.cache_data.
Practical recommendations
- Use
ACCESS_HISTORYfor tracking actual access to sensitive objects. - Use tag-based policies to assign row-level security at scale.
- Deploy enterprise-wide applications into a shared schema as described in
Building Streamlit Apps At Scale on Snowflake.
Broadly grant
USAGEon that schema and the applications, but assignCREATE STREAMLITonly to authorized roles. - Broad inherited caller grants are suitable for centralized deployments as outlined in Implementing True Data Layer Governance. For citizen developers, make sure they don't create confused deputy risks.
- Put row access and masking policies on any object an application exposes. These controls hold no matter which rights mode the application picks, who wrote it, or what the code does next, and they keep working if the app is rewritten or its rights mode is changed.
- Audit caller grants separately using
SHOW CALLER GRANTS. StandardSHOW GRANTSwill not show caller grants, so a role that looks narrow can still carry a wide authorization for every application it owns. - When using RCR, ensure that visiting users have a default warehouse assigned,
or establish a convention to
USE WAREHOUSEin the application code. The application owner role must holdCALLER USAGEon the warehouse used by the visitor. - Use
IS_ROLE_IN_SESSIONinstead ofCURRENT_ROLEin policies so secondary roles activated inside the application correctly grant access. - Granting
CALLER SELECTto application owner roles requires source code trust. USAGEon exfiltration paths (EXTERNAL ACCESS INTEGRATION,STAGE) should be granted sparingly.- Grants don't need to be direct. Privileges can be granted to a role which can, in turn, be granted to another role, which the users will use.
- While not related to rights modes, consider using
ALLOWED_INTERFACES(doc) to restrict users to only be able to access specific parts of the platform.
References / further reading
- How data governance is done on the internal Snowflake account:
- Building Streamlit Apps at Scale on Snowflake
This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances