> ## Documentation Index
> Fetch the complete documentation index at: https://blaxel-majoffre-feature-func-proxy.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Proxy routing with secrets injection

> Inject secrets, dynamic values, headers, and body fields into outbound sandbox requests through the Blaxel proxy so code never sees raw API keys or credentials.

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

The Blaxel proxy intercepts outbound HTTPS requests from the sandbox and injects headers, body fields, and secrets server-side.

## Header injection

<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: {
      proxy: {
        routing: [
          {
            destinations: ["api.stripe.com"],
            headers: {
              "Authorization": "Bearer {{SECRET:stripe-key}}",
              "Stripe-Version": "2024-12-18.acacia",
            },
            secrets: {
              "stripe-key": "sk_live_...",
            },
          },
        ],
      },
    },
  });
  ```

  ```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": {
          "proxy": {
              "routing": [
                  {
                      "destinations": ["api.stripe.com"],
                      "headers": {
                          "Authorization": "Bearer {{SECRET:stripe-key}}",
                          "Stripe-Version": "2024-12-18.acacia",
                      },
                      "secrets": {
                          "stripe-key": "sk_live_...",
                      },
                  },
              ],
          },
      },
  })
  ```
</CodeGroup>

Code inside the sandbox calls `api.stripe.com` normally - the proxy intercepts the request, injects the `Authorization` and `Stripe-Version` headers with the resolved secret, and forwards it. The sandbox never sees the raw API key.

## Body injection (POST requests)

<CodeGroup>
  ```typescript TypeScript theme={null}
  await SandboxInstance.create({
    name: "body-injection",
    network: {
      proxy: {
        routing: [
          {
            destinations: ["api.stripe.com"],
            headers: {
              "Authorization": "Bearer {{SECRET:stripe-key}}",
            },
            body: {
              "api_key": "{{SECRET:stripe-key}}",
            },
            secrets: {
              "stripe-key": "sk_live_...",
            },
          },
        ],
      },
    },
  });
  ```

  ```python Python theme={null}
  await SandboxInstance.create({
      "name": "body-injection",
      "network": {
          "proxy": {
              "routing": [
                  {
                      "destinations": ["api.stripe.com"],
                      "headers": {
                          "Authorization": "Bearer {{SECRET:stripe-key}}",
                      },
                      "body": {
                          "api_key": "{{SECRET:stripe-key}}",
                      },
                      "secrets": {
                          "stripe-key": "sk_live_...",
                      },
                  },
              ],
          },
      },
  })
  ```
</CodeGroup>

The proxy merges body fields into outbound POST/PUT/PATCH JSON payloads. User-sent fields are preserved; injected fields are added alongside them.

## Multiple routing rules

<CodeGroup>
  ```typescript TypeScript theme={null}
  await SandboxInstance.create({
    name: "multi-route",
    network: {
      proxy: {
        routing: [
          {
            destinations: ["api.stripe.com"],
            headers: { "Authorization": "Bearer {{SECRET:stripe-key}}" },
            secrets: { "stripe-key": "sk_live_..." },
          },
          {
            destinations: ["api.openai.com"],
            headers: { "Authorization": "Bearer {{SECRET:openai-key}}" },
            secrets: { "openai-key": "sk-proj-..." },
          },
        ],
        bypass: ["*.s3.amazonaws.com"],
      },
    },
  });
  ```

  ```python Python theme={null}
  await SandboxInstance.create({
      "name": "multi-route",
      "network": {
          "proxy": {
              "routing": [
                  {
                      "destinations": ["api.stripe.com"],
                      "headers": {"Authorization": "Bearer {{SECRET:stripe-key}}"},
                      "secrets": {"stripe-key": "sk_live_..."},
                  },
                  {
                      "destinations": ["api.openai.com"],
                      "headers": {"Authorization": "Bearer {{SECRET:openai-key}}"},
                      "secrets": {"openai-key": "sk-proj-..."},
                  },
              ],
              "bypass": ["*.s3.amazonaws.com"],
          },
      },
  })
  ```
</CodeGroup>

Secrets are scoped per rule — the Stripe key is never injected into OpenAI requests and vice versa.

## Global catch-all rule

<CodeGroup>
  ```typescript TypeScript theme={null}
  await SandboxInstance.create({
    name: "global-auth",
    network: {
      proxy: {
        routing: [
          {
            destinations: ["*"],
            headers: {
              "X-Global-Auth": "Bearer {{SECRET:global-key}}",
            },
            secrets: {
              "global-key": "token-xyz",
            },
          },
        ],
      },
    },
  });
  ```

  ```python Python theme={null}
  await SandboxInstance.create({
      "name": "global-auth",
      "network": {
          "proxy": {
              "routing": [
                  {
                      "destinations": ["*"],
                      "headers": {"X-Global-Auth": "Bearer {{SECRET:global-key}}"},
                      "secrets": {"global-key": "token-xyz"},
                  },
              ],
          },
      },
  })
  ```
</CodeGroup>

The `["*"]` destination matches all proxied traffic.

## Proxy bypass

Domains listed in `bypass` skip the proxy tunnel entirely (direct connection):

<CodeGroup>
  ```typescript TypeScript theme={null}
  await SandboxInstance.create({
    name: "bypass-only",
    network: {
      proxy: {
        bypass: ["*.s3.amazonaws.com", "169.254.169.254"],
      },
    },
  });
  ```

  ```python Python theme={null}
  await SandboxInstance.create({
      "name": "bypass-only",
      "network": {
          "proxy": {
              "bypass": ["*.s3.amazonaws.com", "169.254.169.254"],
          },
      },
  })
  ```
</CodeGroup>

S3 and metadata endpoint traffic goes direct; everything else routes through the proxy.

## Secret interpolation

Secrets are referenced in headers and body values using the `{{SECRET:name}}` syntax:

```text theme={null}
"Authorization": "Bearer {{SECRET:api-token}}"          → "Bearer tok_live_abc123"
"X-Multi":       "{{SECRET:part-a}}-{{SECRET:part-b}}"  → "ALPHA-BETA"
"X-Plain":       "no-secret-here"                        → "no-secret-here" (unchanged)
```

* Multiple `{{SECRET:...}}` placeholders can appear in a single value
* Secrets are resolved server-side by the proxy — the sandbox runtime never sees raw secret values
* Secrets are write-only: the `secrets` field is stripped from API responses
* Secrets are scoped per routing rule: a secret defined on route A cannot be resolved by route B
* User code inside the sandbox can also send `{{SECRET:name}}` in its own request headers or body — the proxy will resolve them if the secret exists on the matching route

## Dynamic value injection

Alongside secrets, you can inject values that are generated fresh for every request using the `{{FUNC:name(args)}}` syntax. Use them for idempotency keys, request IDs, timestamps, nonces, and other one-time values.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await SandboxInstance.create({
    name: "idempotency-key",
    network: {
      proxy: {
        routing: [
          {
            destinations: ["api.openai.com"],
            headers: {
              "X-Request-Id": "req-{{FUNC:uuid()}}",
            },
          },
        ],
      },
    },
  });
  ```

  ```python Python theme={null}
  await SandboxInstance.create({
      "name": "idempotency-key",
      "network": {
          "proxy": {
              "routing": [
                  {
                      "destinations": ["api.openai.com"],
                      "headers": {
                          "X-Request-Id": "req-{{FUNC:uuid()}}",
                      },
                  },
              ],
          },
      },
  })
  ```
