> ## Documentation Index
> Fetch the complete documentation index at: https://blaxel-feat-keep-alive-timeout-clarification.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Proxy

> Route outbound sandbox traffic through Blaxel's proxy with MITM header and body injection, inject secrets, and configure per-domain firewalls.

<Note>
  This feature is currently in public preview and is not recommended for production use.
</Note>

<span id="proxy-routing" />

<span id="domain-filtering-firewall" />

The Blaxel SDK supports two network features when creating sandboxes:

* **[Proxy routing with secrets injection](./Proxy-secrets-injection)** - the Blaxel proxy performs man-in-the-middle (MITM) interception on outbound HTTPS traffic and injects headers, body fields, and secrets server-side.
* **[Domain filtering](./Proxy-domains)** - the Blaxel proxy controls which external domains the sandbox can reach.

Proxy and network configuration can be updated after creation: you can rotate secrets, add or remove routing rules, and change the domain allowlist or denylist on a running sandbox.

<Warning>
  You cannot enable the proxy on a sandbox that was created without it, or fully disable the proxy on a sandbox that was created with it. Enabling or disabling the proxy requires a new sandbox. If you remove all routing rules and domain filters from an existing proxy-enabled sandbox, the proxy remains active transparently — traffic continues to flow through it without any secret injection or domain blocking.
</Warning>

## How it works

When a sandbox is created with a proxy config, Blaxel:

1. Sets `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables inside the sandbox
2. Installs a CA certificate and sets `NODE_EXTRA_CA_CERTS` and `SSL_CERT_FILE` so TLS clients trust the proxy
3. Performs MITM on outbound HTTPS via CONNECT tunneling
4. Matches each request against routing rules by destination domain
5. Injects configured headers and body fields, resolving `{{SECRET:name}}` placeholders server-side
6. Adds an `X-Blaxel-Request-Id` header to every proxied request for tracing

Most HTTP clients work transparently because they read the injected `HTTP_PROXY`, `HTTPS_PROXY`, and `SSL_CERT_FILE` environment variables — `curl`, `pip`, `npm`, `git`, Python `requests`, and Python `httpx` all work out of the box. Lower-level libraries that don't follow these conventions (for example, calling `urllib3` directly) need to be pointed at the proxy explicitly.

<Note>
  Localhost (`127.0.0.1`), private ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), `169.254.169.254`, `.local`, and `.internal` are always bypassed automatically.
</Note>

## Configuration reference

All network settings are passed via the `network` key in the sandbox creation options:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { SandboxInstance } from "@blaxel/core";

  const sandbox = await SandboxInstance.create({
    name: "my-sandbox",
    image: "blaxel/base-image:latest",
    region: "us-was-1",
    network: {
      // Domain filtering (firewall)
      allowedDomains: [...],
      forbiddenDomains: [...],
      // Proxy with header/body injection
      proxy: {
        routing: [...],
        bypass: [...],
      },
    },
  });
  ```

  ```python Python theme={null}
  from blaxel.core.sandbox import SandboxInstance

  sandbox = await SandboxInstance.create({
      "name": "my-sandbox",
      "image": "blaxel/base-image:latest",
      "region": "us-was-1",
      "network": {
          # Domain filtering (firewall)
          "allowedDomains": [...],
          "forbiddenDomains": [...],
          # Proxy with header/body injection
          "proxy": {
              "routing": [...],
              "bypass": [...],
          },
      },
  })
  ```
</CodeGroup>

### `SandboxNetwork`

| Field              | Type                     | Description                                                                                                                  |
| ------------------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `allowedDomains`   | `string[]` / `list[str]` | Allowlist — only these domains are reachable. Supports wildcards (`*.s3.amazonaws.com`).                                     |
| `forbiddenDomains` | `string[]` / `list[str]` | Denylist — all domains except these are reachable. Supports wildcards. If both are set, `forbiddenDomains` takes precedence. |
| `proxy`            | `ProxyConfig`            | Proxy routing and bypass configuration.                                                                                      |

### `ProxyConfig`

