All ArticlesRuntime Truth
Access & Permissions
Feature Blog
AI Agent Security

Securing Snowflake Cortex

Cortex agents run with the full authority of the role that invokes them, so securing Cortex is a data-layer access problem, not a model problem.

Obsidian Editorial Team
Security Research
·
Obsidian Security
·
July 30, 2026
Key Takeaways

Cortex agents are not a separate security boundary. They execute under the caller's role and inherit every grant that role holds, which is why an agent can read a table the person asking never had access to. - The real question is not what a Cortex agent is configured to do. It is what its execution role actually resolves to inside Snowflake once inherited roles, future grants, and object ACLs are computed. - The riskiest setup is a service account with broad SELECT, or worse DROP TABLE, wired into a Cortex Analyst or Agent and then exposed to a wide audience. - Native Snowflake auditing shows that a query ran under a role. It does not show which agent invoked it, on whose behalf, or whether the effective access exceeded the invoker's own permissions. - Around 90% of agent identities run over-permissioned, and agents move data at machine speed, so a single over-scoped Cortex role is a standing liability, not a one-time misconfiguration. - Governing Cortex today means discovering every agent and mapping its effective access to real grants, then correcting the toxic combinations before they matter.

How Cortex agents inherit Snowflake roles

Snowflake Cortex security starts with one fact that reframes everything else: a Cortex agent has no identity of its own. It executes under the Snowflake role of the session that invokes it, and every function call, warehouse query, and semantic model lookup runs with that role's grants. The agent is the role. Whatever the role can touch, the agent can touch.

This matters because most teams wire Cortex Analysts and Agents to a service account for convenience. That service account often carries broad SELECT on shared schemas, USAGE on more than one warehouse, and sometimes OWNERSHIP or MODIFY on tables that were meant to stay read-only. The moment a business user asks the agent a question, the agent runs with the full authority of that service account, not the user's own role.

Here is the mechanism in plain terms:

  1. A user with no direct access to FINANCE.REVENUE opens a Streamlit app powered by Cortex Analyst.
  2. The app authenticates to Snowflake with a service account role that holds SELECT ON ALL TABLES IN SCHEMA FINANCE.
  3. The user asks, "What was Q4 revenue by region?"
  4. Cortex Analyst generates SQL, runs it under the service account role, and returns the answer.
  5. Snowflake logs a legitimate query from an authorized role. The user just read finance data they were never granted.

This is the same maker-mode pattern that appears in other agent builders, translated to the data warehouse. The control that matters is not the model or the prompt. It is the identity the agent borrows and the effective access that identity carries.

The Snowflake Cortex security blast radius

The blast radius of a Cortex agent equals the union of every grant its execution role holds, including everything it inherits through the role hierarchy. Grant SYSADMIN to a Cortex service role for a quick proof of concept, forget to revoke it, and that agent can drop any table in the account.

Consider a pattern that shows up again and again:

Configuration Intended use Actual blast radius
Service role with SELECT on ANALYTICS.* Answer BI questions Reads all analytics data, including PII columns
Same role also holds USAGE on PROD_WH Run queries efficiently Burns production credits with no quota control
Same role granted SYSADMIN during setup Deploy the agent Can DROP TABLE, DROP SCHEMA, and CREATE anywhere
Agent exposed via Streamlit to all staff Self-service analytics Every employee now executes as SYSADMIN

The service-account-with-DROP-TABLE case is not hypothetical. A Cortex agent that inherits a privileged role can be steered, through ordinary natural-language questions or through instructions planted in the data it reads, into running destructive SQL. Publicly reported incidents where an AI coding agent deleted a production database show the same shape in a different environment. In Snowflake, the guardrail is role isolation, not model tuning. If the role cannot drop the table, no prompt can make the agent drop it.

Discovering Cortex usage across accounts

You cannot govern what you cannot see, and Cortex usage hides. It lives inside Streamlit apps, notebooks, custom applications calling COMPLETE() or EXTRACT_ANSWER(), and Cortex Analyst semantic models scattered across schemas. Discovery is step one, and it has two halves.