</CodeGroup>

Each request through this rule gets a different `X-Request-Id`. Function names are case-insensitive, and each occurrence is evaluated independently, so `{{FUNC:uuid()}}-{{FUNC:uuid()}}` produces two different UUIDs.

## Available functions

| Placeholder                                  | Expands to                                       |
| -------------------------------------------- | ------------------------------------------------ |
| `{{FUNC:uuid()}}` / `{{FUNC:uuidv4()}}`      | Random v4 UUID                                   |
| `{{FUNC:uuidv7()}}`                          | Time-ordered v7 UUID                             |
| `{{FUNC:timestamp()}}`                       | Current Unix time in seconds                     |
| `{{FUNC:timestamp_ms()}}`                    | Current Unix time in milliseconds                |
| `{{FUNC:timestamp_ns()}}`                    | Current Unix time in nanoseconds                 |
| `{{FUNC:datetime()}}` / `{{FUNC:iso8601()}}` | Current UTC time, RFC 3339                       |
| `{{FUNC:date()}}`                            | Current UTC date, `YYYY-MM-DD`                   |
| `{{FUNC:time()}}`                            | Current UTC time of day, `HH:MM:SS`              |
| `{{FUNC:randint()}}` / `{{FUNC:randint(n)}}` | Random integer in `[0, 2147483647)` or `[0, n)`  |
| `{{FUNC:randhex()}}` / `{{FUNC:randhex(n)}}` | 32 or `n` random hex characters (`n` up to 1024) |
| `{{FUNC:randstr()}}` / `{{FUNC:randstr(n)}}` | 16 or `n` random alphanumerics (`n` up to 1024)  |
| `{{FUNC:nonce()}}`                           | 32 random hex characters                         |