| Field     | Type                                  | Description                                                                   |
| --------- | ------------------------------------- | ----------------------------------------------------------------------------- |
| `routing` | `ProxyTarget[]` / `list[ProxyTarget]` | Per-destination routing rules with header/body injection.                     |
| `bypass`  | `string[]` / `list[str]`              | Domains added to `NO_PROXY` that skip the proxy entirely. Supports wildcards. |

### `ProxyTarget`

| Field          | Type                              | Description                                                                                                                                                          |
| -------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `destinations` | `string[]` / `list[str]`          | Domain patterns this rule applies to. Use `["*"]` for a global catch-all rule. Supports wildcards (`*.example.com` matches `sub.example.com` but not `example.com`). |
| `headers`      | `Record<string, string>` / `dict` | Headers injected into matching requests. Values may contain `{{SECRET:name}}` references.                                                                            |
| `body`         | `Record<string, string>` / `dict` | JSON body fields injected into matching requests. Values may contain `{{SECRET:name}}` references.                                                                   |
| `secrets`      | `Record<string, string>` / `dict` | Named secret values for this rule. Referenced via `{{SECRET:name}}` in headers and body. Write-only — never returned in API responses. Stored encrypted at rest.     |

## Region availability

Proxy availability is region-dependent. The `Region` type includes a `proxyAvailable` boolean field. Check region support before relying on proxy features:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { listRegions } from "@blaxel/core";

  const { data: regions } = await listRegions({ throwOnError: true });
  for (const r of regions) {
    console.log(`${r.name}: proxy=${r.proxyAvailable}`);
  }
  ```

  ```python Python theme={null}
  from blaxel.core import listRegions

  result = await listRegions()
  for r in result.data:
      print(f"{r.name}: proxy={r.proxy_available}")
  ```
</CodeGroup>

## Environment variables set inside the sandbox

When proxy is configured, the sandbox automatically has:

| Variable              | Purpose                                                                 |
| --------------------- | ----------------------------------------------------------------------- |
| `HTTP_PROXY`          | Proxy URL for HTTP traffic                                              |
| `HTTPS_PROXY`         | Proxy URL for HTTPS traffic                                             |
| `NO_PROXY`            | Comma-separated bypass list (always includes localhost, private ranges) |
| `NODE_EXTRA_CA_CERTS` | Path to CA cert for Node.js TLS verification                            |
| `SSL_CERT_FILE`       | Path to CA cert for other TLS clients (`curl`, Python, etc.)            |

## CLI tool compatibility

When proxy is enabled, common CLI tools and high-level clients work transparently inside the sandbox with no extra configuration:

| Tool              | Protocol | Notes                                                        |
| ----------------- | -------- | ------------------------------------------------------------ |
| `curl`            | HTTPS    | Automatic via `HTTPS_PROXY` env var                          |
| `git`             | HTTPS    | May need `GIT_SSL_CAINFO=$SSL_CERT_FILE` for some operations |
| `pip` / `pip3`    | HTTPS    | Automatic                                                    |
| `npm` / `npx`     | HTTPS    | Automatic                                                    |
| Python `requests` | HTTPS    | Automatic via env vars                                       |
| Python `httpx`    | HTTPS    | Automatic via env vars                                       |

Python's stdlib `urllib.request` is not recommended with the proxy. Prefer `requests` or `httpx`, or configure `urllib3.ProxyManager` directly if you need lower-level control:

```python theme={null}
import os
import urllib.parse
import urllib3

proxy_url = os.environ["HTTPS_PROXY"]
_parsed = urllib.parse.urlsplit(proxy_url)
_userinfo = _parsed.netloc.rsplit("@", 1)
proxy_kwargs = {}
if len(_userinfo) == 2:
    proxy_auth = urllib.parse.unquote(_userinfo[0])
    proxy_kwargs["proxy_headers"] = urllib3.make_headers(proxy_basic_auth=proxy_auth)
