Files
e2b-dev--e2b/spec
cursor[bot] 6824cdf313 feat(sdk): route sandbox egress through your own SOCKS5 proxy (BYOP) (#1688)
Drafts the SDK surface for [bring your own
proxy](https://e2b-docs-byop-egress-proxy.mintlify.site/network/byop):
`network.egressProxy` / `network["egress_proxy"]` on sandbox create, on
`updateNetwork` / `update_network`, and in what `getInfo` / `get_info`
reports back. Tunneling happens on the host after the allow and deny
lists are evaluated, so nothing runs inside the sandbox and code running
there can neither see the proxy nor route around it.

## The spec pin comes first

The pinned infra spec marked `egressProxy` `x-not-implemented: true`,
which Redocly's `filter-out` decorator drops from both generated clients
— so the field did not exist in `schema.gen.ts` or in the Python client
models, and no handwritten surface could reach it.
[infra@0716edb9e8](https://github.com/e2b-dev/infra/commit/0716edb9e840f110c5f87c186876c01e61553098)
removes the flag, so the first commit bumps `spec/infra-ref` and re-runs
codegen rather than hand-writing the wire types.

The pin picks up three other spec changes, and all of them are invisible
to the SDKs: `AdminTeamRunningSandboxCounts`, the dead
`NodeDetail.cachedBuilds` field, and `/admin/sandboxes/running-counts`
are admin-tagged, and the envd spec is byte-identical between the two
commits (verified by comparing the `packages/envd/spec` trees at both
refs). `make codegen` could not run here because the VM has no Docker,
so the spec was replaced with the byte-identical upstream file at the
new pin and the two REST generators were run natively with the pinned
`@redocly/cli` and `e2b-openapi-python-client`.

## Usage

Create a sandbox that tunnels its egress:

```ts
import { Sandbox } from 'e2b'

const sandbox = await Sandbox.create({
  network: {
    egressProxy: {
      address: 'proxy.example.com:1080',
      username: 'proxy-user',
      password: 'proxy-password',
    },
  },
})
```

```python
from e2b import Sandbox

sandbox = Sandbox.create(
    network={
        "egress_proxy": {
            "address": "proxy.example.com:1080",
            "username": "proxy-user",
            "password": "proxy-password",
        },
    },
)
```

It composes with the rest of the network configuration — here everything
except `api.example.com` is denied, and what is allowed goes through
your proxy:

```ts
await Sandbox.create({
  network: {
    allowOut: ['api.example.com'],
    denyOut: ({ allTraffic }) => [allTraffic],
    egressProxy: { address: 'proxy.example.com:1080' },
  },
})
```

```python
Sandbox.create(
    network={
        "allow_out": ["api.example.com"],
        "deny_out": lambda ctx: [ctx.all_traffic],
        "egress_proxy": {"address": "proxy.example.com:1080"},
    },
)
```

Set or replace it on a sandbox that is already running, with no restart.
The update replaces the whole configuration instead of merging into it,
so an update that leaves the proxy out stops tunneling:

```ts
await sandbox.updateNetwork({
  allowOut: ['api.example.com'],
  denyOut: ({ allTraffic }) => [allTraffic],
  egressProxy: { address: 'proxy.example.com:1080' },
})

// Stop tunneling: an update without egressProxy clears it
await sandbox.updateNetwork({})
```

```python
sandbox.update_network({
    "allow_out": ["api.example.com"],
    "deny_out": lambda ctx: [ctx.all_traffic],
    "egress_proxy": {"address": "proxy.example.com:1080"},
})

# Stop tunneling: an update without egress_proxy clears it
sandbox.update_network({})
```

Read the active proxy back:

```ts
const info = await sandbox.getInfo()
console.log(info.network?.egressProxy)
// { address: 'proxy.example.com:1080', username: 'proxy-user' }
```

```python
info = sandbox.get_info()
print(info.network["egress_proxy"])
# {'address': 'proxy.example.com:1080', 'username': 'proxy-user'}
```

## Design notes

- **`SandboxEgressProxyOpts` in, `SandboxEgressProxyInfo` out.** The API
never returns the password, so the result type does not have the field —
the same split as `SandboxNetworkRule` / `SandboxNetworkRuleInfo`.
`fromApiEgressProxy` / `_from_client_egress_proxy` map the generated
type at the boundary and drop a password even if a future API version
starts echoing one back, so the type cannot quietly become a lie.
- **The body is rebuilt from known fields**, as `buildIamBody` already
does, so stray keys on the caller's object never reach the wire and a
later mutation of it cannot alter an in-flight request.
- **No client-side validation.** Address form, port range, hostname
resolution, the internal-range rejection and the
password-without-username rule are all the server's — it is the only
side that can check them, and each already comes back as a readable API
error.
- **`null` never reaches a consumer.** The wire field is nullable; both
SDKs normalize it (absent key in Python, `undefined` in JS), and an
explicit `null` / `None` from an untyped caller is treated as "no proxy"
on the way in.
- Both types are exported from the flat entry points (`index.ts`,
`__all__`).

## Testing

Unit-level in both SDKs — msw in JS (12 tests), the shared builders in
Python (11 tests, covering sync and async since they share the
builders). Integration coverage is not included on purpose: tunneling
needs a SOCKS5 proxy reachable from E2B's infrastructure, which CI has
no way to stand up, and the feature is gated behind a private-beta team
flag.

`pnpm run format`, `pnpm run lint` and `pnpm run typecheck` are clean
repo-wide. The remaining test failures in this environment are all
`AuthenticationException` / missing `E2B_API_KEY` in pre-existing
integration suites; no credentials were available on the VM.

## Notes

- BYOP is available on E2B Cloud and in BYOC. A sandbox that names a
proxy on a deployment built from open source `e2b-dev/infra` is rejected
as unsupported by the orchestrator, which is why the field carried
`x-not-implemented` upstream for a while.
- No Linear MCP was available in this run, so no issue is linked.


<div><a
href="https://cursor.com/agents/bc-653eef78-87bb-5c9c-92d8-e573cd7ba5be?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/automations/8e94ee92-9b0d-11f1-ba66-0e7d0216e441"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/view-automation-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/view-automation-light.png"><img
alt="View Automation" width="141" height="28"
src="https://cursor.com/assets/images/view-automation-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mish Ushakov <mishushakov@users.noreply.github.com>
2026-08-19 17:54:11 +02:00
..
2025-10-20 14:38:18 -07:00

API specs

Most files in this directory are owned by other repositories and are synced here with Copybara (config in ../copy.bara.sky) — don't edit them by hand; change them in their source repository and re-sync:

  • openapi.yml, envd/envd.yaml, envd/filesystem/, envd/process/ are owned by the infra repository, pinned by infra-ref.
  • openapi-volumecontent.yml is owned by the private belt repository, pinned by belt-ref.

Fetches authenticate with a GitHub token when available (GITHUB_TOKEN, or being logged in with gh auth login); the public infra specs also fetch anonymously, while the volume-content spec needs a token with read access to belt. When a fetch fails, make codegen warns and falls back to the tracked copy.

make codegen re-fetches all of them at their pinned commits before generating the clients, and the generated-files CI check fails if the tracked copies don't match the pins. The files are stored byte-identical to upstream. To update the specs, point the pin at a newer commit and re-run make codegen. To fetch without regenerating:

pnpm fetch:api-spec     # openapi.yml
pnpm fetch:envd-spec    # envd spec
pnpm fetch:volume-spec  # openapi-volumecontent.yml
E2B_INFRA_REF=main pnpm fetch:api-spec     # try the latest without moving the pin
E2B_BELT_REF=main pnpm fetch:volume-spec

The remaining files (mcp-server.json, envd/buf-*.gen.yaml) are owned by this repository. The SDK generate pipelines filter openapi.yml down to the tags each SDK exposes with Redocly CLI (see ../redocly.yaml) before generating the clients.