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 14 commits intomainfrom
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>
|
PR SummaryMedium Risk Overview Updates the OpenAPI spec and regenerated JS/Python clients accordingly, including a new Reviewed by Cursor Bugbot for commit 0653579. Bugbot is set up for automated code reviews on this repo. Configure here. |
Package ArtifactsBuilt from 17ec9fb. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.19.3-mishushakov-network-allowout-transform.0.tgzCLI ( npm install ./e2b-cli-2.10.1-mishushakov-network-allowout-transform.0.tgzPython SDK ( pip install ./e2b-2.20.2+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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f8ea7fa. Configure here.
| 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>


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