http = urllib3.ProxyManager(
    proxy_url,
    **proxy_kwargs,
    cert_reqs="CERT_REQUIRED",
    ca_certs=os.environ.get("SSL_CERT_FILE"),
)
resp = http.request(
    "GET",
    "https://api.stripe.com/v1/charges"
)
print(resp.status, resp.data.decode())
```

## Behavior details

* **Wildcard matching**: `*.example.com` matches `sub.example.com` and `a.b.example.com` but not `example.com` itself
* **No cross-route leakage**: Headers/secrets from one routing rule are never applied to requests matching a different rule
* **User headers preserved**: The proxy adds injected headers alongside any headers the sandbox code sends — it does not overwrite user-sent headers
* **Body merge**: Injected body fields are merged into JSON request bodies up to 1 MB. User-sent fields take precedence on key collisions. Requests over 1 MB, requests without a known `Content-Length` (e.g. chunked uploads), and streaming RPCs are forwarded unchanged — use header injection for credentials in those cases
* **Tracing**: Every proxied request gets an `X-Blaxel-Request-Id` header for observability
* **Local traffic**: Requests to `localhost` / `127.0.0.1` are never routed through the proxy

### HTTP/2 and gRPC

The proxy supports HTTP/2 and gRPC. Clients that previously required HTTP/1.1 workarounds now work without any flags or downgrades:

* gRPC SDKs in Go, Python (`grpcio`), and Node (`@grpc/grpc-js`)
* `curl --http2`
* Go `net/http` (HTTP/2 is the default)

All four gRPC RPC styles — unary, server-streaming, client-streaming, and bidirectional — flow through, and gRPC trailers (`grpc-status`, `grpc-message`) are preserved end-to-end.

A few things to keep in mind:

* **Header injection works on every request**, including streaming RPCs.
* **Body injection is skipped on streaming requests** (gRPC streams, chunked uploads). Use header injection to attach credentials on streaming workloads — see the body injection limits in [Behavior details](#behavior-details).

## Update proxy configuration

You can update proxy routing rules, secrets, domain filters, and bypass lists on a running sandbox using the SDK's `updateNetwork` method. This fetches the current sandbox, merges the new network config, and applies the update in one call:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { SandboxInstance } from "@blaxel/core";

  await SandboxInstance.updateNetwork("my-sandbox", {
    network: {
      allowedDomains: ["api.stripe.com", "api.openai.com", "api.anthropic.com"],
      proxy: {
        routing: [
          {
            destinations: ["api.stripe.com"],
            headers: { "Authorization": "Bearer {{SECRET:stripe-key}}" },
            secrets: { "stripe-key": "sk_live_REPLACE_WITH_YOUR_KEY" },
          },
          {
            destinations: ["api.anthropic.com"],
            headers: { "x-api-key": "{{SECRET:anthropic-key}}" },
            secrets: { "anthropic-key": "REPLACE_WITH_YOUR_ANTHROPIC_KEY" },
          },
        ],
      },
    },
  });
  ```

  ```python Python theme={null}
  from blaxel.core.sandbox import SandboxInstance
  from blaxel.core.sandbox.types import SandboxUpdateNetwork

  await SandboxInstance.update_network(
      "my-sandbox",
      SandboxUpdateNetwork(network={
          "allowedDomains": ["api.stripe.com", "api.openai.com", "api.anthropic.com"],
          "proxy": {
              "routing": [
                  {
                      "destinations": ["api.stripe.com"],
                      "headers": {"Authorization": "Bearer {{SECRET:stripe-key}}"},
                      "secrets": {"stripe-key": "sk_live_REPLACE_WITH_YOUR_KEY"},
                  },
                  {
                      "destinations": ["api.anthropic.com"],
                      "headers": {"x-api-key": "{{SECRET:anthropic-key}}"},
                      "secrets": {"anthropic-key": "REPLACE_WITH_YOUR_ANTHROPIC_KEY"},
                  },
              ],
          },
      }),
  )
  ```

  ```bash cURL theme={null}
  curl -X PUT "https://api.blaxel.ai/v0/sandboxes/my-sandbox" \
    -H "Authorization: Bearer $BL_API_KEY" \
    -H "X-Blaxel-Workspace: my-workspace" \
    -H "Content-Type: application/json" \
    -d '{
      "metadata": { "name": "my-sandbox" },
      "spec": {
        "network": {
          "allowedDomains": ["api.stripe.com", "api.openai.com", "api.anthropic.com"],
          "proxy": {
            "routing": [
              {
                "destinations": ["api.stripe.com"],
                "headers": { "Authorization": "Bearer {{SECRET:stripe-key}}" },
                "secrets": { "stripe-key": "sk_live_REPLACE_WITH_YOUR_KEY" }
              },
              {
                "destinations": ["api.anthropic.com"],
                "headers": { "x-api-key": "{{SECRET:anthropic-key}}" },
                "secrets": { "anthropic-key": "REPLACE_WITH_YOUR_ANTHROPIC_KEY" }
              }
            ]
          }
        }
      }
    }'
  ```
