@claxedo/sandbox-manager 0.6.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +60 -15
  2. package/dist/checkpoint-manager.d.ts +47 -0
  3. package/dist/checkpoint-manager.mjs +116 -0
  4. package/dist/daytona-allow-list.d.ts +11 -0
  5. package/dist/daytona-allow-list.mjs +5 -1
  6. package/dist/driver-catalog.d.ts +5 -52
  7. package/dist/driver-catalog.mjs +263 -121
  8. package/dist/drivers/box.mjs +309 -39
  9. package/dist/drivers/cloudflare.d.ts +18 -7
  10. package/dist/drivers/cloudflare.mjs +423 -34
  11. package/dist/drivers/daytona.d.ts +82 -13
  12. package/dist/drivers/daytona.mjs +495 -45
  13. package/dist/drivers/docker.mjs +284 -38
  14. package/dist/drivers/exe.d.ts +26 -0
  15. package/dist/drivers/exe.mjs +492 -0
  16. package/dist/drivers/fetch-bridge.mjs +29 -5
  17. package/dist/drivers/modal.d.ts +2 -5
  18. package/dist/drivers/modal.mjs +296 -46
  19. package/dist/drivers/transient-error.d.ts +11 -0
  20. package/dist/drivers/vercel.d.ts +27 -11
  21. package/dist/drivers/vercel.mjs +339 -59
  22. package/dist/hosted-network-policy.d.ts +87 -0
  23. package/dist/hosted-network-policy.mjs +61 -0
  24. package/dist/image-name.mjs +34 -0
  25. package/dist/image.d.ts +18 -3
  26. package/dist/image.mjs +47 -32
  27. package/dist/index.d.ts +281 -17
  28. package/dist/index.mjs +393 -26
  29. package/dist/json.d.ts +13 -0
  30. package/dist/lease-policy.d.ts +6 -3
  31. package/dist/lease-policy.mjs +213 -54
  32. package/dist/lease-types.d.ts +17 -3
  33. package/dist/runtime-env.d.ts +12 -1
  34. package/dist/runtime-env.mjs +14 -0
  35. package/dist/runtime-version.mjs +1 -9
  36. package/dist/service-url-exposure.d.ts +1 -2
  37. package/dist/service-url-exposure.mjs +1 -17
  38. package/dist/stores/memory.mjs +39 -4
  39. package/package.json +67 -51
  40. package/dist/drivers/cloudflare-egress.d.ts +0 -40
  41. package/dist/drivers/cloudflare-egress.mjs +0 -113
@@ -1,7 +1,17 @@
1
- // src/drivers/modal.ts
2
- import { ModalClient, Probe } from "modal";
3
-
4
1
  // src/runtime-env.ts
