Tutorial: Give a sandbox secure access to an external API

This tutorial shows how to let code inside a Cortex Sandbox call an external API (GitHub in the examples) using a brokered secret and an egress policy. When you are done, you will have seen the two security properties that make this safe:

  • The secret’s value is not placed in the sandbox. Code inside the sandbox sees only a randomly generated placeholder. The real value is substituted on outbound requests to the host you declare.
  • The sandbox can only reach the external hosts you grant. An arbitrary internet host is refused by the egress proxy unless an External Access Integration grants it. Snowflake itself and the cloud-storage stages backing the sandbox stay reachable at every setting, so this confines which external hosts the code can call — it is not a data-exfiltration boundary against a Snowflake-reachable destination.

Time to complete: about 20 minutes.

Prerequisites

  • The snowflake-sandbox-python SDK installed and a Snowflake connection configured. See Cortex Sandboxes quickstart.
  • A Snowflake SECRET object that holds the credential you want to use, with USAGE granted to the role that creates the sandbox.
  • An External Access Integration (EAI) that grants the exact host your code calls, with USAGE granted to that same role. An EAI is the only per-host egress grant the platform applies, so the example below cannot reach api.github.com without one. The account also needs EAI egress-context lookups enabled.

Create the SECRET

Create a SECRET that holds the API token for the service you want to call. The example below uses a generic-string SECRET named GH_TOKEN in the <your_database>.<your_schema> schema storing a GitHub token with the scopes your code needs.

CREATE SECRET <your_database>.<your_schema>.GH_TOKEN
  TYPE    = GENERIC_STRING
  SECRET_STRING = '<your-github-token>';

GRANT USAGE ON SECRET <your_database>.<your_schema>.GH_TOKEN TO ROLE <your_role>;

Keep the token out of the sandbox. You only ever read USAGE on the SECRET — you never read its value, and neither does your code. Nothing secret is written to your driver script, the code bundle, or the container.

Create the External Access Integration

The SECRET supplies the credential; it does not open the network. Grant the host your code calls with a network rule and an EAI. List every host you actually call: a grant matches a hostname exactly, so github.com does not cover api.github.com.

CREATE NETWORK RULE <your_database>.<your_schema>.GH_RULE
  MODE = EGRESS
  TYPE = HOST_PORT
  VALUE_LIST = ('api.github.com:443', 'github.com:443');

CREATE EXTERNAL ACCESS INTEGRATION GH_EAI
  ALLOWED_NETWORK_RULES = (<your_database>.<your_schema>.GH_RULE)
  ENABLED = TRUE;

GRANT USAGE ON INTEGRATION GH_EAI TO ROLE <your_role>;

The network rule cannot live in a personal USER$ database.

Refer the secret and lock down egress

In Python, reference the SECRET with Secret.from_name(...). Give it an environment variable name and scope it to the GitHub host. Then set an Egress policy that closes the default egress baseline and grants back only your EAI’s hosts:

from snowflake.sandbox import Secret

secret = Secret.from_name("<your_database>.<your_schema>.GH_TOKEN", env_var="GH_TOKEN", host="github.com")

The host scopes the credential substitution to a host pattern. For substitution a bare github.com also covers api.github.com and codeload.github.com, so one secret serves both cloning and API calls.

Substitution scope and network reachability are two different controls, and they do not use the same matching. The secret’s host grants reachability to that hostname exactly — host="github.com" does not make api.github.com reachable, even though the credential would be substituted there. Reachability for anything else comes from the sandbox’s Egress, and the only per-host grant is an External Access Integration.

Run a fully verified example

The following program creates a sandbox, mounts the brokered secret, calls the GitHub API, and then tries a host that is not allowed. It is the pattern to build on for any external-API workload.

from snowflake.sandbox import Sandbox, Secret, Egress

PROBE = r"""
import os, json, urllib.request, urllib.error

tok = os.environ.get("GH_TOKEN", "")

def call(url):
    req = urllib.request.Request(
        url,
        headers={"Authorization": f"Bearer {tok}", "User-Agent": "sandbox-tutorial"},
    )
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            print(url, "->", r.status)
    except urllib.error.HTTPError as e:
        print(url, "-> HTTP", e.code, e.read().decode("utf-8", "replace")[:60])
    except Exception as e:
        print(url, "->", type(e).__name__)

call("https://api.github.com/user")   # allowlisted host
call("https://example.com")           # not allowlisted - should be refused
"""

secret = Secret.from_name("<your_database>.<your_schema>.GH_TOKEN", env_var="GH_TOKEN", host="github.com")
with Sandbox.create(
    secrets=[secret],
    egress=Egress(allow_public_package_registries=False, external_access_integrations=("GH_EAI",)),
) as sb:
    result = sb.exec(["python", "-c", PROBE])
    print(result.stdout)

Expected output:

https://api.github.com/user -> 200
https://example.com -> HTTP 403 egress to this host is not allowed

The first line confirms the brokered credential reached an allowlisted host. The second confirms the second host did not. How the refusal surfaces depends on where it is enforced: if the proxy answers with a status, you get the -> HTTP 403 line above; if the connection is refused outright, the probe’s final except prints the exception name instead, for example -> URLError. Either way the request did not leave the sandbox.

What the output proves

  • The call to api.github.com succeeds. Two separate controls had to line up: the External Access Integration made the host reachable, and the egress proxy substituted the real token from your SECRET on the outbound request — so the API authenticates you even though the token never entered the container.
  • The call to example.com is refused with 403 egress to this host is not allowed. That message comes from the egress proxy, not the target service, and is what confines the sandbox to the hosts you permitted.

Next steps

  • To reach a different API, create a SECRET for it, scope it to that host with host=, add the host to a network rule behind an External Access Integration, and pass Egress(allow_public_package_registries=False, external_access_integrations=("YOUR_EAI",)). The secret broker handles the credential substitution, but it does not grant the host — the integration does. Egress.only() on its own takes no arguments and grants nothing back, so a sandbox created with it cannot reach the API at all.
  • See Cortex Sandboxes SDK reference for the full Secret and Egress API, including Egress variants such as external_access_integrations.