</CodeGroup>

The update replaces the entire `network` configuration. Include all routing rules, domain filters, and secrets you want active after the update.

<Warning>
  You cannot enable the proxy on a sandbox that was created without it, or fully disable the proxy on a sandbox that was created with it. Removing all routing rules and domain filters does not disable the proxy — traffic continues to flow through it transparently without any injection or blocking.
</Warning>

## Full example: agent sandbox with proxy + firewall

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { SandboxInstance } from "@blaxel/core";

  const sandbox = await SandboxInstance.create({
    name: "agent-workspace",
    image: "blaxel/base-image:latest",
    region: "us-was-1",
    labels: { team: "ml", env: "staging" },
    network: {
      allowedDomains: [
        "api.stripe.com",
        "api.openai.com",
        "httpbin.org",
        "*.s3.amazonaws.com",
      ],
      proxy: {
        routing: [
          {
            destinations: ["api.stripe.com"],
            headers: {
              "Authorization": "Bearer {{SECRET:stripe-key}}",
              "Stripe-Version": "2024-12-18.acacia",
            },
            body: {
              "api_key": "{{SECRET:stripe-key}}",
            },
            secrets: {
              "stripe-key": "sk-live-abc123...",
            },
          },
          {
            destinations: ["api.openai.com"],
            headers: {
              "Authorization": "Bearer {{SECRET:openai-key}}",
              "OpenAI-Organization": "org-abc123",
            },
            secrets: {
              "openai-key": "sk-proj-xyz789...",
            },
          },
        ],
        bypass: ["*.s3.amazonaws.com"],
      },
    },
  });

  // curl https://api.stripe.com/...  -> gets auth header + body injected
  // curl https://api.openai.com/...  -> gets auth header injected
  // curl https://httpbin.org/...     -> allowed, no injection
  // curl https://evil.com/...        -> BLOCKED by allowedDomains firewall

  const result = await sandbox.process.exec({
    command: "curl -s https://api.stripe.com/v1/charges",
    waitForCompletion: true,
  });
  console.log(result.logs);
  ```

  ```python Python theme={null}
  from blaxel.core.sandbox import SandboxInstance

  sandbox = await SandboxInstance.create({
      "name": "agent-workspace",
      "image": "blaxel/base-image:latest",
      "region": "us-was-1",
      "labels": {"team": "ml", "env": "staging"},
      "network": {
          "allowedDomains": [
              "api.stripe.com",
              "api.openai.com",
              "httpbin.org",
              "*.s3.amazonaws.com",
          ],
          "proxy": {
              "routing": [
                  {
                      "destinations": ["api.stripe.com"],
                      "headers": {
                          "Authorization": "Bearer {{SECRET:stripe-key}}",
                          "Stripe-Version": "2024-12-18.acacia",
                      },
                      "body": {
                          "api_key": "{{SECRET:stripe-key}}",
                      },
                      "secrets": {
                          "stripe-key": "sk-live-abc123...",
                      },
                  },
                  {
                      "destinations": ["api.openai.com"],
                      "headers": {
                          "Authorization": "Bearer {{SECRET:openai-key}}",
                          "OpenAI-Organization": "org-abc123",
                      },
                      "secrets": {
                          "openai-key": "sk-proj-xyz789...",
                      },
                  },
              ],
              "bypass": ["*.s3.amazonaws.com"],
          },
      },
  })

  # curl https://api.stripe.com/...  -> gets auth header + body injected
  # curl https://api.openai.com/...  -> gets auth header injected
  # curl https://httpbin.org/...     -> allowed, no injection
  # curl https://evil.com/...        -> BLOCKED by allowedDomains firewall

  result = await sandbox.process.exec({
      "command": "curl -s https://api.stripe.com/v1/charges",
      "wait_for_completion": True,
  })
  print(result.logs)
  ```
</CodeGroup>