Where to look inside Snowflake:

  • SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY, filtered for SNOWFLAKE.CORTEX function calls
  • SNOWFLAKE.ACCOUNT_USAGE.CORTEX_FUNCTIONS_USAGE_HISTORY for token and function-level usage
  • Streamlit inventory via SHOW STREAMLITS IN ACCOUNT
  • Semantic model files in stages, usually .yaml files that define a Cortex Analyst
  • Service users created for agent authentication, including legacy service accounts

Where Cortex usage hides outside Snowflake:

  • Third-party BI tools with embedded Cortex calls
  • Custom apps using key-pair authentication to a service role
  • Notebooks in Snowpark, Hex, or Deepnote calling Cortex functions
  • Partner integrations from Snowflake Marketplace that invoke Cortex under the hood

The gap most teams hit is attribution. Native audit tells you a query ran under a role. It does not tell you which agent, which semantic model, which downstream application, or which end user triggered it. Discovery that stops at "a Cortex function was called" leaves you with a count, not an inventory. What closes that gap is correlation: tying each Cortex call back to the specific agent, the service account it borrowed, and the tables and actions that call actually reached.

Effective access, not documented scopes

The documented scope of a Cortex agent is what its designer wrote in a spec or a semantic model YAML. The effective access is what its role resolves to inside Snowflake after every inherited role, future grant, and object ACL is computed. These are rarely the same, and the difference is where risk lives.

Two agents can share the same stated purpose, "answer sales pipeline questions," and carry very different effective access:

  • Agent A: role SALES_ANALYST_AGENT with SELECT on SALES.PIPELINE_VW only. Effective access matches the documented scope.
  • Agent B: role SALES_AGENT_SVC, which inherits ANALYST_ROLE, which inherits DATA_READER, which holds SELECT ON ALL TABLES IN DATABASE PROD. The spec says "pipeline only." The effective access includes HR, finance, and customer PII.

Auditing Cortex means computing that resolution for every agent, and recomputing it as things change. Role hierarchies get edited. GRANT SELECT ON FUTURE TABLES adds access silently. A schema added to PROD last week is readable by Agent B today, without anyone approving it. Reading the vendor's configuration page tells you the theoretical scope. Only the resolved grant path tells you the truth, and only correlating that path with what the agent actually queried tells you whether the access is being used.

Toxic combinations at the data layer

A single risk factor on a Cortex agent might be medium severity. Stack two or three and the picture turns critical fast. These toxic combinations are what a security team should rank first, because they concentrate the damage.

High-priority combinations for Cortex:

  • Cortex agent, plus a service role with SYSADMIN or ACCOUNTADMIN, plus exposure to all staff via Streamlit
  • Cortex Analyst semantic model, plus a role with SELECT on PII tables, plus no dynamic data masking
  • Cortex agent, plus an orphaned service user whose creator is disabled but whose keys still work, plus broad grants
  • Third-party Cortex integration, plus key-pair auth stored outside Snowflake, plus no key rotation
  • COMPLETE() calls sending sensitive column data, plus no masking policy on the source columns
  • Several Cortex agents sharing one service role, with no way to tell which agent ran which query

That last item is the machine insider problem in Snowflake form. When five agents share CORTEX_SVC_ROLE, the audit log shows the role ran a query. It cannot tell you which agent ran it, or on whose behalf. The fix is not more log volume. It is correlation that pins each query to a specific agent identity, the service account behind it, and the objects and actions it touched, so a toxic combination is visible as a single finding instead of five anonymous rows.

Governance and least privilege for Snowflake Cortex security

Governing Cortex means enforcing least privilege at the role, warehouse, and object level, then verifying continuously that effective access still matches intent. Configuration drifts on its own. The check that holds up is runtime truth: what each agent actually did, not what its spec claims it should do.

