> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensorlake.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Networking

> Route internet traffic into sandbox applications and control outbound internet access

Sandboxes support two networking features:

1. Routing internet traffic into services running inside a sandbox through `*.sandbox.tensorlake.ai`
2. Restricting the sandbox's own outbound internet access

## Sandbox Public URL

Every running sandbox is reachable through sandbox-specific ingress.

* `https://<sandbox-id-or-name>.sandbox.tensorlake.ai` routes to the sandbox management API on port `9501`
* `https://<port>-<sandbox-id-or-name>.sandbox.tensorlake.ai` routes to a user service listening on `<port>` inside the sandbox

The proxy preserves the request path and query string, supports WebSocket upgrades, and forwards gRPC over HTTP/2.

The hostname can use either the sandbox ID or a sandbox name. The proxy resolves names to the sandbox's canonical ID before forwarding the request.

These examples use the familiar `*.sandbox.tensorlake.ai` hostname pattern. The returned `sandbox_url` is the management URL on port `9501`.

## Route Traffic Into Sandbox Apps

There are two access modes for internet-facing sandbox traffic:

1. `Authenticated requests`: the caller sends TensorLake auth credentials, and the proxy authorizes the request before forwarding it.
2. `Unauthenticated requests`: the sandbox owner explicitly makes selected user ports public, and the proxy skips auth for those user ports.

### Expose a User Port

Port `9501` is the built-in management API and is always routable through the bare sandbox hostname.

For any other port, the proxy only forwards requests if that port is listed in `exposed_ports`.

<Note>
  `allow_unauthenticated_access` does not expose a port by itself. User ports still have to be present in `exposed_ports`.
</Note>

#### Authenticated-Only Exposure with the HTTP API

Use this when a port should be routable from the internet but still require TensorLake auth on every request.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from tensorlake.sandbox import Sandbox


    sandbox = client.expose_ports(
        "my-env",
        [8080],
        allow_unauthenticated_access=False,
    )

    print(sandbox.exposed_ports)

    sandbox = client.unexpose_ports("my-env", [8080])
    print(sandbox.exposed_ports)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const sandbox = await client.exposePorts(
      "my-env",
      [8080],
      { allowUnauthenticatedAccess: false },
    );

    console.log(sandbox.exposedPorts);

    const updated = await client.unexposePorts("my-env", [8080]);
    console.log(updated.exposedPorts);
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    curl -X PATCH https://api.tensorlake.ai/sandboxes/<sandbox-id-or-name> \
      -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "allow_unauthenticated_access": false,
        "exposed_ports": [8080]
      }'

    curl -X PATCH https://api.tensorlake.ai/sandboxes/<sandbox-id-or-name> \
      -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "allow_unauthenticated_access": false,
        "exposed_ports": []
      }'
    ```
  </Tab>
</Tabs>

#### Unauthenticated Public Internet Access with the CLI

Use this when you want anyone on the internet to be able to reach a sandbox app without TensorLake credentials. Common cases include webhook receivers, demo apps, public APIs, browser clients, and temporary preview environments.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    tl sbx port expose <sandbox-id-or-name> 8080
    tl sbx port ls <sandbox-id-or-name>
    tl sbx port rm <sandbox-id-or-name> 8080
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from tensorlake.sandbox import Sandbox


    sandbox = client.expose_ports(
        "my-public-sandbox",
        [8080],
        allow_unauthenticated_access=True,
    )

    print(sandbox.allow_unauthenticated_access, sandbox.exposed_ports)

    sandbox = client.unexpose_ports("my-public-sandbox", [8080])
    print(sandbox.allow_unauthenticated_access, sandbox.exposed_ports)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const sandbox = await client.exposePorts(
      "my-public-sandbox",
      [8080],
      { allowUnauthenticatedAccess: true },
    );

    console.log(sandbox.allowUnauthenticatedAccess, sandbox.exposedPorts);

    const updated = await client.unexposePorts("my-public-sandbox", [8080]);
    console.log(updated.allowUnauthenticatedAccess, updated.exposedPorts);
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    curl -X PATCH https://api.tensorlake.ai/sandboxes/<sandbox-id-or-name> \
      -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "allow_unauthenticated_access": true,
        "exposed_ports": [8080]
      }'

    curl -X PATCH https://api.tensorlake.ai/sandboxes/<sandbox-id-or-name> \
      -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "allow_unauthenticated_access": false,
        "exposed_ports": []
      }'
    ```
  </Tab>
