Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
85f8a45
feat(sdk): support structured network rules with per-host transforms
mishushakov Apr 22, 2026
5229a6f
feat(python-sdk): support structured network rules and revert denyOut
mishushakov Apr 22, 2026
6905b09
test(network): trim transform tests to a single allowOut entry
mishushakov Apr 23, 2026
3600001
chore(python-sdk): hoist SandboxNetworkRule import in SandboxNetworkC…
mishushakov Apr 23, 2026
92f4334
chore(python-sdk): drop unnecessary SandboxNetworkConfig alias
mishushakov Apr 23, 2026
2f9ebbe
fix(python-sdk): preserve UNSET for SandboxNetworkConfig.allow_out
mishushakov Apr 23, 2026
9b9eb89
fix(python-sdk): handle empty allow_out from generated client
mishushakov Apr 23, 2026
3af584f
revert: keep isinstance(network.allow_out, Unset) check in from_clien…
mishushakov Apr 23, 2026
f867b9f
refactor(python-sdk): simplify allow_out conversion via to_dict()
mishushakov Apr 23, 2026
6ade12c
refactor(sdk): split network policy and firewall transforms
mishushakov Apr 24, 2026
99ab3e0
Merge branch 'main' into mishushakov/network-allowout-transform
mishushakov Apr 24, 2026
5a5f885
refactor(sdk): nest firewall rules under network.rules
mishushakov Apr 28, 2026
f8ea7fa
feat(js-sdk): accept Map for network.rules
mishushakov Apr 28, 2026
0653579
feat(sdk): add transform callback with placeholder context
mishushakov Apr 28, 2026
42eae98
updated tests
mishushakov May 13, 2026
730165a
added changeset
mishushakov May 13, 2026
fbefba3
test(network): assert sandboxId placeholder resolves at egress
mishushakov May 14, 2026
1663cb2
fix(sdk): narrow SandboxNetworkInfo.rules to static shape
mishushakov May 14, 2026
40c8f00
test(network): point transform tests at httpbin.e2b.team
mishushakov May 15, 2026
708c7b5
chore(python-sdk): ruff-format wrap curl test line
mishushakov May 15, 2026
8d32f29
test(network): disable transform placeholder resolution tests
mishushakov May 18, 2026
6b939d2
fix(sdk): freeze shared TRANSFORM_CONTEXT and mark fields readonly
mishushakov May 18, 2026
0dec3af
refactor(sdk): drop transform callback + placeholder context
mishushakov May 19, 2026
9d23573
Merge branch 'main' into mishushakov/network-allowout-transform
mishushakov May 26, 2026
71b1323
chore(spec): restore single-quote style in openapi.yml
mishushakov May 26, 2026
5985203
chore(spec): align openapi.yml style with main
mishushakov May 26, 2026
397debb
chore: narrow network rules spec diff
matthewlouisbrockman May 26, 2026
ba18b9a
chore: regenerate JS API schema
matthewlouisbrockman May 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/network-rules-transform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'e2b': minor
'@e2b/python-sdk': minor
---

Support structured network rules with per-host transforms
74 changes: 72 additions & 2 deletions packages/js-sdk/src/api/schema.gen.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions packages/js-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ export type {
SandboxListOpts,
SandboxPaginator,
SandboxNetworkOpts,
SandboxNetworkInfo,
SandboxNetworkSelector,
SandboxNetworkSelectorContext,
SandboxNetworkRule,
SandboxNetworkRuleInfo,
SandboxNetworkRules,
SandboxNetworkTransform,
SandboxLifecycle,
SandboxInfoLifecycle,
SnapshotInfo,
Expand Down
178 changes: 172 additions & 6 deletions packages/js-sdk/src/sandbox/sandboxApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DEFAULT_SANDBOX_TIMEOUT_MS,
} from '../connectionConfig'
import { compareVersions } from 'compare-versions'
import { ALL_TRAFFIC } from './network'
import {
InvalidArgumentError,
SandboxNotFoundError,
Expand Down Expand Up @@ -37,23 +38,118 @@ export type GitHubMcpServer = {
}
}