Random functions use a cryptographically secure random source. `randstr` draws uniformly from `[a-zA-Z0-9]`.

## Where dynamic values apply

`{{FUNC:*}}` expansion runs only in the header and body values you define on a routing rule. It never runs on data that code inside the sandbox sends. Request headers and body content your code sends are resolved for `{{SECRET:name}}` only; any `{{FUNC:*}}` text your code sends is passed through unchanged.

| Source                               | `{{SECRET:*}}` |    `{{FUNC:*}}`   |
| ------------------------------------ | :------------: | :---------------: |
| Routing rule header and body values  |       Yes      |        Yes        |
| Request headers sent by sandbox code |       Yes      | No (left as text) |
| Request body sent by sandbox code    |       Yes      | No (left as text) |

Within a single value, functions are expanded before secrets are substituted. A secret whose value happens to contain `{{...}}` syntax is inserted verbatim and never re-interpreted as a placeholder.

<Note>
  Functions expand only in the values you configure on a routing rule, never in data your sandbox code sends. This keeps a caller from packing a request with many expensive placeholders to force disproportionate work.
</Note>

## Reading generated values from the response

Because generated values are produced inside the proxy, each injected header or body field that used a function is echoed back on the response as its own header, keyed by the injection target:

```text theme={null}
X-Blaxel-Func-<target>: <function>=<value>[, <function>=<value> ...]
```

For example, injecting `{{FUNC:uuid()}}` into the `X-Request-Id` header adds the following response header:

```text theme={null}
X-Blaxel-Func-X-Request-Id: uuid=3f2a...c9
```

The value lists the functions used in that target as `name=value` tokens, in generation order. Only function values are echoed. Resolved `{{SECRET:*}}` values are never returned to your code. A value is echoed only when its injection resolves cleanly: if a value combines a function with a secret that fails to resolve, the whole injection is skipped and nothing is echoed for it.

## Fail-safe behavior and limits

Anything the proxy cannot resolve is left as literal text rather than blanked out or failing the request:

* Unknown function, for example `{{FUNC:notafunction()}}`
* Malformed placeholder with missing parentheses, for example `{{FUNC:uuid}}`
* Invalid argument, for example `{{FUNC:randhex(abc)}}` or `{{FUNC:randint(0)}}`
* Over the size cap, for example `{{FUNC:randstr(5000)}}`
* Beyond the per-request budget

Two limits bound the work any single request can trigger:

* Per-function output: `randstr` and `randhex` cap their length argument at 1024 characters
* Per-request count: at most 10 functions are expanded per request, counted across all header and body values combined. Any beyond the limit are left as literal text

## Reading proxy config from an existing sandbox

After creation or retrieval, network config is available as typed model attributes:

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

  const sandbox = await SandboxInstance.get("my-sandbox");
  const network = sandbox.spec.network;

  if (network?.proxy?.routing) {
    for (const route of network.proxy.routing) {
      console.log(route.destinations);
      console.log(route.headers["Authorization"]);
    }
    if (network.proxy.bypass) {
      console.log(network.proxy.bypass);
    }
  }

  if (network?.allowedDomains) {
    console.log(network.allowedDomains);
  }
  ```

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

  sandbox = await SandboxInstance.get("my-sandbox")
  network = sandbox.spec.network  # SandboxNetwork (or Unset)

  if not isinstance(network, Unset) and not isinstance(network.proxy, Unset):
      for route in network.proxy.routing:
          print(route.destinations)
          print(route.headers["Authorization"])
      if not isinstance(network.proxy.bypass, Unset):
          print(network.proxy.bypass)

  if not isinstance(network, Unset) and not isinstance(network.allowed_domains, Unset):
      print(network.allowed_domains)
  ```
</CodeGroup>