</Tabs>

The CLI `port expose` workflow sets both:

* `exposed_ports`
* `allow_unauthenticated_access=true`

So traffic to that user port becomes publicly reachable from the internet without TensorLake auth.

### Authenticated Requests

Authenticated routing is the default model for sandbox access.

* The management URL on port `9501` always requires auth
* User ports can also require auth when they are exposed but `allow_unauthenticated_access=false`

Authenticate direct requests to the sandbox proxy with a project-scoped API
key. The key selects the project, so callers do not send forwarded organization
or project headers:

```bash theme={null}
curl https://8080-<sandbox-id-or-name>.sandbox.tensorlake.ai/health \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY"
```

You can use the same authenticated routing model for HTTP, gRPC, and WebSocket services:

```bash theme={null}
# HTTP
curl https://8080-<sandbox-id-or-name>.sandbox.tensorlake.ai/health \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY"

# gRPC
grpcurl \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
  50051-<sandbox-id-or-name>.sandbox.tensorlake.ai:443 \
  list

# WebSocket
wscat \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
  -c "wss://3000-<sandbox-id-or-name>.sandbox.tensorlake.ai/socket"
```

```typescript theme={null}
const response = await fetch(
  "https://8080-my-env.sandbox.tensorlake.ai/health",
  {
    headers: {
      Authorization: `Bearer ${process.env.TENSORLAKE_API_KEY}`,
    },
  },
);

console.log(await response.text());
```

#### Browser session authentication

Browser applications can authenticate requests to the sandbox proxy with the
user's Tensorlake Cloud session cookie. This is a browser-facing proxy flow,
not SDK authentication; Python and TypeScript SDK clients continue to use only
a project-scoped API key.

The browser sends the current `tl.session_token` cookie automatically. The
legacy `tl-session` name also remains supported during migration. Because a
user session can access more than one project, include the organization and
project currently selected in the application as the `organizationId` and
`projectId` query parameters:

```typescript theme={null}
const url = new URL(
  "https://8080-my-env.sandbox.tensorlake.ai/health",
);
url.searchParams.set("organizationId", organizationId);
url.searchParams.set("projectId", projectId);

const response = await fetch(url, {
  credentials: "include",
});

console.log(await response.text());
```

Use the same query parameters for browser WebSocket clients, which cannot set
custom headers on the upgrade request:

```typescript theme={null}
const url = new URL(
  "wss://3000-my-env.sandbox.tensorlake.ai/socket",
);
url.searchParams.set("organizationId", organizationId);
url.searchParams.set("projectId", projectId);

const socket = new WebSocket(url);
```

<Note>
  The sandbox proxy consumes the Tensorlake session cookie and removes it before
  forwarding the request to the sandbox. Your application does not receive the
  platform session credential; its other cookies are preserved.
</Note>

### Unauthenticated Requests

To make a user port public on the internet, both of these conditions must be true:

* the port is in `exposed_ports`
* `allow_unauthenticated_access=true`

When those are set, the proxy skips TensorLake auth for that user port.

```bash theme={null}
curl -X PATCH https://api.tensorlake.ai/sandboxes/<sandbox-id-or-name> \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "allow_unauthenticated_access": true,
    "exposed_ports": [8080]
  }'
```

After that, requests to the exposed user port can omit auth entirely:

```bash theme={null}
curl https://8080-<sandbox-id-or-name>.sandbox.tensorlake.ai/health
```

```typescript theme={null}
const response = await fetch(
  "https://8080-my-public-sandbox.sandbox.tensorlake.ai/health",
);

console.log(await response.text());
```

<Note>
  Unauthenticated access only applies to user ports. The management API on port `9501` never becomes public.
</Note>

<Note>
  If a named sandbox is suspended, the proxy can auto-resume it when a request arrives for an exposed port.
</Note>

## Outbound Internet Access