/**
* Transform applied to egress requests matching a {@link SandboxNetworkRule}.
*/
export type SandboxNetworkTransform = {
/**
* Headers to inject into the outbound request. Values override any headers
* already present on the request.
*/
headers?: Record<string, string>
}

/**
* Per-domain rule applied to egress requests.
*/
export type SandboxNetworkRule = {
/**
* Transform applied to requests matching this rule.
*/
transform?: SandboxNetworkTransform
}

/**
* Map of host (or CIDR / IP) to ordered list of rules applied to outbound
* requests for that host. Accepts either a plain object or a `Map`.
* Registering a host here does not allow egress on its own — the host must
* also appear in {@link SandboxNetworkOpts.allowOut}.
*/
export type SandboxNetworkRules =
| Record<string, SandboxNetworkRule[]>
| Map<string, SandboxNetworkRule[]>

/**
* Per-domain rule as returned by the sandbox info endpoint. Mirrors
* {@link SandboxNetworkRule} but with `transform` always materialized to the
* static {@link SandboxNetworkTransform} shape — no callback variant.
*/
export type SandboxNetworkRuleInfo = {
transform?: SandboxNetworkTransform
}

/**
* Context passed to {@link SandboxNetworkOpts.allowOut} and
* {@link SandboxNetworkOpts.denyOut} when they are defined as functions.
*/
export type SandboxNetworkSelectorContext = {
/** All traffic sentinel — equivalent to `'0.0.0.0/0'`. */
allTraffic: string
/** Rules registered in {@link SandboxNetworkOpts.rules}. */
rules: Map<string, SandboxNetworkRule[]>
}

/**
* Egress rule list, either a static array of CIDR blocks / IP addresses /
* hostnames, or a callback that receives `{ allTraffic, rules }` and returns
* the same.
*/
export type SandboxNetworkSelector =
| string[]
| ((ctx: SandboxNetworkSelectorContext) => string[])

