From d2542613a0a0a463b8e0d34e82354052d262e672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Tue, 14 Apr 2026 11:49:48 +0200 Subject: [PATCH 1/2] python-sdk: Add support for proxy environment variables like HTTPS_PROXY This adds support for the following proxy environment variables: - https_proxy - HTTPS_PROXY - http_proxy - HTTP_PROXY - all_proxy - ALL_PROXY Lowercase version takes precedence as that seems to be standard across cURL, Python, and wget -- yes, I agree -- it is nuts! For a reference on the used environment variables and their precedence see: https://superuser.com/a/1690537 --- .../e2b/api/client_async/__init__.py | 4 ++-- .../e2b/api/client_sync/__init__.py | 4 ++-- packages/python-sdk/e2b/connection_config.py | 19 +++++++++++++++++++ .../e2b/volume/client_async/__init__.py | 3 ++- .../e2b/volume/client_sync/__init__.py | 3 ++- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/packages/python-sdk/e2b/api/client_async/__init__.py b/packages/python-sdk/e2b/api/client_async/__init__.py index 1e97a688b8..87ebe5d8ff 100644 --- a/packages/python-sdk/e2b/api/client_async/__init__.py +++ b/packages/python-sdk/e2b/api/client_async/__init__.py @@ -4,7 +4,7 @@ from typing import Dict -from e2b.connection_config import ConnectionConfig +from e2b.connection_config import ConnectionConfig, _resolve_proxy from e2b.api import limits, AsyncApiClient @@ -45,7 +45,7 @@ def get_transport(config: ConnectionConfig) -> AsyncTransportWithLogger: transport = AsyncTransportWithLogger( limits=limits, - proxy=config.proxy, + proxy=_resolve_proxy(config.proxy), ) AsyncTransportWithLogger._instances[loop_id] = transport return transport diff --git a/packages/python-sdk/e2b/api/client_sync/__init__.py b/packages/python-sdk/e2b/api/client_sync/__init__.py index f3d62ca729..20fa859191 100644 --- a/packages/python-sdk/e2b/api/client_sync/__init__.py +++ b/packages/python-sdk/e2b/api/client_sync/__init__.py @@ -4,7 +4,7 @@ import logging from e2b.api import ApiClient, limits -from e2b.connection_config import ConnectionConfig +from e2b.connection_config import ConnectionConfig, _resolve_proxy logger = logging.getLogger(__name__) @@ -44,7 +44,7 @@ def get_transport(config: ConnectionConfig) -> TransportWithLogger: transport = TransportWithLogger( limits=limits, - proxy=config.proxy, + proxy=_resolve_proxy(config.proxy), ) TransportWithLogger.singleton = transport return transport diff --git a/packages/python-sdk/e2b/connection_config.py b/packages/python-sdk/e2b/connection_config.py index 987d5c9e37..7d0c6e4399 100644 --- a/packages/python-sdk/e2b/connection_config.py +++ b/packages/python-sdk/e2b/connection_config.py @@ -208,6 +208,25 @@ def sandbox_headers(self): } +def _resolve_proxy(proxy: Optional[ProxyTypes]) -> Optional[ProxyTypes]: + """ + Resolve the proxy to use for a request. + If an explicit proxy is provided, use it. Otherwise, fall back to + standard environment variables (HTTPS_PROXY, HTTP_PROXY, ALL_PROXY). + """ + if proxy is not None: + return proxy + + return ( + os.environ.get("https_proxy") + or os.environ.get("HTTPS_PROXY") + or os.environ.get("http_proxy") + or os.environ.get("HTTP_PROXY") + or os.environ.get("all_proxy") + or os.environ.get("ALL_PROXY") + ) + + Username = str """ User used for the operation in the sandbox. diff --git a/packages/python-sdk/e2b/volume/client_async/__init__.py b/packages/python-sdk/e2b/volume/client_async/__init__.py index f1e44a8a33..de69f46619 100644 --- a/packages/python-sdk/e2b/volume/client_async/__init__.py +++ b/packages/python-sdk/e2b/volume/client_async/__init__.py @@ -8,6 +8,7 @@ from e2b.api.metadata import default_headers from e2b.exceptions import AuthenticationException from e2b.volume.client.client import AuthenticatedClient as AsyncVolumeApiClient +from e2b.connection_config import _resolve_proxy from e2b.volume.connection_config import VolumeConnectionConfig logger = logging.getLogger(__name__) @@ -63,7 +64,7 @@ def get_transport(config: VolumeConnectionConfig) -> AsyncTransportWithLogger: transport = AsyncTransportWithLogger( limits=limits, - proxy=config.proxy, + proxy=_resolve_proxy(config.proxy), ) AsyncTransportWithLogger.singleton = transport return transport diff --git a/packages/python-sdk/e2b/volume/client_sync/__init__.py b/packages/python-sdk/e2b/volume/client_sync/__init__.py index 5afdcfef2d..d7ccd280fa 100644 --- a/packages/python-sdk/e2b/volume/client_sync/__init__.py +++ b/packages/python-sdk/e2b/volume/client_sync/__init__.py @@ -8,6 +8,7 @@ from e2b.api.metadata import default_headers from e2b.exceptions import AuthenticationException from e2b.volume.client.client import AuthenticatedClient as VolumeApiClient +from e2b.connection_config import _resolve_proxy from e2b.volume.connection_config import VolumeConnectionConfig logger = logging.getLogger(__name__) @@ -63,7 +64,7 @@ def get_transport(config: VolumeConnectionConfig) -> TransportWithLogger: transport = TransportWithLogger( limits=limits, - proxy=config.proxy, + proxy=_resolve_proxy(config.proxy), ) TransportWithLogger.singleton = transport return transport From 0019f614141705169a592546bdd6a87d1cda0cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Tue, 14 Apr 2026 13:13:41 +0200 Subject: [PATCH 2/2] js-sdk: Add support for proxy environment variables like HTTPS_PROXY This adds support for the following proxy environment variables: - https_proxy - HTTPS_PROXY - http_proxy - HTTP_PROXY - all_proxy - ALL_PROXY Lowercase version takes precedence as that seems to be standard across cURL, Python, and wget -- yes, I agree -- it is nuts! Adds the dependency `undici` which allows `fetch` requests to go through a proxy. `undici` is owned by the Node.js organization (https://github.com/nodejs/undici). For a reference on the used environment variables and their precedence see: https://superuser.com/a/1690537 --- packages/js-sdk/package.json | 3 +- packages/js-sdk/src/api/index.ts | 2 ++ packages/js-sdk/src/envd/api.ts | 3 +- packages/js-sdk/src/proxy.ts | 41 ++++++++++++++++++++++++++++ packages/js-sdk/src/sandbox/index.ts | 4 ++- packages/js-sdk/src/volume/client.ts | 2 ++ pnpm-lock.yaml | 9 ++++++ 7 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 packages/js-sdk/src/proxy.ts diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index e6f8d2c06d..16de03d3ec 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -97,7 +97,8 @@ "glob": "^11.1.0", "openapi-fetch": "^0.14.1", "platform": "^1.3.6", - "tar": "^7.5.11" + "tar": "^7.5.11", + "undici": "^7.0.0" }, "engines": { "node": ">=20" diff --git a/packages/js-sdk/src/api/index.ts b/packages/js-sdk/src/api/index.ts index 109d69bb3f..6507ed4bda 100644 --- a/packages/js-sdk/src/api/index.ts +++ b/packages/js-sdk/src/api/index.ts @@ -5,6 +5,7 @@ import { defaultHeaders } from './metadata' import { ConnectionConfig } from '../connectionConfig' import { AuthenticationError, RateLimitError, SandboxError } from '../errors' import { createApiLogger } from '../logs' +import { getProxyFetch } from '../proxy' export function handleApiError( response: FetchResponse, @@ -74,6 +75,7 @@ class ApiClient { this.api = createClient({ baseUrl: config.apiUrl, + fetch: getProxyFetch(), // In HTTP 1.1, all connections are considered persistent unless declared otherwise // keepalive: true, headers: { diff --git a/packages/js-sdk/src/envd/api.ts b/packages/js-sdk/src/envd/api.ts index b0bd1704c8..c9cb33bb94 100644 --- a/packages/js-sdk/src/envd/api.ts +++ b/packages/js-sdk/src/envd/api.ts @@ -3,6 +3,7 @@ import createClient from 'openapi-fetch' import type { components, paths } from './schema.gen' import { ConnectionConfig } from '../connectionConfig' import { createApiLogger } from '../logs' +import { getProxyFetch } from '../proxy' import { SandboxError, InvalidArgumentError, @@ -130,7 +131,7 @@ class EnvdApiClient { ) { this.api = createClient({ baseUrl: config.apiUrl, - fetch: config?.fetch, + fetch: config?.fetch ?? getProxyFetch(), headers: config?.headers, // In HTTP 1.1, all connections are considered persistent unless declared otherwise // keepalive: true, diff --git a/packages/js-sdk/src/proxy.ts b/packages/js-sdk/src/proxy.ts new file mode 100644 index 0000000000..fd08cfb206 --- /dev/null +++ b/packages/js-sdk/src/proxy.ts @@ -0,0 +1,41 @@ +import { ProxyAgent } from 'undici' + +import { getEnvVar } from './api/metadata' +import { runtime } from './utils' + +function getProxyUrl(): string | undefined { + return ( + getEnvVar('https_proxy') || + getEnvVar('HTTPS_PROXY') || + getEnvVar('http_proxy') || + getEnvVar('HTTP_PROXY') || + getEnvVar('all_proxy') || + getEnvVar('ALL_PROXY') || + undefined + ) +} + +let cachedProxyFetch: typeof fetch | null = null +let proxyResolved = false + +/** + * Returns a proxy-aware fetch function if HTTPS_PROXY/HTTP_PROXY/ALL_PROXY + * env vars are set. In browser environments, returns undefined (browsers + * handle proxies natively via OS settings). + * + * Uses undici's ProxyAgent. + */ +export function getProxyFetch(): typeof fetch | undefined { + if (proxyResolved) return cachedProxyFetch ?? undefined + proxyResolved = true + + if (runtime === 'browser') return undefined + + const proxyUrl = getProxyUrl() + if (!proxyUrl) return undefined + + const agent = new ProxyAgent(proxyUrl) + cachedProxyFetch = ((input: any, init?: any) => + fetch(input, { ...init, dispatcher: agent })) as typeof fetch + return cachedProxyFetch +} diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 7384dc7af2..a2e71c6a4e 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -9,6 +9,7 @@ import { } from '../connectionConfig' import { EnvdApiClient, handleEnvdApiError } from '../envd/api' import { createRpcLogger } from '../logs' +import { getProxyFetch } from '../proxy' import { Commands, Pty } from './commands' import { Filesystem } from './filesystem' import { Git } from './git' @@ -150,6 +151,7 @@ export class Sandbox extends SandboxApi { 'E2b-Sandbox-Port': this.envdPort.toString(), } + const proxyFetch = getProxyFetch() ?? fetch const rpcTransport = createConnectTransport({ baseUrl: this.envdApiUrl, useBinaryFormat: false, @@ -178,7 +180,7 @@ export class Sandbox extends SandboxApi { redirect: 'follow', } - return fetch(url, options) + return proxyFetch(url, options) }, }) diff --git a/packages/js-sdk/src/volume/client.ts b/packages/js-sdk/src/volume/client.ts index 1cf71a8219..5b774c9f6a 100644 --- a/packages/js-sdk/src/volume/client.ts +++ b/packages/js-sdk/src/volume/client.ts @@ -3,6 +3,7 @@ import createClient from 'openapi-fetch' import type { components, paths } from './schema.gen' import { defaultHeaders, getEnvVar } from '../api/metadata' import { createApiLogger, Logger } from '../logs' +import { getProxyFetch } from '../proxy' import type { Volume } from './index' const FILE_TIMEOUT_MS = 3_600_000 // 1 hour @@ -99,6 +100,7 @@ class VolumeApiClient { constructor(config: VolumeConnectionConfig) { this.api = createClient({ baseUrl: config.apiUrl, + fetch: getProxyFetch(), headers: { ...defaultHeaders, ...(config.token && { Authorization: `Bearer ${config.token}` }), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 265d55fadd..ae3bd8ff60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -164,6 +164,9 @@ importers: tar: specifier: ^7.5.11 version: 7.5.12 + undici: + specifier: ^7.0.0 + version: 7.25.0 devDependencies: '@testing-library/react': specifier: ^16.2.0 @@ -4049,6 +4052,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + unique-filename@2.0.1: resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -8569,6 +8576,8 @@ snapshots: undici-types@6.21.0: {} + undici@7.25.0: {} + unique-filename@2.0.1: dependencies: unique-slug: 3.0.0