A practical governance baseline:

  1. Dedicated agent roles. One role per Cortex agent or agent category. Never reuse SYSADMIN or a shared analyst role.
  2. Minimum viable grants. Grant SELECT on specific views, not on schemas or databases. Keep FUTURE GRANTS off Cortex service roles.
  3. Warehouse scoping. Give each agent role USAGE on a small, monitored warehouse with a resource monitor and a credit cap.
  4. Dynamic data masking. Apply masking policies to PII columns so Cortex sees masked values unless the invoking user is explicitly cleared.
  5. Row access policies. Filter rows on the invoking user where you can, not on the agent role.
  6. Tag-based governance. Tag sensitive objects, then review every Cortex role that touches those tags.
  7. Rotate service credentials. Key-pair auth for Cortex service users should rotate on a fixed cycle. An orphaned key is an orphaned agent.
  8. Correlate the logs. Join QUERY_HISTORY with application logs so every Cortex query traces to a person, not just a service role.

Snowflake's native controls make each of these possible one at a time. What native tooling does not give you is a single view of which controls are missing across hundreds of agents, or which agent's effective access has outgrown its documented scope. That is the work Obsidian does for Cortex today: continuous discovery of every Cortex agent and non-human identity, a resolved map of effective access against real grants, and the toxic combinations ranked so you fix the ones that matter. It correlates each agent's runtime activity to the service account and the tables and actions it reached, which turns an audit log into an authority map. This runs from the account metadata Snowflake already exposes. It does not sit inline and it does not proxy your data path.

Autonomous runtime enforcement, where a decision engine blocks a Cortex agent's action in the moment, is on the roadmap for platforms like Cortex, not shipping today. It follows an "Intel Inside" model where Obsidian supplies the access and behavior intelligence to a decision point rather than becoming an inline gateway. For Cortex right now, the durable wins come from discovery, effective-access mapping, and least-privilege governance, which remove the blast radius before an agent can act on it.

Frequently Asked Questions

How does Snowflake Cortex handle data security?

Cortex runs inside Snowflake's existing RBAC, encryption, and network-policy boundary. Data is encrypted at rest and in transit under the same model as any other Snowflake workload. The variable that determines exposure is the role: Cortex functions, Analysts, and Agents can only touch what the invoking role is granted, so authorization, not encryption, is where the risk concentrates.

Why can a Cortex agent read data the person asking never had access to?

Because the agent executes under the role of the session or service account that invokes it, not the end user's own role. If a Streamlit app authenticates with a service account that has broad SELECT, every user of that app inherits that access for the duration of their question. The agent borrows the service account's effective access, which is often far wider than the user's.

How do I find every Cortex agent running in my account?

Start with QUERY_HISTORY and CORTEX_FUNCTIONS_USAGE_HISTORY for Cortex function calls, SHOW STREAMLITS IN ACCOUNT for apps, semantic model YAML files in stages, and the service users created for agent auth. The harder part is attribution: correlating each call back to a specific agent, the service account it used, and the objects it reached, rather than a raw count of Cortex queries.

What is the difference between an agent's documented scope and its effective access?

The documented scope is what a spec or semantic model says the agent should do. The effective access is what its role actually resolves to after inherited roles, future grants, and object ACLs are computed. An agent scoped "pipeline only" on paper can hold SELECT across an entire production database through role inheritance. Only the resolved grant path, checked against actual queries, shows the real picture.

How do I apply least privilege to Cortex without breaking it?

Create a dedicated role per agent, grant SELECT on specific views rather than schemas, scope each role to a small capped warehouse, and apply masking and row-access policies on sensitive columns. Avoid inheritance from SYSADMIN or account-wide reader roles, and rotate key-pair credentials on a fixed cycle. Least privilege here is per-agent, not per-team.

Can Obsidian block a Cortex agent at runtime?

Not today. For Snowflake Cortex, Obsidian discovers every agent and non-human identity, maps effective access against real grants, and surfaces toxic combinations so you can correct them. Autonomous runtime enforcement for platforms like Cortex is on the roadmap and follows an "Intel Inside" decision-engine model rather than an inline gateway. The present-day value is removing over-permissioned blast radius before an agent can act on it.