By default, sandboxes have outbound internet access enabled. Disable it for untrusted code:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from tensorlake.sandbox import Sandbox


    sandbox = Sandbox.create(
        allow_internet_access=False
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const sandbox = await Sandbox.create({
      allowInternetAccess: false,
    });

    console.log(sandbox.sandboxId);
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    curl -X POST https://api.tensorlake.ai/sandboxes \
      -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "network": {"allow_internet_access": false}
      }'
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    tl sbx create --no-internet
    ```
  </Tab>
</Tabs>

In a verified public-cloud test, a sandbox created with `allow_internet_access=False` failed DNS resolution for `https://example.com`, confirming that outbound internet access was disabled.

Setting `allow_internet_access=false` with an empty `allow_out` blocks all outbound traffic, including DNS requests. Combined with a non-empty `allow_out`, the listed destinations stay reachable, but DNS remains blocked unless the resolver's IP address is itself listed in `allow_out`. In the CLI, `--no-internet` (or `-N`) cannot be combined with `--network-allow` or `--network-deny`.

## Allow Specific Destinations

Use `allow_out` when you want a sandbox to reach only selected destinations.

* values can be domains, leading-wildcard domains like `*.example.com`, IPv4 addresses, or IPv4 CIDR ranges
* `deny_out` takes precedence: a destination matched by both `allow_out` and `deny_out` is blocked
* hostname rules are followed across DNS changes, so a CDN-backed domain keeps working as its IP addresses rotate

A wildcard entry matches all subdomains but not the apex domain itself: `*.example.com` matches `api.example.com` and `v1.api.example.com`, but not `example.com`. Add the apex as a separate entry if you need it too. Wildcards are only supported in `allow_out`, not `deny_out`.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sandbox = Sandbox.create(
        allow_internet_access=True,
        allow_out=["example.com", "*.example.com", "203.0.113.10", "10.0.0.0/8"],
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const sandbox = await Sandbox.create({
      allowInternetAccess: true,
      allowOut: ["example.com", "*.example.com", "203.0.113.10", "10.0.0.0/8"],
    });

    console.log(sandbox.sandboxId);
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    curl -X POST https://api.tensorlake.ai/sandboxes \
      -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "network": {
          "allow_internet_access": true,
          "allow_out": ["example.com", "*.example.com", "203.0.113.10", "10.0.0.0/8"]
        }
      }'
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    tl sbx create \
      --network-allow example.com \
      --network-allow "*.example.com" \
      --network-allow 203.0.113.10 \
      --network-allow 10.0.0.0/8
    ```
  </Tab>
</Tabs>

This allows DNS requests to the sandbox's configured resolvers and traffic to the listed destinations. All other outbound traffic is blocked. The short form of `--network-allow` is `-A`. Quote wildcard entries in the shell so `*` is not expanded.

## Block Specific Destinations

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sandbox = Sandbox.create(
        deny_out=["example.com"]
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const sandbox = await Sandbox.create({
      denyOut: ["example.com"],
    });

    console.log(sandbox.sandboxId);
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    curl -X POST https://api.tensorlake.ai/sandboxes \
      -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "network": {"deny_out": ["example.com"]}
      }'
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    tl sbx create --network-deny example.com
    ```
  </Tab>
</Tabs>

In a verified public-cloud request, `deny_out=["example.com"]` blocked `https://example.com` while `https://api.openai.com/v1/models` still returned `401`, confirming outbound connectivity was still available for destinations that were not denied.

The short form of `--network-deny` is `-D`. You can combine `-A` and `-D`; deny rules take precedence when a destination matches both lists.

## Update the Policy on a Running Sandbox

You can change a sandbox's egress policy without recreating or suspending it. The new policy is applied to the running sandbox's firewall as a single atomic swap, so there is no window where egress is unprotected. Already-established connections are not interrupted.

The `network` argument is tri-state:

