Skip to content
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
88 changes: 84 additions & 4 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.

6 changes: 6 additions & 0 deletions packages/js-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export type {
SandboxListOpts,
SandboxPaginator,
SandboxNetworkOpts,
SandboxNetworkInfo,
SandboxNetworkSelector,
SandboxNetworkSelectorContext,
SandboxNetworkRule,
SandboxNetworkRules,
SandboxNetworkTransform,
SandboxLifecycle,
SandboxInfoLifecycle,
SnapshotInfo,
Expand Down
155 changes: 149 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 { SandboxNotFoundError, TemplateError } from '../errors'
import { timeoutToSeconds } from '../utils'
import type { Volume } from '../volume'
Expand Down Expand Up @@ -33,23 +34,107 @@ 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[]>

/**
* 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 @@ -65,6 +150,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?: SandboxNetworkRules
allowPublicTraffic?: boolean
maskRequestHost?: string
}

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

/**
* Sandbox lifecycle configuration.
Expand Down Expand Up @@ -402,6 +500,50 @@ export interface SandboxMetrics {
diskTotal: number
}

function resolveNetworkSelector(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f8ea7fa. Configure here.

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 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: Object.fromEntries(rules) }
: {}),
...(network.allowPublicTraffic !== undefined
? { allowPublicTraffic: network.allowPublicTraffic }
: {}),
...(network.maskRequestHost !== undefined
? { maskRequestHost: network.maskRequestHost }
: {}),
}
}

function getLifecycle(
opts?: Pick<SandboxBetaCreateOpts, 'lifecycle' | 'autoPause'>
): SandboxLifecycle {
Expand Down Expand Up @@ -614,6 +756,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 @@ -792,7 +935,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 !== undefined ? { autoPause } : {}),
...(autoResumeEnabled !== undefined
? { autoResume: { enabled: autoResumeEnabled } }
Expand Down
Loading
Loading