2
+ function workspaceRuntimeBootEnv(input) {
3
+ const env = {
4
+ ...workspaceRuntimeTargetEnv(input),
5
+ ...workspaceRuntimeSourceEnv(input),
6
+ ...input.env
7
+ };
8
+ if (input.runner) env.WORKSPACE_RUNTIME_RUNNER = input.runner;
9
+ if (input.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = input.controlEnv.relayJwksUrl;
10
+ if (input.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = input.controlEnv.relayVerifyPem;
11
+ if (input.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = input.controlEnv.managementJwksUrl;
12
+ if (input.controlEnv?.sessionAuthorityUrl) env.WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL = input.controlEnv.sessionAuthorityUrl;
13
+ return env;
14
+ }
5
15
  function workspaceRuntimeTargetEnv(input) {
6
16
  return {
7
17
  WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
@@ -28,21 +38,10 @@ function shell(input) {
28
38
  // src/constants.ts
29
39
  var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
30
40
 
31
- // src/image.ts
32
- import { Image, DaytonaNotFoundError } from "@daytona/sdk";
33
-
34
41
  // src/runtime-version.ts
35
42
  var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
36
- var versionCache;
37
43
  function workspaceRuntimeVersion() {
38
- if (versionCache) return versionCache;
39
- const envVersion = process.env.WORKSPACE_RUNTIME_VERSION;
40
- if (envVersion) {
41
- versionCache = envVersion;
42
- return versionCache;
43
- }
44
- versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
45
- return versionCache;
44
+ return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
46
45
  }
47
46
 
48
47
  // src/image-name.ts
@@ -66,11 +65,253 @@ function defaultSnapshotName(version = workspaceRuntimeVersion(), buildId, env =
66
65
  return `claxedo-workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
67
66
  }
68
67
 
68
+ // ../claxedo-helpers/dist/guards.mjs
69
+ function isRecord(value) {
70
+ return value !== null && typeof value === "object" && !Array.isArray(value);
71
+ }
72
+ function asRecord(value) {
73
+ return isRecord(value) ? value : void 0;
74
+ }
75
+
76
+ // src/json.ts
77
+ function num(input) {
78
+ return typeof input === "number" ? input : void 0;
79
+ }
80
+
69
81
  // src/image.ts
70
82
  var SANDBOX_IMAGE_REPOSITORY = sandboxImageRepository();
71
83
  var SANDBOX_IMAGE = process.env.CLAXEDO_SANDBOX_IMAGE || defaultSandboxImage();
72
84
  var SNAPSHOT_NAME = process.env.CLAXEDO_SNAPSHOT_NAME || defaultSnapshotName();
73
85
 
86
+ // src/hosted-network-policy.ts
87
+ var SANDBOX_MODEL_PROVIDER_HOSTS = [
88
+ "api.anthropic.com",
89
+ "api.openai.com",
90
+ "generativelanguage.googleapis.com",
91
+ "openrouter.ai",
92
+ // Model catalog + engine config, fetched on runner startup.
93
+ "models.dev",
94
+ "opencode.ai"
95
+ ];
96
+ var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
97
+ "registry.npmjs.org",
98
+ "nodejs.org",
99
+ "pypi.org",
100
+ "files.pythonhosted.org",
101
+ "crates.io",
102
+ "static.crates.io",
103
+ "index.crates.io",
104
+ "proxy.golang.org",
105
+ "sum.golang.org"
106
+ ];
107
+ var SANDBOX_BASELINE_EGRESS_HOSTS = [
108
+ ...SANDBOX_MODEL_PROVIDER_HOSTS,
109
+ ...SANDBOX_PACKAGE_REGISTRY_HOSTS
110
+ ];
111
+
112
+ // ../sandbox-contract/dist/index.mjs
113
+ var sandboxDriverCredentialFields = {
114
+ exe: [{ key: "api_token", label: "API Token", secret: true }],
115
+ daytona: [{ key: "api_key", label: "API Key", secret: true }],
116
+ modal: [
117
+ { key: "token_id", label: "Token ID", secret: true },
118
+ { key: "token_secret", label: "Token Secret", secret: true }
119
+ ],
120
+ vercel: [
121
+ { key: "access_token", label: "Access Token", secret: true },
122
+ { key: "team_id", label: "Team ID" },
123
+ { key: "project_id", label: "Project ID" }
124
+ ],
125
+ cloudflare: [
126
+ { key: "api_token", label: "API Token", secret: true },
127
+ { key: "worker_url", label: "Worker URL" }
128
+ ],
129
+ box: [{ key: "api_key", label: "API Key", secret: true }],
130
+ docker: [{ key: "image", label: "Image" }]
131
+ };
132
+ var sandboxDriverLabels = {
133
+ exe: "exe.dev",
134
+ daytona: "Daytona",
135
+ modal: "Modal",
136
+ vercel: "Vercel",
137
+ cloudflare: "Cloudflare",
138
+ box: "Box",
139
+ docker: "Docker"
140
+ };
141
+
142
+ // src/driver-catalog.ts
143
+ var sandboxDriverCatalog = {
144
+ exe: {
145
+ id: "exe",
146
+ label: sandboxDriverLabels.exe,
147
+ credentialFields: sandboxDriverCredentialFields.exe,
148
+ metadata: {
149
+ driverRunsIn: ["worker", "node"],
150
+ hostStopBehavior: "not-supported",
151
+ hostResumeBehavior: "same-host",
152
+ targetAccess: "relay",
153
+ secretBrokering: "none",
154
+ egressControl: "none",
155
+ persistence: {
156
+ resume: "same-sandbox",
157
+ capture: "none",
158
+ clone: true,
159
+ captureSource: "not-applicable",
160
+ retention: "provider-managed",
161
+ restoreMount: "same-resource"
162
+ }
163
+ }
164
+ },
165
+ daytona: {
166
+ id: "daytona",
167
+ label: sandboxDriverLabels.daytona,
168
+ credentialFields: sandboxDriverCredentialFields.daytona,
169
+ metadata: {
170
+ driverRunsIn: ["worker", "node"],
171
+ hostStopBehavior: "suspends-host",
172
+ hostResumeBehavior: "same-host",
173
+ targetAccess: "relay",
174
+ secretBrokering: "native",
175
+ // The driver translates names to domainAllowList, or addresses to networkAllowList.
176
+ egressControl: "hosts-and-cidrs",
177
+ persistence: {
178
+ resume: "same-sandbox",
179
+ capture: "filesystem",
180
+ clone: false,
181
+ captureSource: "preserved",
182
+ retention: "provider-managed",
183
+ restoreMount: "new-resource"
184
+ }
185
+ }
186
+ },
187
+ modal: {
188
+ id: "modal",
189
+ label: sandboxDriverLabels.modal,
190
+ credentialFields: sandboxDriverCredentialFields.modal,
191
+ metadata: {
192
+ driverRunsIn: ["node"],
193
+ hostStopBehavior: "terminates-host",
194
+ hostResumeBehavior: "replacement-host",
195
+ targetAccess: "relay",
196
+ secretBrokering: "none",
197
+ // The driver does not install Modal's domain allowlist or alpha sidecar.
198
+ egressControl: "none",
199
+ persistence: {
200
+ resume: "replacement-restore",
201
+ capture: "filesystem",
202
+ clone: false,
203
+ captureSource: "preserved",
204
+ retention: "provider-managed",
205
+ restoreMount: "new-resource"
206
+ }
207
+ }
208
+ },
209
+ vercel: {
210
+ id: "vercel",
211
+ label: sandboxDriverLabels.vercel,
212
+ credentialFields: sandboxDriverCredentialFields.vercel,
213
+ metadata: {
214
+ driverRunsIn: ["node"],
215
+ hostStopBehavior: "terminates-host",
216
+ hostResumeBehavior: "replacement-host",
217
+ targetAccess: "relay",
218
+ secretBrokering: "native",
219
+ // The driver translates hosts only; the SDK's subnet rules are not wired to net.cidrs.
220
+ egressControl: "hosts",
221
+ persistence: {
222
+ resume: "replacement-restore",
223
+ capture: "filesystem",
224
+ clone: false,
225
+ captureSource: "stopped",
226
+ retention: "explicit",
227
+ restoreMount: "new-resource"
228
+ }
229
+ }
230
+ },
231
+ cloudflare: {
232
+ id: "cloudflare",
233
+ label: sandboxDriverLabels.cloudflare,
234
+ credentialFields: sandboxDriverCredentialFields.cloudflare,
235
+ metadata: {
236
+ driverRunsIn: ["worker"],
237
+ hostStopBehavior: "not-supported",
238
+ hostResumeBehavior: "same-host",
239
+ targetAccess: "relay",
240
+ secretBrokering: "native",
241
+ // Credential handlers do not restrict unrelated destinations.
242
+ egressControl: "none",
243
+ persistence: {
244
+ resume: "replacement-restore",
245
+ capture: "directories",
246
+ clone: false,
247
+ captureSource: "preserved",
248
+ retention: "provider-managed",
249
+ restoreMount: "copy-on-write"
250
+ }
251
+ }
252
+ },
253
+ box: {
254
+ id: "box",
255
+ label: sandboxDriverLabels.box,
256
+ credentialFields: sandboxDriverCredentialFields.box,
257
+ metadata: {
258
+ driverRunsIn: ["node"],
259
+ hostStopBehavior: "suspends-host",
260
+ hostResumeBehavior: "same-host",
261
+ targetAccess: "relay",
262
+ secretBrokering: "none",
263
+ egressControl: "none",
264
+ persistence: {
265
+ resume: "same-sandbox",
266
+ capture: "none",
267
+ clone: false,
268
+ captureSource: "not-applicable",
269
+ retention: "provider-managed",
270
+ restoreMount: "same-resource"
271
+ }
272
+ }
273
+ },
274
+ docker: {
275
+ id: "docker",
276
+ label: sandboxDriverLabels.docker,
277
+ credentialFields: sandboxDriverCredentialFields.docker,
278
+ metadata: {
279
+ driverRunsIn: ["local"],
280
+ hostStopBehavior: "terminates-host",
281
+ hostResumeBehavior: "same-host",
282
+ targetAccess: "loopback",
283
+ secretBrokering: "none",
284
+ egressControl: "none",
285
+ persistence: {
286
+ resume: "replacement-restore",
287
+ capture: "same-resource",
288
+ clone: false,
289
+ captureSource: "preserved",
290
+ retention: "provider-managed",
291
+ restoreMount: "same-resource"
292
+ }
293
+ }
294
+ }
295
+ };
296
+
297
+ // src/drivers/transient-error.ts
298
+ function textOf(input) {
299
+ return typeof input === "string" ? input : "";
300
+ }
301
+ function driverErrorSignals(err) {
302
+ const shaped = isRecord(err) ? err : {};
303
+ const nested = isRecord(shaped.response) ? shaped.response : {};
304
+ return {
305
+ status: num(nested.status) ?? num(shaped.status),
306
+ text: `${textOf(shaped.code)} ${textOf(shaped.name)} ${textOf(shaped.message)}`.toLowerCase()
307
+ };
308
+ }
309
+ function isTransientDriverError(err, markers) {
310
+ const { status, text: text2 } = driverErrorSignals(err);
311
+ if (status !== void 0 && status >= 500) return true;
312
+ return markers.some((marker) => text2.includes(marker));
313
+ }
314
+
74
315
  // src/drivers/modal.ts
75
316
  var DEFAULT_APP_NAME = "claxedo";
76
317
  var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
@@ -78,21 +319,28 @@ var DEFAULT_WORKSPACE_DIR = "/workspace";
78
319
  var DEFAULT_TIMEOUT_MS = 30 * 60 * 1e3;
79
320
  var DEFAULT_TUNNEL_TIMEOUT_MS = 3e4;
80
321
  var DEFAULT_OPERATION_TIMEOUT_MS = 6e4;
322
+ function isModalClient(value) {
323
+ const row = asRecord(value);
324
+ return typeof asRecord(row?.apps)?.fromName === "function" && typeof asRecord(row?.images)?.fromRegistry === "function" && typeof asRecord(row?.sandboxes)?.create === "function";
325
+ }
81
326
  function nameFor(workspaceId) {
82
327
  return `claxedo-${workspaceId}`;
83
328
  }
329
+ var TRANSIENT_MARKERS = ["timeout", "unavailable", "deadline"];
84
330
  function transientDriverError(err) {
85
- const shaped = err;
86
- const status = shaped.response?.status ?? shaped.status;
87
- if (typeof status === "number" && status >= 500) return true;
88
- const text = `${shaped.code ?? ""} ${shaped.name ?? ""} ${shaped.message ?? ""}`.toLowerCase();
89
- return text.includes("timeout") || text.includes("unavailable") || text.includes("deadline");
331
+ return isTransientDriverError(err, TRANSIENT_MARKERS);
90
332
  }
91
333
  function createModalSandboxDriver(options) {
92
- const client = options.client ?? new ModalClient({
93
- tokenId: options.tokenId,
94
- tokenSecret: options.tokenSecret
95
- });
334
+ let defaultClient;
335
+ function resolveClient() {
336
+ if (options.client) return options.client;
337
+ defaultClient ??= import("modal").then((sdk) => {
338
+ const client = new sdk.ModalClient({ tokenId: options.tokenId, tokenSecret: options.tokenSecret });
339
+ if (!isModalClient(client)) throw new Error("modal SDK does not expose the client surface this driver uses");
340
+ return client;
341
+ });
342
+ return defaultClient;
343
+ }
96
344
  const appName = options.appName ?? DEFAULT_APP_NAME;
97
345
  const runtimePort = options.runtimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT;
98
346
  const runtimeCommand = options.runtimeCommand ?? DEFAULT_RUNTIME_COMMAND;
@@ -100,29 +348,28 @@ function createModalSandboxDriver(options) {
100
348
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
101
349
  const tunnelTimeoutMs = options.tunnelTimeoutMs ?? DEFAULT_TUNNEL_TIMEOUT_MS;
102
350
  const operationTimeoutMs = options.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;
103
- const readinessProbe = options.readinessProbe ?? ((port) => Probe.withTcp(port, { intervalMs: 1e3 }));
351
+ async function readinessProbeFor(port) {
352
+ if (options.readinessProbe) return options.readinessProbe(port);
353
+ const { Probe } = await import("modal");
354
+ return Probe.withTcp(port, { intervalMs: 1e3 });
355
+ }
104
356
  function workspaceDirectory(input) {
105
357
  return input.workspaceRoot ?? workspaceDir;
106
358
  }
107
359
  function bootEnv(input, hostId) {
108
- const env = {
109
- ...workspaceRuntimeTargetEnv({
110
- workspaceId: input.workspaceId,
111
- hostId,
112
- directory: workspaceDirectory(input),
113
- port: runtimePort,
114
- host: "0.0.0.0"
115
- }),
116
- ...workspaceRuntimeSourceEnv({ source: input.source }),
117
- ...input.env
118
- };
119
- if (options.runner) env.WORKSPACE_RUNTIME_RUNNER = options.runner;
120
- if (options.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = options.controlEnv.relayJwksUrl;
121
- if (options.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = options.controlEnv.relayVerifyPem;
122
- if (options.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = options.controlEnv.managementJwksUrl;
123
- return env;
360
+ return workspaceRuntimeBootEnv({
361
+ workspaceId: input.workspaceId,
362
+ hostId,
363
+ directory: workspaceDirectory(input),
364
+ port: runtimePort,
365
+ host: "0.0.0.0",
366
+ source: input.source,
367
+ env: input.env,
368
+ runner: options.runner,
369
+ controlEnv: options.controlEnv
370
+ });
124
371
  }
125
- async function image(input) {
372
+ async function image(client, input) {
126
373
  if (input.bootSource?.kind === "driver-snapshot") return client.images.fromId(input.bootSource.snapshotId);
127
374
  if (input.bootSource?.kind === "image") return client.images.fromId(input.bootSource.image);
128
375
  if (input.snapshot) return client.images.fromId(input.snapshot);
@@ -160,9 +407,10 @@ function createModalSandboxDriver(options) {
160
407
  ...bootEnv(input, hostId),
161
408
  ...await options.env?.(input, { id: hostId })
162
409
  };
410
+ const client = await resolveClient();
163
411
  const sandbox = await client.sandboxes.create(
164
412
  await client.apps.fromName(appName, { createIfMissing: true }),
165
- await image(input),
413
+ await image(client, input),
166
414
  {
167
415
  name: nameFor(input.workspaceId),
168
416
  command: ["bash", "-lc", `mkdir -p ${shell(directory)}; cd ${shell(directory)}; exec ${runtimeCommand}`],
@@ -172,7 +420,7 @@ function createModalSandboxDriver(options) {
172
420
  timeoutMs,
173
421
  ...options.idleTimeoutMs !== void 0 ? { idleTimeoutMs: options.idleTimeoutMs } : {},
174
422
  tags: { ...input.labels, "claxedo.workspaceId": input.workspaceId },
175
- readinessProbe: readinessProbe(runtimePort),
423
+ readinessProbe: await readinessProbeFor(runtimePort),
176
424
  ...network(input)
177
425
  }
178
426
  ).catch((err) => {
@@ -183,7 +431,7 @@ function createModalSandboxDriver(options) {
183
431
  return readyTarget(input, sandbox, hostId);
184
432
  }
185
433
  async function sandboxById(target) {
186
- return client.sandboxes.fromId(target.sandboxId);
434
+ return (await resolveClient()).sandboxes.fromId(target.sandboxId);
187
435
  }
188
436
  return {
189
437
  id: "modal",
@@ -192,7 +440,9 @@ function createModalSandboxDriver(options) {
192
440
  hostStopBehavior: "terminates-host",
193
441
  hostResumeBehavior: "replacement-host",
194
442
  targetAccess: "relay",
195
- secretBrokering: "none"
443
+ secretBrokering: "none",
444
+ egressControl: "none",
445
+ persistence: sandboxDriverCatalog.modal.metadata.persistence
196
446
  },
197
447
  ensureHost,
198
448
  resumeHost: (input) => ensureHost(input.ensure),
@@ -0,0 +1,11 @@
1
+ /** The status and lower-cased text a driver error carries, if it carries any. */
2
+ export declare function driverErrorSignals(err: unknown): {
3
+ status: number | undefined;
4
+ text: string;
5
+ };
6
+ /**
7
+ * A 5xx is transient for every driver; anything else is a per-driver judgment
8
+ * made from the error text, so each driver passes the markers it has actually
9
+ * seen its own SDK produce.
10
+ */
11
+ export declare function isTransientDriverError(err: unknown, markers: readonly string[]): boolean;
@@ -1,5 +1,6 @@
1
- import { type NetworkPolicy } from "@vercel/sandbox";
2
- import type { SandboxBrokeredSecret, SandboxDriver, SandboxDriverEnsureInput } from "..";
1
+ import type { NetworkPolicy } from "@vercel/sandbox";
2
+ import { type SandboxBrokeredSecret, type SandboxDriver, type SandboxDriverEnsureInput } from "..";
3
+ import { type WorkspaceRuntimeControlEnv } from "../runtime-env";
3
4
  type VercelSnapshotLike = {
4
5
  snapshotId: string;
5
6
  };
@@ -80,11 +81,7 @@ export type VercelSandboxDriverOptions = {
80
81
  runtimeCommand?: string;
81
82
  workspaceDir?: string;
82
83
  runner?: string;
83
- controlEnv?: {
84
- relayJwksUrl?: string;
85
- relayVerifyPem?: string;
86
- managementJwksUrl?: string;
87
- };
84
+ controlEnv?: WorkspaceRuntimeControlEnv;
88
85
  env?: (input: SandboxDriverEnsureInput, host: {
89
86
  id: string;
90
87
  }) => Record<string, string> | Promise<Record<string, string>>;
@@ -101,10 +98,29 @@ export type VercelSandboxDriverOptions = {
101
98
  * a header on outbound requests to the allowlisted hosts, so the raw value
102
99
  * never enters the sandbox (see @claxedo/sandbox-manager SandboxBrokeredSecret
103
100
  * and https://vercel.com/docs/sandbox/concepts/firewall — credentials
104
- * brokering). The resulting policy is deny-all-except-brokered-hosts, which is
105
- * the secure default: a sandbox holding a brokered credential should only be
106
- * able to reach the host that credential is for.
101
+ * brokering).
102
+ *
103
+ * The result is the union of `base` — the create-time allow-list — and the
104
+ * brokered hosts, never a replacement. Replacing it is wrong in both
105
+ * directions: it breaks egress the caller was granted, since
106
+ * `updateNetworkPolicy` overwrites the whole policy, so a sandbox created with
107
+ * git and the npm registry allowed loses both the instant a brokered secret is
108
+ * attached, and the agent's next `npm install` fails mid-run. It can also
109
+ * silently widen egress, since a brokered host that was not in the create-time
110
+ * list becomes reachable, installing a policy nobody approved as a side effect
111
+ * of attaching a credential. A union changes exactly one thing — it adds the
112
+ * brokered hosts, the smallest edit that makes the credential usable.
113
+ *
114
+ * `base` is the create-time `NetworkPolicy` exactly as passed to `create`, and
115
+ * it is required even though `undefined` is a legal value: `undefined` means
116
+ * "no policy was requested", so the merge keeps egress unrestricted, expressed
117
+ * as the `"*"` wildcard the record form needs for the header transforms to ride
118
+ * alongside it. An optional parameter defaulting to
119
+ * deny-all-except-brokered-hosts would let an existing caller keep compiling
120
+ * while its egress silently widens. Spelling the base out forces that decision
121
+ * to be made once, visibly. A `"deny-all"` base contributes nothing, leaving
122
+ * deny-all-except-brokered-hosts.
107
123
  */
108
- export declare function vercelBrokeredNetworkPolicy(secrets: SandboxBrokeredSecret[]): NetworkPolicy;
124
+ export declare function vercelBrokeredNetworkPolicy(secrets: SandboxBrokeredSecret[], base: NetworkPolicy | undefined): NetworkPolicy;
109
125
  export declare function createVercelSandboxDriver(options: VercelSandboxDriverOptions): SandboxDriver;
110
126
  export {};