> ## 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`

Verified against `sandbox-proxy`, the proxy accepts these auth modes:

* API key: `Authorization: Bearer <api-key>`
* Personal access token: `Authorization: Bearer tl_pat...` plus `X-Forwarded-Organization-Id` and `X-Forwarded-Project-Id`
* Session cookie: `tl.session_token` or legacy `tl-session`, plus the same forwarded organization/project context

For browser WebSocket clients that cannot set custom `X-Forwarded-*` headers, the proxy also accepts `organizationId` and `projectId` in the query string.

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

  <Tab title="PAT">
    ```bash theme={null}
    curl https://8080-<sandbox-id-or-name>.sandbox.tensorlake.ai/health \
      -H "Authorization: Bearer $TENSORLAKE_PAT" \
      -H "X-Forwarded-Organization-Id: $TENSORLAKE_ORGANIZATION_ID" \
      -H "X-Forwarded-Project-Id: $TENSORLAKE_PROJECT_ID"
    ```
  </Tab>

  <Tab title="Cookie">
    ```bash theme={null}
    curl https://8080-<sandbox-id-or-name>.sandbox.tensorlake.ai/health \
      -H "Cookie: tl.session_token=$TENSORLAKE_SESSION_TOKEN" \
      -H "X-Forwarded-Organization-Id: $TENSORLAKE_ORGANIZATION_ID" \
      -H "X-Forwarded-Project-Id: $TENSORLAKE_PROJECT_ID"
    ```
  </Tab>
</Tabs>

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());
```

### 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` blocks all outbound traffic, including DNS requests. 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, 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

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sandbox = Sandbox.create(
        allow_internet_access=True,
        allow_out=["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", "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", "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 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 domain, IPv4 address, and IPv4 CIDR range. All other outbound traffic is blocked. The short form of `--network-allow` is `-A`.

## 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`  | Allows internet access, including DNS requests. If `false`, all outbound traffic is blocked, including DNS requests. If `allow_out` is non-empty and this is `true`, only the listed destinations and resolver-scoped DNS requests are allowed |
| `allow_out`                    | `list[str]`         | `[]`    | Allowed domains, IPv4 addresses, or IPv4 CIDRs. A non-empty list allows the listed destinations and resolver-scoped DNS requests when `allow_internet_access` is `true`                                                                        |
| `deny_out`                     | `list[str]`         | `[]`    | Denied domains, IPv4 addresses, or IPv4 CIDRs. 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`                                                                                                                                                                      |
