> ## Documentation Index
> Fetch the complete documentation index at: https://blaxel-cdrappier-devin-archive-external-storage-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Domain filtering

> Restrict which external domains, HTTP methods, and URL paths a Blaxel sandbox can reach using allowlists and denylists to prevent data exfiltration and unwanted outbound traffic.

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

Domain filtering lets you control which external domains a sandbox can reach. You can define an allowlist (only listed domains are reachable) or a denylist (all domains except listed ones are reachable). Domain filtering and proxy routing are **independent configurations** — you do not need to duplicate domains across both. A domain can appear in the allowlist without having a proxy routing rule, and vice versa.

<Warning>
  Domain filtering relies on the sandbox's tools and libraries respecting the standard proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`). Traffic from tools that ignore these variables will not be filtered. Routing-level enforcement is planned for a future release.
</Warning>

## Allowlist

Only the listed domains are reachable:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await SandboxInstance.create({
    name: "restricted-sandbox",
    image: "blaxel/base-image:latest",
    region: "us-was-1",
    network: {
      allowedDomains: ["api.stripe.com", "api.openai.com", "*.s3.amazonaws.com"],
      proxy: { routing: [] },
    },
  });
  ```

  ```python Python theme={null}
  await SandboxInstance.create({
      "name": "restricted-sandbox",
      "image": "blaxel/base-image:latest",
      "region": "us-was-1",
      "network": {
          "allowedDomains": ["api.stripe.com", "api.openai.com", "*.s3.amazonaws.com"],
          "proxy": {"routing": []},
      },
  })
  ```
</CodeGroup>

## Denylist

All domains except the listed ones are reachable:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await SandboxInstance.create({
    name: "denylist-sandbox",
    image: "blaxel/base-image:latest",
    region: "us-was-1",
    network: {
      forbiddenDomains: ["*.malware.com", "evil.example.org"],
      proxy: { routing: [] },
    },
  });
  ```

  ```python Python theme={null}
  await SandboxInstance.create({
      "name": "denylist-sandbox",
      "image": "blaxel/base-image:latest",
      "region": "us-was-1",
      "network": {
          "forbiddenDomains": ["*.malware.com", "evil.example.org"],
          "proxy": {"routing": []},
      },
  })
  ```
</CodeGroup>

<Note>
  When a request matches both lists, `forbiddenDomains` wins and the request is blocked. An empty `allowedDomains` allows everything that is not in `forbiddenDomains`.
</Note>

## Method and path patterns

Each entry in `allowedDomains` and `forbiddenDomains` is a pattern. A bare domain like `api.stripe.com` or `*.s3.amazonaws.com` is the simplest form. You can also scope a rule to specific HTTP methods and URL paths by adding method prefixes and a path suffix:

```text theme={null}
[!]METHOD:...:domain[/path]
```

* An optional leading `!` negates the method set.
* Zero or more HTTP methods, colon-separated, before the domain (case-insensitive).
* The domain (exact, `*.suffix`, or `*`).
* An optional `/path` prefix.

| Pattern                                  | Matches                                          |
| ---------------------------------------- | ------------------------------------------------ |
| `api.openai.com`                         | Any request to `api.openai.com`                  |
| `*.googleapis.com`                       | Any subdomain, but not the bare `googleapis.com` |
| `*`                                      | Any domain                                       |
| `GET:POST:storage.googleapis.com`        | Only GET and POST to that domain                 |
| `!POST:storage.googleapis.com`           | Every method except POST                         |
| `storage.googleapis.com/company-mercor*` | Paths starting with `/company-mercor`            |
| `GET:storage.googleapis.com/data/`       | GET requests under `/data/`                      |

### Methods

* No method in the pattern matches any method.
* One or more methods match only those methods (case-insensitive).
* A leading `!` negates the set: it matches every method except those listed. `!POST:domain` matches everything but POST, and `!GET:POST:domain` matches everything but GET and POST.

### Domains

* `*` matches every domain.
* `*.example.com` matches `sub.example.com` and `a.b.example.com`, but not the bare `example.com`.
* Any other value is an exact, case-insensitive match.
* A `:port` on the request is ignored when matching.

### Paths

* No path in the pattern applies the rule to every path on the domain.
* A path is matched as a start-anchored prefix. `*` matches any run of characters; every other character is literal.
  * `/data/` matches `/data/` and `/data/file.txt`, but not `/data`.
  * `/company-mercor*` matches `/company-mercor` and `/company-mercor-1/obj`, but not `/company-other`.
* The query string is ignored, so tokens in query parameters never affect the decision.
* The path is canonicalized before matching, so `.`, `..`, and duplicate slashes cannot slip a request past a prefix rule. A request to `/company-mercor/../other/obj` is evaluated as `/other/obj`.

<Tip>
  A pattern that does not fit this shape is treated as a plain domain equal to the literal string. It can never widen access beyond the domain it names, so a typo fails safe and matches nothing extra.
</Tip>

This example allows read-only access under one path prefix and blocks writes to a sensitive subpath:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await SandboxInstance.create({
    name: "scoped-egress",
    network: {
      allowedDomains: ["GET:storage.googleapis.com/company-mercor*"],
      forbiddenDomains: ["POST:storage.googleapis.com/company-mercor/secrets*"],
      proxy: { routing: [] },
    },
  });
  ```

  ```python Python theme={null}
  await SandboxInstance.create({
      "name": "scoped-egress",
      "network": {
          "allowedDomains": ["GET:storage.googleapis.com/company-mercor*"],
          "forbiddenDomains": ["POST:storage.googleapis.com/company-mercor/secrets*"],
          "proxy": {"routing": []},
      },
  })
  ```
</CodeGroup>

## Evaluation order

For each outbound request the proxy decides in this order:

1. If any `forbiddenDomains` pattern matches, the request is blocked.
2. Otherwise, if `allowedDomains` is non-empty and no pattern matches, the request is blocked.
3. Otherwise, the request is allowed.

Forbidden rules always take precedence over allow rules. An empty `allowedDomains` allows everything that is not forbidden. A blocked request receives an HTTP `403` response with a `Proxy-Error: firewall_blocked` header.

## Backward compatibility

Plain-domain entries behave exactly as before, so existing configurations keep working without changes. The method, path, and negation syntax is opt-in: put the richer strings into the same `allowedDomains` and `forbiddenDomains` arrays. There are no new fields.

## Firewall + proxy combined

Firewall rules and proxy routing compose naturally:

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

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

Only `api.stripe.com` and `api.openai.com` are reachable. The proxy injects credentials for Stripe requests; OpenAI requests go through unmodified.