* **omit it** to leave the current policy unchanged (you can update `name` or exposed ports without touching the network policy),
* **pass a policy** to replace the whole policy, or
* **clear it** to return the sandbox to unrestricted egress.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from tensorlake.sandbox import CLEAR_NETWORK_POLICY, NetworkConfig, Sandbox

    sandbox = Sandbox.connect("<sandbox-id>")

    # Replace the policy: allow only api.example.com.
    sandbox.update(
        network=NetworkConfig(
            allow_internet_access=True,
            allow_out=["api.example.com"],
        )
    )

    # Later, clear the policy (unrestricted egress).
    sandbox.update(network=CLEAR_NETWORK_POLICY)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const sandbox = await Sandbox.connect({ sandboxId: "<sandbox-id>" });

    // Replace the policy: allow only api.example.com.
    await sandbox.update({
      network: {
        allowInternetAccess: true,
        allowOut: ["api.example.com"],
        denyOut: [],
      },
    });

    // Later, clear the policy (unrestricted egress) by passing null.
    await sandbox.update({ network: null });
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    # Replace the policy.
    curl -X PATCH https://api.tensorlake.ai/sandboxes/<sandbox-id> \
      -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "network": {
          "allow_internet_access": true,
          "allow_out": ["api.example.com"]
        }
      }'

    # Clear the policy by sending an explicit null.
    curl -X PATCH https://api.tensorlake.ai/sandboxes/<sandbox-id> \
      -H "Authorization: Bearer $TENSORLAKE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"network": null}'
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    # Replace the policy: allow only api.example.com and resolver-scoped DNS.
    tl sbx update <sandbox-id-or-name> -A api.example.com

    # Replace the policy and block all outbound traffic, including DNS.
    tl sbx update <sandbox-id-or-name> --no-internet

    # Clear the policy and restore unrestricted egress.
    tl sbx update <sandbox-id-or-name> --clear-network
    ```
  </Tab>
</Tabs>

Each `tl sbx update` command replaces or clears the complete network policy. Repeat `-A` or `-D` to add multiple rules to the replacement policy. `--no-internet` is an absolute block-all mode and cannot be combined with either rule flag; `--clear-network` cannot be combined with any replacement-policy flag.

<Note>
  If a hostname in the new policy cannot be resolved, the update is rejected and the previous policy stays fully enforced — the sandbox keeps running under the policy it already had.
</Note>

This is useful for phase-based agents: start a sandbox with a broad allowlist while it fetches dependencies, then tighten to a minimal policy (or block all egress) before running untrusted work.

## Network Configuration Summary

| Parameter                      | Type                | Default | Description                                                                                                                                                                                                                    |
| ------------------------------ | ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `allow_internet_access`        | `bool`              | `true`  | When `allow_out` is empty, acts as a simple on/off switch for all outbound traffic. When `allow_out` is non-empty, only controls DNS. See the matrix below                                                                     |
| `allow_out`                    | `list[str]`         | `[]`    | Allowed domains, leading-wildcard domains (`*.example.com`), IPv4 addresses, or IPv4 CIDRs. A non-empty list makes the sandbox default-deny: only the listed destinations are reachable, regardless of `allow_internet_access` |
| `deny_out`                     | `list[str]`         | `[]`    | Denied domains, IPv4 addresses, or IPv4 CIDRs (no wildcards). Takes precedence over `allow_out`: a destination matched by both is blocked                                                                                      |
| `exposed_ports`                | `list[int] \| null` | `null`  | User ports that the sandbox proxy is allowed to route to                                                                                                                                                                       |
| `allow_unauthenticated_access` | `bool`              | `false` | Skip TensorLake auth for exposed user ports. Never applies to port `9501`                                                                                                                                                      |

### How `allow_internet_access` and `allow_out` combine

| `allow_internet_access` | `allow_out` | Outbound behavior                                                                                                                 |
| ----------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `true`                  | empty       | All egress open, except destinations matched by `deny_out`                                                                        |
| `true`                  | non-empty   | Default-deny. Only the listed destinations are reachable, plus DNS to the sandbox's resolvers                                     |
| `false`                 | non-empty   | Default-deny. Only the listed destinations are reachable. DNS is blocked unless the resolver's IP is itself listed in `allow_out` |
| `false`                 | empty       | All egress blocked, including DNS                                                                                                 |

In short: when `allow_out` is empty, `allow_internet_access` is a simple switch for all outbound traffic. A non-empty `allow_out` makes the sandbox default-deny in both modes, and `allow_internet_access` then only controls DNS. With `allow_internet_access=false`, hostname entries in `allow_out` cannot resolve unless the resolver's IP is also listed, so in this case, either add the resolver IP alongside them or use IP and CIDR entries only.