export type SandboxNetworkOpts = {
/**
* Allow outbound traffic from the sandbox to the specified addresses.
* If `allowOut` is not specified, all outbound traffic is allowed.
*
* Accepts either a static array of CIDR blocks, IP addresses, or hostnames,
* or a callback that receives `{ allTraffic, rules }` and returns the same.
* `allTraffic` is `'0.0.0.0/0'`; `rules` is a `Map` view of
* {@link SandboxNetworkOpts.rules}.
*
* Examples:
* - To allow traffic to a specific addresses: `["1.1.1.1", "8.8.8.0/24"]`
* - Static list: `["1.1.1.1", "8.8.8.0/24"]`
* - Allow only rule-registered hosts:
* `({ rules }) => [...rules.keys()]`
*/
allowOut?: string[]
allowOut?: SandboxNetworkSelector

/**
* Deny outbound traffic from the sandbox to the specified addresses.
*
* Accepts the same shapes as {@link allowOut}.
*
* Examples:
* - To deny traffic to a specific addresses: `["1.1.1.1", "8.8.8.0/24"]`
* - Static list: `["1.1.1.1", "8.8.8.0/24"]`
* - Block all egress: `({ allTraffic }) => [allTraffic]`
*/
denyOut?: string[]
denyOut?: SandboxNetworkSelector

/**
* Per-domain transform rules applied to matching egress HTTP/HTTPS
* requests. Keys are domains (e.g. `"api.example.com"`); values are
* ordered lists of rules.
*
* Registering a host here does not allow egress on its own — the host must
* also appear in {@link allowOut}. Hosts registered here are exposed to the
* `allowOut`/`denyOut` callbacks via `rules`.
*
* @example
* ```ts
* await Sandbox.create({
* network: {
* allowOut: ({ rules }) => [...rules.keys()],
* rules: {
* 'api.openai.com': [
* { transform: { headers: { Authorization: `Bearer ${token}` } } },
* ],
* },
* },
* })
* ```
*/
rules?: SandboxNetworkRules

/**
* Specify if the sandbox URLs should be accessible only with authentication.
Expand All @@ -69,6 +165,19 @@ export type SandboxNetworkOpts = {
maskRequestHost?: string
}

/**
* Network configuration as returned by the sandbox info endpoint. Mirrors
* {@link SandboxNetworkOpts} but with `allowOut`/`denyOut` always materialized
* to plain string arrays.
*/
export type SandboxNetworkInfo = {
allowOut?: string[]
denyOut?: string[]
rules?: Record<string, SandboxNetworkRuleInfo[]>
allowPublicTraffic?: boolean
maskRequestHost?: string
}
Comment thread
cursor[bot] marked this conversation as resolved.

export type SandboxLifecycle = {
/**
* Action to take when sandbox timeout is reached.
Expand Down Expand Up @@ -360,7 +469,7 @@ export interface SandboxInfo {
/**
* Sandbox network configuration.
*/
network?: SandboxNetworkOpts
network?: SandboxNetworkInfo

/**
* Sandbox lifecycle configuration.
Expand Down Expand Up @@ -413,6 +522,62 @@ export interface SandboxMetrics {
diskTotal: number
}

function resolveNetworkSelector(
Comment thread
mishushakov marked this conversation as resolved.
selector: SandboxNetworkSelector | undefined,
rules: Map<string, SandboxNetworkRule[]>
): string[] | undefined {
if (selector === undefined) {
return undefined
}

if (typeof selector === 'function') {
return selector({ allTraffic: ALL_TRAFFIC, rules })
}

return selector
}

function resolveRulesForBody(
rules: Map<string, SandboxNetworkRule[]>
): Record<string, { transform?: SandboxNetworkTransform }[]> {
const out: Record<string, { transform?: SandboxNetworkTransform }[]> = {}
for (const [host, hostRules] of rules) {
out[host] = hostRules.map((rule) =>
rule.transform === undefined ? {} : { transform: rule.transform }
)
}
return out
}

function buildNetworkBody(
network: SandboxNetworkOpts | undefined
): components['schemas']['SandboxNetworkConfig'] | undefined {
if (!network) {
return undefined
}

const rules =
network.rules instanceof Map
? network.rules
: new Map(Object.entries(network.rules ?? {}))
const allowOut = resolveNetworkSelector(network.allowOut, rules)
const denyOut = resolveNetworkSelector(network.denyOut, rules)

return {
...(allowOut !== undefined ? { allowOut } : {}),
...(denyOut !== undefined ? { denyOut } : {}),
...(network.rules !== undefined
? { rules: resolveRulesForBody(rules) }
: {}),
...(network.allowPublicTraffic !== undefined
? { allowPublicTraffic: network.allowPublicTraffic }
: {}),
...(network.maskRequestHost !== undefined
? { maskRequestHost: network.maskRequestHost }
: {}),
}
}

export class SandboxApi {
protected constructor() {}

Expand Down Expand Up @@ -605,6 +770,7 @@ export class SandboxApi {
? {
allowOut: res.data.network.allowOut,
denyOut: res.data.network.denyOut,
rules: res.data.network.rules ?? undefined,
allowPublicTraffic: res.data.network.allowPublicTraffic,
maskRequestHost: res.data.network.maskRequestHost,
}
Expand Down Expand Up @@ -786,7 +952,7 @@ export class SandboxApi {
timeout: timeoutToSeconds(timeoutMs),
secure: opts?.secure ?? true,
allow_internet_access: opts?.allowInternetAccess ?? true,
network: opts?.network,
network: buildNetworkBody(opts?.network),
autoPause: onTimeout === 'pause',
autoResume: { enabled: autoResume },
}
Expand Down
Loading
Loading