feat(sdk): support structured network rules with per-host transforms#1286
feat(sdk): support structured network rules with per-host transforms#1286mishushakov wants to merge 20 commits into
Conversation
Extends SandboxNetworkConfig.allowOut/denyOut to accept objects of the
form { host, transform: [{ headers }] } alongside plain string entries.
Updates OpenAPI spec, regenerates JS + Python clients, surfaces new TS
types (SandboxNetworkRule, SandboxNetworkRuleTransform, SandboxNetworkEntry),
and adds a contract test that curls httpbin.org/headers and asserts an
injected Authorization-style header is reflected back.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 708c7b5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
PR SummaryMedium Risk Overview Extends Updates the OpenAPI spec and regenerated JS/Python clients to include the new network rule/transform schemas, adds Reviewed by Cursor Bugbot for commit 708c7b5. Bugbot is set up for automated code reviews on this repo. Configure here. |
Package ArtifactsBuilt from 9d859c9. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.20.2-mishushakov-network-allowout-transform.0.tgzCLI ( npm install ./e2b-cli-2.10.2-mishushakov-network-allowout-transform.0.tgzPython SDK ( pip install ./e2b-2.21.1+mishushakov.network.allowout.transform-py3-none-any.whl |
- Adds SandboxNetworkRule and SandboxNetworkRuleTransform TypedDicts to the Python SDK, widens SandboxNetworkOpts.allow_out to accept them, and exposes them from the top-level e2b package. - Mirrors the TS contract: deny_out stays as List[str], only allow_out gains the object form with optional per-host transforms. - Reverts denyOut in the OpenAPI spec to items: string only (allowOut keeps the oneOf form with SandboxNetworkRule) and regenerates the JS + Python clients to match. - Adds httpbin.org/headers transform tests for both async and sync Python sandbox tests, parallel to the TS case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Drops the redundant string-form duplicate of httpbin.org and the denyOut/deny_out scoping; the test now exercises just the structured rule with header transform. - Switches the Python tests to plain-dict literals (typed via a local SandboxNetworkOpts annotation) instead of TypedDict constructors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…onfig Replaces the TYPE_CHECKING / in-method imports of SandboxNetworkRule with a single top-level import. There is no circular-import risk here, so the openapi-python-client guard is unnecessary noise. Note: this file is generated by openapi-python-client and the guard will reappear on the next \`make codegen\` run unless we add a postprocess step. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There is no local SandboxNetworkConfig to collide with — the user-facing equivalent is SandboxNetworkOpts — so the ClientSandboxNetworkConfig rename was dead noise. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The generated from_dict pre-initialized allow_out to [] and then iterated over \`_allow_out or []\`, collapsing the absent-key case (UNSET, which is falsy) into an empty list. Downstream from_client_network_config could then set "allow_out": [] in the user-facing dict, which is semantically "deny all outbound" rather than "field not provided". Now we only build the list when the key is actually present, leaving allow_out as UNSET otherwise. The other oneOf-array fields are not affected because deny_out remains a plain list[str]. Note: this lives in generated code; \`make codegen\` will reintroduce the bug until openapi-python-client is patched or a postprocess step is added. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reverts the manual edits to the generated SandboxNetworkConfig (TYPE_CHECKING hoist + UNSET-preserving from_dict) since that file is owned by openapi-python-client and will be overwritten on the next codegen run. Addresses the same underlying bug — generated from_dict pre-inits allow_out to [] and then iterates with \`_allow_out or []\`, so an absent allowOut field deserializes to [] instead of UNSET — by treating empty allow_out as "not provided" inside the hand-written from_client_network_config wrapper. This keeps the public dict from gaining a misleading "allow_out": [] entry. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…t_network_config Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Separate the outbound network policy (allowOut/denyOut) from firewall
rules. Rules are registered under a top-level firewall map keyed by host
but do not grant egress on their own — hosts must still be referenced
via allowOut. Selectors accept either a static list or a callback that
receives { firewallHosts, allHosts } for composable policies.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move per-host transform rules from a top-level `firewall` field to
`network.rules`. The selector context now exposes `{ allTraffic, rules }`
where `allTraffic` is `'0.0.0.0/0'` and `rules` is a Map (Mapping in
Python) view of `network.rules`. SandboxFirewall* schemas renamed to
SandboxNetworkRule / SandboxNetworkTransform.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SandboxNetworkRules now accepts both a plain object and a Map. The helper normalizes either form to a Map for the selector context and serializes via Object.fromEntries for the wire body. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
| diskTotal: number | ||
| } | ||
|
|
||
| function resolveNetworkSelector( |
There was a problem hiding this comment.
JS SDK SandboxMetrics missing new memCache field
Medium Severity
The OpenAPI spec adds memCache as a required field on SandboxMetric (visible in the spec diff and in the generated schema.gen.ts at line 2321), and the Python generated client (sandbox_metric.py) was updated accordingly. However, the JS SDK's hand-written SandboxMetrics interface does not include memCache. Any metrics mapping code will silently drop this field, making it inaccessible to JS SDK consumers.
Reviewed by Cursor Bugbot for commit f8ea7fa. Configure here.
Network rules can now declare transforms as a callback that receives a typed context exposing literal placeholder strings (sandboxId, teamId, executionId, identity.jwt). The proxy resolves these per request at egress, so the SDK serializes the resolved object as-is and users get typed access without hardcoding template strings. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
An organization admin can view or raise the cap at claude.ai/admin-settings/claude-code. The cap resets at the start of the next billing period.
Once the cap resets or is raised, reopen this pull request to trigger a review.
Inject `ctx.sandboxId` as a header on the httpbin rule and verify the
reflected response matches the live `sandbox.sandbox_id`, proving the
proxy substituted `${e2b.sandboxId}` per request.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fbefba3. Configure here.
API responses can only contain plain JSON, so rules in the info shape must not allow the JS Map variant or callback transforms accepted by the input types. Introduce SandboxNetworkRuleInfo mirroring SandboxNetworkRule with transform fixed to the static SandboxNetworkTransform, and use it for SandboxNetworkInfo.rules in both SDKs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Thanks for working on this. I’m trying to understand the intended pattern for credential brokering and request logging. For our use case, we want connectors to integrate with third-party platforms like CRMs, Jira, GitHub, etc. Sandboxes need to make outbound API calls to these services, while the actual credentials should stay outside the sandbox. The credential to inject may depend on the actual sandbox, tenant, and user. Ideally, this would be an egress-time callback where we can access the original outbound request, inspect the URL / method / headers, inject the right token, and log or audit the request. From my reading, If the design is intentionally static, could E2B at least support updating the network config after sandbox creation, similar to Vercel’s Is that the expected workaround, or is there a better recommended pattern? |
|
@nauxliu thanks for the feedback! We will consider making it possible to update the networking configuration after the Sandbox creation - I will check in with the team! |
We host our own httpbin mirror at httpbin.e2b.team — switch the rule host and curl target away from httpbin.org so tests don't depend on a third-party service. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>


Summary
network.rulesmap keyed by host (or CIDR / IP) for per-host outbound request transforms (e.g. header injection). Registering a host inrulesdoes not grant egress on its own — it must still be referenced viaallowOut. JS accepts either a plain object or aMap.network.allowOut/network.denyOutto accept either a static list or a selector callback receiving{ allTraffic, rules }(Python:ctx.all_traffic,ctx.rules).allTrafficis'0.0.0.0/0';rulesis aMap(PythonMapping) view ofnetwork.rules, so policies can be composed against the registered rule hosts without duplicating them.transformon a rule accepts either a static object or a callback that receives a typedSandboxNetworkTransformContext. The context fields are literal placeholder strings (${e2b.sandboxId},${e2b.teamId},${e2b.executionId},${e2b.identity.jwt}) that the egress proxy resolves per request — so users get typed access without hardcoding template strings.SandboxNetworkRule,SandboxNetworkRules,SandboxNetworkTransform,SandboxNetworkTransformContext,SandboxNetworkSelector,SandboxNetworkSelectorContext,SandboxNetworkInfo.transform.headersrule forhttpbin.org, runcurl https://httpbin.org/headers, and assert the injected header is reflected back.Examples
Inject a static header on requests to a specific host (TypeScript)
Same thing in Python
Transform callback — inject the sandbox's identity JWT (resolved per request)
The proxy substitutes
${e2b.identity.jwt}(and the other placeholder fields) at egress time, so the same template is reused across all requests without ever materializing the secret in your code:network.rulesas aMap(TypeScript)Block all egress except an explicit allowlist
Static list (unchanged, still supported)
Notes
transform.headersand resolve the placeholder strings for the httpbin assertion to pass end-to-end — the new test locks the wire shape as a contract.ALL_TRAFFICis still exported but the selector form (({ allTraffic }) => [allTraffic]/lambda ctx: [ctx.all_traffic]) is the recommended way to express "everything".${e2b.*}placeholders) is sent on the wire. The proxy substitutes the placeholders per request — they are not resolved by the SDK.Test plan
network.test.ts/test_network.pycases still pass once backend auth is availablefirewall transform injects headerscases pass once egress proxy injects headers${e2b.sandboxId},${e2b.teamId},${e2b.executionId},${e2b.identity.jwt}placeholders intransform.headersvalues