Cortex Sandboxes concepts and security¶
This page explains the security and operational model of Cortex Sandboxes.
Isolation model¶
Each sandbox is a durable, named handle that persists until you terminate it; the container behind it is suspended when idle (see Lifecycle). It runs with your Snowflake identity, is not a public service endpoint, and is isolated from other sandboxes and other Snowflake accounts.
- A sandbox runs as your session identity. Code inside acts as your Snowflake user and is bound by the same RBAC, row-access, and masking policies as a query you run yourself. The role of the creating session is injected as
SNOWFLAKE_ROLE. - Network egress is controlled by the sandbox’s
Egressconfiguration. The default gives the container the platform baseline: Snowflake endpoints, the cloud-storage stages behind them, and a curated set of package-manager hosts such as PyPI and npm, which acceptGETandHEADrequests only. Every other destination is blocked, including GitHub and dbt hosts. Egress can instead be closed to Snowflake alone, or widened to named external hosts with an External Access Integration. Restricting package-manager hosts toGETandHEADlimits, but does not prevent, data leaving the sandbox. Packages installed from these hosts are third-party software that you are responsible for vetting.
Session identity and governance¶
A sandbox runs with your Snowflake identity. When you create a sandbox, the platform provisions a Snowflake credential for that identity in the container automatically. This means:
- Code inside the sandbox queries Snowflake by reading
/snowflake/session/token, with no credential configuration, environment variables, or secrets to set up. That file holds a per-sandbox placeholder, never the real credential. The egress proxy substitutes the real one on Snowflake-bound requests, so the raw credential is not exposed to your code or an agent. Code in the sandbox can still make any Snowflake request your identity is authorized to make. The credential is short-lived, refreshed automatically, and works only from inside the sandbox. - Row-level security (RLS), column masking, and role-based access control apply to whatever the sandbox reads or writes, exactly as they would for a query you run directly. These policies apply when code reads data from Snowflake. Important: once data is in the sandbox, Snowflake policies don’t control what code does with it, including writing it to files or sending it to any host the sandbox’s egress configuration allows.
- By default, the sandbox runs with your primary role and all of your secondary roles, so code in the sandbox, including agent-generated code, can use any privilege those roles grant. To restrict the sandbox to a single role’s grants, specify that role when you create the sandbox. We recommend doing so for agent-driven or automated workloads.
- Audit logging: every query the sandbox runs appears in
SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORYattributed to your user, the same as any other SQL session.
Secrets¶
You can provide secrets to a sandbox using Snowflake SECRET objects. When you reference a secret by name at sandbox creation, the platform injects a placeholder into the container and substitutes the real value on outbound network requests to the declared host. The actual secret value is not placed in the container or the SDK. It is brokered entirely on the egress proxy.
Declaring a secret’s host also grants network reachability to that host, and unlike the package-manager baseline it is not restricted to GET and HEAD: you can POST and PUT to a host opened this way, which is what authenticating in order to write requires. Because a declared host accepts POST and PUT, code in the sandbox can also send data to it. Reachability is granted only to the exact host you declare, so declare the narrowest host possible. For how reachability and secret substitution differ, see Give a sandbox secure access to an external API.
Lifecycle¶
A sandbox is a durable handle. Its name is the identity you reconnect with; it also has an id:
- Create.
Sandbox.create(...)creates the container and returns a live handle. - Run. Run commands with
exec(to completion) orexec_stream(line-by-line streaming). Send messages to a warm session withsend. - Reconnect. Reattach to an existing sandbox by its
namefrom any process. - Suspend and resume. Bank the sandbox’s state with
suspend(), and wake it withresume(). Both are optional: the platform does this on its own idle timer, and any call that touches a suspended sandbox resumes it transparently. - Terminate. Destroy the container with
terminate(). This is also what releases the sandbox’s slot against your live-sandbox cap: a suspended sandbox still occupies one.
Suspension is automatic, and can also be requested. The platform suspends a sandbox that has been idle, which stops it consuming compute but does not delete it, and it resumes on next use. The sandbox stays addressable across a suspend; what does not survive is the container behind it (see below). The default idle window is 10 minutes; set idle_suspend= on Sandbox.create() to choose a different one, down to a minimum of one minute. Detached CLI runs default to a 24-hour window.
You can also drive both ends yourself, with sb.suspend() and sb.resume() in the SDK or snow sandbox suspend and snow sandbox resume on the CLI. Neither changes what suspension is: the mechanics below are identical whether the platform suspended the sandbox or you did. What they change is when:
suspend()banks state now rather than after the idle window elapses, such as at the end of an agent turn or before a deploy. It is idempotent, so calling it on an already-suspended sandbox succeeds without doing anything, and it returns once the snapshot exists and the container is gone.resume()pays the cold start up front instead of inside the firstexec(), which is worth doing before a latency-sensitive call. It is rarely needed otherwise. It returns once the resume is under way rather than once the sandbox is serving.refresh()still reportsreadyduring that window, so the nextexec()is what waits for it.
Idle means no inbound traffic, which is not the same as doing nothing. CPU and disk work do not hold a sandbox open, so a long compute loop with nothing arriving is suspended on schedule, which is why a detached CLI run defaults to a 24 hour window. Note: polling a sandbox’s status counts as inbound traffic, so a monitoring loop keeps every sandbox it watches alive and consuming compute.
A resume after suspension is always a cold start. Memory is not preserved: the container is frozen, its filesystem snapshotted, then stopped and deleted, and the resume builds a fresh container from the base image plus that snapshot. So every running process is gone and tmpfs (/tmp, /dev, /run, /snowflake/stages) is wiped, while writes to the persistent rootfs (/var/tmp, /var/log, /home, /root) come back. Nothing re-executes the sandbox’s command on resume, so a detached run does not restart itself. Keep durable state in a Snowflake table or a stage, and compare sb.generation across calls to detect a restart.
For the full API surface, see the Cortex Sandboxes SDK reference.