@claxedo/sandbox-manager 0.7.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.
- package/README.md +10 -14
- package/dist/driver-catalog.d.ts +3 -53
- package/dist/driver-catalog.mjs +137 -128
- package/dist/drivers/box.mjs +143 -68
- package/dist/drivers/cloudflare.d.ts +4 -7
- package/dist/drivers/cloudflare.mjs +198 -108
- package/dist/drivers/daytona.d.ts +49 -13
- package/dist/drivers/daytona.mjs +225 -83
- package/dist/drivers/docker.mjs +118 -67
- package/dist/drivers/exe.mjs +74 -51
- package/dist/drivers/fetch-bridge.mjs +19 -4
- package/dist/drivers/modal.d.ts +2 -5
- package/dist/drivers/modal.mjs +130 -75
- package/dist/drivers/transient-error.d.ts +11 -0
- package/dist/drivers/vercel.d.ts +19 -22
- package/dist/drivers/vercel.mjs +178 -104
- package/dist/hosted-network-policy.d.ts +2 -2
- package/dist/hosted-network-policy.mjs +3 -3
- package/dist/image-name.mjs +34 -0
- package/dist/image.d.ts +2 -2
- package/dist/image.mjs +33 -11
- package/dist/index.d.ts +62 -17
- package/dist/index.mjs +68 -22
- package/dist/json.d.ts +13 -0
- package/dist/lease-policy.d.ts +6 -2
- package/dist/lease-policy.mjs +50 -40
- package/dist/lease-types.d.ts +11 -2
- package/dist/runtime-env.d.ts +12 -1
- package/dist/runtime-env.mjs +14 -0
- package/dist/service-url-exposure.d.ts +1 -2
- package/dist/service-url-exposure.mjs +1 -17
- package/dist/stores/memory.mjs +2 -2
- package/package.json +57 -55
- package/dist/drivers/cloudflare-egress.d.ts +0 -40
- package/dist/drivers/cloudflare-egress.mjs +0 -113
package/dist/drivers/docker.mjs
CHANGED
|
@@ -5,7 +5,31 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
|
|
8
|
+
// ../claxedo-helpers/dist/string.mjs
|
|
9
|
+
function isString(value) {
|
|
10
|
+
return typeof value === "string";
|
|
11
|
+
}
|
|
12
|
+
function trimToUndefined(value) {
|
|
13
|
+
if (!isString(value))
|
|
14
|
+
return;
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
// src/runtime-env.ts
|
|
20
|
+
function workspaceRuntimeBootEnv(input) {
|
|
21
|
+
const env = {
|
|
22
|
+
...workspaceRuntimeTargetEnv(input),
|
|
23
|
+
...workspaceRuntimeSourceEnv(input),
|
|
24
|
+
...input.env
|
|
25
|
+
};
|
|
26
|
+
if (input.runner) env.WORKSPACE_RUNTIME_RUNNER = input.runner;
|
|
27
|
+
if (input.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = input.controlEnv.relayJwksUrl;
|
|
28
|
+
if (input.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = input.controlEnv.relayVerifyPem;
|
|
29
|
+
if (input.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = input.controlEnv.managementJwksUrl;
|
|
30
|
+
if (input.controlEnv?.sessionAuthorityUrl) env.WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL = input.controlEnv.sessionAuthorityUrl;
|
|
31
|
+
return env;
|
|
32
|
+
}
|
|
9
33
|
function workspaceRuntimeTargetEnv(input) {
|
|
10
34
|
return {
|
|
11
35
|
WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
|
|
@@ -32,9 +56,6 @@ function shell(input) {
|
|
|
32
56
|
// src/constants.ts
|
|
33
57
|
var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
|
|
34
58
|
|
|
35
|
-
// src/image.ts
|
|
36
|
-
import { Image, DaytonaNotFoundError } from "@daytona/sdk";
|
|
37
|
-
|
|
38
59
|
// src/runtime-version.ts
|
|
39
60
|
var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
|
|
40
61
|
function workspaceRuntimeVersion() {
|
|
@@ -62,6 +83,16 @@ function defaultSnapshotName(version = workspaceRuntimeVersion(), buildId, env =
|
|
|
62
83
|
return `claxedo-workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
|
|
63
84
|
}
|
|
64
85
|
|
|
86
|
+
// ../claxedo-helpers/dist/guards.mjs
|
|
87
|
+
function isRecord(value) {
|
|
88
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/json.ts
|
|
92
|
+
function num(input) {
|
|
93
|
+
return typeof input === "number" ? input : void 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
65
96
|
// src/image.ts
|
|
66
97
|
var SANDBOX_IMAGE_REPOSITORY = sandboxImageRepository();
|
|
67
98
|
var SANDBOX_IMAGE = process.env.CLAXEDO_SANDBOX_IMAGE || defaultSandboxImage();
|
|
@@ -93,12 +124,42 @@ var SANDBOX_BASELINE_EGRESS_HOSTS = [
|
|
|
93
124
|
...SANDBOX_PACKAGE_REGISTRY_HOSTS
|
|
94
125
|
];
|
|
95
126
|
|
|
127
|
+
// ../sandbox-contract/dist/index.mjs
|
|
128
|
+
var sandboxDriverCredentialFields = {
|
|
129
|
+
exe: [{ key: "api_token", label: "API Token", secret: true }],
|
|
130
|
+
daytona: [{ key: "api_key", label: "API Key", secret: true }],
|
|
131
|
+
modal: [
|
|
132
|
+
{ key: "token_id", label: "Token ID", secret: true },
|
|
133
|
+
{ key: "token_secret", label: "Token Secret", secret: true }
|
|
134
|
+
],
|
|
135
|
+
vercel: [
|
|
136
|
+
{ key: "access_token", label: "Access Token", secret: true },
|
|
137
|
+
{ key: "team_id", label: "Team ID" },
|
|
138
|
+
{ key: "project_id", label: "Project ID" }
|
|
139
|
+
],
|
|
140
|
+
cloudflare: [
|
|
141
|
+
{ key: "api_token", label: "API Token", secret: true },
|
|
142
|
+
{ key: "worker_url", label: "Worker URL" }
|
|
143
|
+
],
|
|
144
|
+
box: [{ key: "api_key", label: "API Key", secret: true }],
|
|
145
|
+
docker: [{ key: "image", label: "Image" }]
|
|
146
|
+
};
|
|
147
|
+
var sandboxDriverLabels = {
|
|
148
|
+
exe: "exe.dev",
|
|
149
|
+
daytona: "Daytona",
|
|
150
|
+
modal: "Modal",
|
|
151
|
+
vercel: "Vercel",
|
|
152
|
+
cloudflare: "Cloudflare",
|
|
153
|
+
box: "Box",
|
|
154
|
+
docker: "Docker"
|
|
155
|
+
};
|
|
156
|
+
|
|
96
157
|
// src/driver-catalog.ts
|
|
97
158
|
var sandboxDriverCatalog = {
|
|
98
159
|
exe: {
|
|
99
160
|
id: "exe",
|
|
100
|
-
label:
|
|
101
|
-
credentialFields:
|
|
161
|
+
label: sandboxDriverLabels.exe,
|
|
162
|
+
credentialFields: sandboxDriverCredentialFields.exe,
|
|
102
163
|
metadata: {
|
|
103
164
|
driverRunsIn: ["worker", "node"],
|
|
104
165
|
hostStopBehavior: "not-supported",
|
|
@@ -118,17 +179,15 @@ var sandboxDriverCatalog = {
|
|
|
118
179
|
},
|
|
119
180
|
daytona: {
|
|
120
181
|
id: "daytona",
|
|
121
|
-
label:
|
|
122
|
-
credentialFields:
|
|
182
|
+
label: sandboxDriverLabels.daytona,
|
|
183
|
+
credentialFields: sandboxDriverCredentialFields.daytona,
|
|
123
184
|
metadata: {
|
|
124
185
|
driverRunsIn: ["worker", "node"],
|
|
125
186
|
hostStopBehavior: "suspends-host",
|
|
126
187
|
hostResumeBehavior: "same-host",
|
|
127
188
|
targetAccess: "relay",
|
|
128
189
|
secretBrokering: "native",
|
|
129
|
-
//
|
|
130
|
-
// by address: `domainAllowList` (names) alongside `networkAllowList`
|
|
131
|
-
// (CIDRs), with `networkBlockAll` as the deny-all floor.
|
|
190
|
+
// The driver translates names to domainAllowList, or addresses to networkAllowList.
|
|
132
191
|
egressControl: "hosts-and-cidrs",
|
|
133
192
|
persistence: {
|
|
134
193
|
resume: "same-sandbox",
|
|
@@ -142,20 +201,15 @@ var sandboxDriverCatalog = {
|
|
|
142
201
|
},
|
|
143
202
|
modal: {
|
|
144
203
|
id: "modal",
|
|
145
|
-
label:
|
|
146
|
-
credentialFields:
|
|
147
|
-
{ key: "token_id", label: "Token ID", secret: true },
|
|
148
|
-
{ key: "token_secret", label: "Token Secret", secret: true }
|
|
149
|
-
],
|
|
204
|
+
label: sandboxDriverLabels.modal,
|
|
205
|
+
credentialFields: sandboxDriverCredentialFields.modal,
|
|
150
206
|
metadata: {
|
|
151
207
|
driverRunsIn: ["node"],
|
|
152
208
|
hostStopBehavior: "terminates-host",
|
|
153
209
|
hostResumeBehavior: "replacement-host",
|
|
154
210
|
targetAccess: "relay",
|
|
155
211
|
secretBrokering: "none",
|
|
156
|
-
//
|
|
157
|
-
// an allowlist, and its driver throws on a host policy. A blackout is
|
|
158
|
-
// not containment for a workspace that has to clone and reach a model.
|
|
212
|
+
// The driver does not install Modal's domain allowlist or alpha sidecar.
|
|
159
213
|
egressControl: "none",
|
|
160
214
|
persistence: {
|
|
161
215
|
resume: "replacement-restore",
|
|
@@ -169,20 +223,15 @@ var sandboxDriverCatalog = {
|
|
|
169
223
|
},
|
|
170
224
|
vercel: {
|
|
171
225
|
id: "vercel",
|
|
172
|
-
label:
|
|
173
|
-
credentialFields:
|
|
174
|
-
{ key: "access_token", label: "Access Token", secret: true },
|
|
175
|
-
{ key: "team_id", label: "Team ID" },
|
|
176
|
-
{ key: "project_id", label: "Project ID" }
|
|
177
|
-
],
|
|
226
|
+
label: sandboxDriverLabels.vercel,
|
|
227
|
+
credentialFields: sandboxDriverCredentialFields.vercel,
|
|
178
228
|
metadata: {
|
|
179
229
|
driverRunsIn: ["node"],
|
|
180
230
|
hostStopBehavior: "terminates-host",
|
|
181
231
|
hostResumeBehavior: "replacement-host",
|
|
182
232
|
targetAccess: "relay",
|
|
183
233
|
secretBrokering: "native",
|
|
184
|
-
//
|
|
185
|
-
// never CIDRs.
|
|
234
|
+
// The driver translates hosts only; the SDK's subnet rules are not wired to net.cidrs.
|
|
186
235
|
egressControl: "hosts",
|
|
187
236
|
persistence: {
|
|
188
237
|
resume: "replacement-restore",
|
|
@@ -196,23 +245,15 @@ var sandboxDriverCatalog = {
|
|
|
196
245
|
},
|
|
197
246
|
cloudflare: {
|
|
198
247
|
id: "cloudflare",
|
|
199
|
-
label:
|
|
200
|
-
credentialFields:
|
|
201
|
-
{ key: "api_token", label: "API Token", secret: true },
|
|
202
|
-
{ key: "worker_url", label: "Worker URL" }
|
|
203
|
-
],
|
|
248
|
+
label: sandboxDriverLabels.cloudflare,
|
|
249
|
+
credentialFields: sandboxDriverCredentialFields.cloudflare,
|
|
204
250
|
metadata: {
|
|
205
251
|
driverRunsIn: ["worker"],
|
|
206
252
|
hostStopBehavior: "not-supported",
|
|
207
253
|
hostResumeBehavior: "same-host",
|
|
208
254
|
targetAccess: "relay",
|
|
209
|
-
secretBrokering: "
|
|
210
|
-
//
|
|
211
|
-
// (drivers/cloudflare-egress.ts) is an OPT-IN proxy the sandbox chooses
|
|
212
|
-
// to route brokered-credential requests through, not a boundary. The
|
|
213
|
-
// driver therefore drops `net` entirely — which is exactly why this
|
|
214
|
-
// must be declared, so the manager refuses instead of provisioning a
|
|
215
|
-
// sandbox the caller believes is contained.
|
|
255
|
+
secretBrokering: "native",
|
|
256
|
+
// Credential handlers do not restrict unrelated destinations.
|
|
216
257
|
egressControl: "none",
|
|
217
258
|
persistence: {
|
|
218
259
|
resume: "replacement-restore",
|
|
@@ -226,8 +267,8 @@ var sandboxDriverCatalog = {
|
|
|
226
267
|
},
|
|
227
268
|
box: {
|
|
228
269
|
id: "box",
|
|
229
|
-
label:
|
|
230
|
-
credentialFields:
|
|
270
|
+
label: sandboxDriverLabels.box,
|
|
271
|
+
credentialFields: sandboxDriverCredentialFields.box,
|
|
231
272
|
metadata: {
|
|
232
273
|
driverRunsIn: ["node"],
|
|
233
274
|
hostStopBehavior: "suspends-host",
|
|
@@ -247,8 +288,8 @@ var sandboxDriverCatalog = {
|
|
|
247
288
|
},
|
|
248
289
|
docker: {
|
|
249
290
|
id: "docker",
|
|
250
|
-
label:
|
|
251
|
-
credentialFields:
|
|
291
|
+
label: sandboxDriverLabels.docker,
|
|
292
|
+
credentialFields: sandboxDriverCredentialFields.docker,
|
|
252
293
|
metadata: {
|
|
253
294
|
driverRunsIn: ["local"],
|
|
254
295
|
hostStopBehavior: "terminates-host",
|
|
@@ -268,6 +309,24 @@ var sandboxDriverCatalog = {
|
|
|
268
309
|
}
|
|
269
310
|
};
|
|
270
311
|
|
|
312
|
+
// src/drivers/transient-error.ts
|
|
313
|
+
function textOf(input) {
|
|
314
|
+
return typeof input === "string" ? input : "";
|
|
315
|
+
}
|
|
316
|
+
function driverErrorSignals(err) {
|
|
317
|
+
const shaped = isRecord(err) ? err : {};
|
|
318
|
+
const nested = isRecord(shaped.response) ? shaped.response : {};
|
|
319
|
+
return {
|
|
320
|
+
status: num(nested.status) ?? num(shaped.status),
|
|
321
|
+
text: `${textOf(shaped.code)} ${textOf(shaped.name)} ${textOf(shaped.message)}`.toLowerCase()
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function isTransientDriverError(err, markers) {
|
|
325
|
+
const { status, text: text2 } = driverErrorSignals(err);
|
|
326
|
+
if (status !== void 0 && status >= 500) return true;
|
|
327
|
+
return markers.some((marker) => text2.includes(marker));
|
|
328
|
+
}
|
|
329
|
+
|
|
271
330
|
// src/drivers/docker.ts
|
|
272
331
|
var execFileAsync = promisify(execFile);
|
|
273
332
|
var DEFAULT_OPERATION_TIMEOUT_MS = 6e4;
|
|
@@ -278,13 +337,13 @@ var DEFAULT_WORKSPACE_DIR = "/workspace";
|
|
|
278
337
|
var DOCKER_LABEL = "claxedo";
|
|
279
338
|
var AUTH_STAGE_DIR = "/tmp/claxedo-auth";
|
|
280
339
|
function dockerSandboxSyncLocalAuth(env = process.env) {
|
|
281
|
-
return
|
|
340
|
+
return enabledFlag(env.CLAXEDO_DOCKER_SANDBOX_SYNC_LOCAL_AUTH);
|
|
282
341
|
}
|
|
283
342
|
function dockerSandboxAuthHome(env = process.env) {
|
|
284
|
-
return
|
|
343
|
+
return trimToUndefined(env.CLAXEDO_DOCKER_SANDBOX_AUTH_HOME) ?? trimToUndefined(env.HOME) ?? os.homedir();
|
|
285
344
|
}
|
|
286
345
|
function dockerSandboxImageFromEnv(env = process.env) {
|
|
287
|
-
return
|
|
346
|
+
return trimToUndefined(env.CLAXEDO_DOCKER_SANDBOX_IMAGE) ?? trimToUndefined(env.CLAXEDO_SANDBOX_IMAGE) ?? SANDBOX_IMAGE;
|
|
288
347
|
}
|
|
289
348
|
function parseDockerPort(input) {
|
|
290
349
|
const line = input.trim().split(/\r?\n/)[0]?.trim();
|
|
@@ -315,12 +374,8 @@ function dockerLocalAuthCandidates(home) {
|
|
|
315
374
|
}
|
|
316
375
|
];
|
|
317
376
|
}
|
|
318
|
-
function
|
|
319
|
-
|
|
320
|
-
return txt ? txt : void 0;
|
|
321
|
-
}
|
|
322
|
-
function disabled(input) {
|
|
323
|
-
return ["0", "false", "no", "off"].includes(input?.trim().toLowerCase() ?? "");
|
|
377
|
+
function enabledFlag(input) {
|
|
378
|
+
return ["1", "true", "yes", "on"].includes(input?.trim().toLowerCase() ?? "");
|
|
324
379
|
}
|
|
325
380
|
function nameFor(workspaceId) {
|
|
326
381
|
const cleanId = workspaceId.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -383,10 +438,9 @@ function envArgs(input) {
|
|
|
383
438
|
function labelArgs(input) {
|
|
384
439
|
return Object.entries(input).flatMap(([key, value]) => ["--label", `${key}=${value}`]);
|
|
385
440
|
}
|
|
441
|
+
var TRANSIENT_MARKERS = ["timeout", "temporarily unavailable", "connection refused"];
|
|
386
442
|
function transientDriverError(err) {
|
|
387
|
-
|
|
388
|
-
const text = `${shaped.code ?? ""} ${shaped.message ?? ""}`.toLowerCase();
|
|
389
|
-
return text.includes("timeout") || text.includes("temporarily unavailable") || text.includes("connection refused");
|
|
443
|
+
return isTransientDriverError(err, TRANSIENT_MARKERS);
|
|
390
444
|
}
|
|
391
445
|
function createDockerSandboxDriver(options) {
|
|
392
446
|
const dockerCommand = options.docker ?? docker;
|
|
@@ -411,19 +465,16 @@ function createDockerSandboxDriver(options) {
|
|
|
411
465
|
return input.bootSource?.kind === "image" ? input.bootSource.image : input.snapshot ?? options.image;
|
|
412
466
|
}
|
|
413
467
|
function bootEnv(input, hostId) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
};
|
|
425
|
-
if (options.runner) env.WORKSPACE_RUNTIME_RUNNER = options.runner;
|
|
426
|
-
return env;
|
|
468
|
+
return workspaceRuntimeBootEnv({
|
|
469
|
+
workspaceId: input.workspaceId,
|
|
470
|
+
hostId,
|
|
471
|
+
directory: workspaceDirectory(input),
|
|
472
|
+
port: runtimePort(input),
|
|
473
|
+
host: "0.0.0.0",
|
|
474
|
+
source: input.source,
|
|
475
|
+
env: input.env,
|
|
476
|
+
runner: options.runner
|
|
477
|
+
});
|
|
427
478
|
}
|
|
428
479
|
async function readyTarget(input, sandboxId, hostId) {
|
|
429
480
|
const out = await dockerCommand(["port", sandboxId, `${runtimePort(input)}/tcp`], timeoutMs);
|
package/dist/drivers/exe.mjs
CHANGED
|
@@ -59,12 +59,42 @@ function defaultSnapshotName(version = workspaceRuntimeVersion(), buildId, env =
|
|
|
59
59
|
return `claxedo-workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// ../sandbox-contract/dist/index.mjs
|
|
63
|
+
var sandboxDriverCredentialFields = {
|
|
64
|
+
exe: [{ key: "api_token", label: "API Token", secret: true }],
|
|
65
|
+
daytona: [{ key: "api_key", label: "API Key", secret: true }],
|
|
66
|
+
modal: [
|
|
67
|
+
{ key: "token_id", label: "Token ID", secret: true },
|
|
68
|
+
{ key: "token_secret", label: "Token Secret", secret: true }
|
|
69
|
+
],
|
|
70
|
+
vercel: [
|
|
71
|
+
{ key: "access_token", label: "Access Token", secret: true },
|
|
72
|
+
{ key: "team_id", label: "Team ID" },
|
|
73
|
+
{ key: "project_id", label: "Project ID" }
|
|
74
|
+
],
|
|
75
|
+
cloudflare: [
|
|
76
|
+
{ key: "api_token", label: "API Token", secret: true },
|
|
77
|
+
{ key: "worker_url", label: "Worker URL" }
|
|
78
|
+
],
|
|
79
|
+
box: [{ key: "api_key", label: "API Key", secret: true }],
|
|
80
|
+
docker: [{ key: "image", label: "Image" }]
|
|
81
|
+
};
|
|
82
|
+
var sandboxDriverLabels = {
|
|
83
|
+
exe: "exe.dev",
|
|
84
|
+
daytona: "Daytona",
|
|
85
|
+
modal: "Modal",
|
|
86
|
+
vercel: "Vercel",
|
|
87
|
+
cloudflare: "Cloudflare",
|
|
88
|
+
box: "Box",
|
|
89
|
+
docker: "Docker"
|
|
90
|
+
};
|
|
91
|
+
|
|
62
92
|
// src/driver-catalog.ts
|
|
63
93
|
var sandboxDriverCatalog = {
|
|
64
94
|
exe: {
|
|
65
95
|
id: "exe",
|
|
66
|
-
label:
|
|
67
|
-
credentialFields:
|
|
96
|
+
label: sandboxDriverLabels.exe,
|
|
97
|
+
credentialFields: sandboxDriverCredentialFields.exe,
|
|
68
98
|
metadata: {
|
|
69
99
|
driverRunsIn: ["worker", "node"],
|
|
70
100
|
hostStopBehavior: "not-supported",
|
|
@@ -84,17 +114,15 @@ var sandboxDriverCatalog = {
|
|
|
84
114
|
},
|
|
85
115
|
daytona: {
|
|
86
116
|
id: "daytona",
|
|
87
|
-
label:
|
|
88
|
-
credentialFields:
|
|
117
|
+
label: sandboxDriverLabels.daytona,
|
|
118
|
+
credentialFields: sandboxDriverCredentialFields.daytona,
|
|
89
119
|
metadata: {
|
|
90
120
|
driverRunsIn: ["worker", "node"],
|
|
91
121
|
hostStopBehavior: "suspends-host",
|
|
92
122
|
hostResumeBehavior: "same-host",
|
|
93
123
|
targetAccess: "relay",
|
|
94
124
|
secretBrokering: "native",
|
|
95
|
-
//
|
|
96
|
-
// by address: `domainAllowList` (names) alongside `networkAllowList`
|
|
97
|
-
// (CIDRs), with `networkBlockAll` as the deny-all floor.
|
|
125
|
+
// The driver translates names to domainAllowList, or addresses to networkAllowList.
|
|
98
126
|
egressControl: "hosts-and-cidrs",
|
|
99
127
|
persistence: {
|
|
100
128
|
resume: "same-sandbox",
|
|
@@ -108,20 +136,15 @@ var sandboxDriverCatalog = {
|
|
|
108
136
|
},
|
|
109
137
|
modal: {
|
|
110
138
|
id: "modal",
|
|
111
|
-
label:
|
|
112
|
-
credentialFields:
|
|
113
|
-
{ key: "token_id", label: "Token ID", secret: true },
|
|
114
|
-
{ key: "token_secret", label: "Token Secret", secret: true }
|
|
115
|
-
],
|
|
139
|
+
label: sandboxDriverLabels.modal,
|
|
140
|
+
credentialFields: sandboxDriverCredentialFields.modal,
|
|
116
141
|
metadata: {
|
|
117
142
|
driverRunsIn: ["node"],
|
|
118
143
|
hostStopBehavior: "terminates-host",
|
|
119
144
|
hostResumeBehavior: "replacement-host",
|
|
120
145
|
targetAccess: "relay",
|
|
121
146
|
secretBrokering: "none",
|
|
122
|
-
//
|
|
123
|
-
// an allowlist, and its driver throws on a host policy. A blackout is
|
|
124
|
-
// not containment for a workspace that has to clone and reach a model.
|
|
147
|
+
// The driver does not install Modal's domain allowlist or alpha sidecar.
|
|
125
148
|
egressControl: "none",
|
|
126
149
|
persistence: {
|
|
127
150
|
resume: "replacement-restore",
|
|
@@ -135,20 +158,15 @@ var sandboxDriverCatalog = {
|
|
|
135
158
|
},
|
|
136
159
|
vercel: {
|
|
137
160
|
id: "vercel",
|
|
138
|
-
label:
|
|
139
|
-
credentialFields:
|
|
140
|
-
{ key: "access_token", label: "Access Token", secret: true },
|
|
141
|
-
{ key: "team_id", label: "Team ID" },
|
|
142
|
-
{ key: "project_id", label: "Project ID" }
|
|
143
|
-
],
|
|
161
|
+
label: sandboxDriverLabels.vercel,
|
|
162
|
+
credentialFields: sandboxDriverCredentialFields.vercel,
|
|
144
163
|
metadata: {
|
|
145
164
|
driverRunsIn: ["node"],
|
|
146
165
|
hostStopBehavior: "terminates-host",
|
|
147
166
|
hostResumeBehavior: "replacement-host",
|
|
148
167
|
targetAccess: "relay",
|
|
149
168
|
secretBrokering: "native",
|
|
150
|
-
//
|
|
151
|
-
// never CIDRs.
|
|
169
|
+
// The driver translates hosts only; the SDK's subnet rules are not wired to net.cidrs.
|
|
152
170
|
egressControl: "hosts",
|
|
153
171
|
persistence: {
|
|
154
172
|
resume: "replacement-restore",
|
|
@@ -162,23 +180,15 @@ var sandboxDriverCatalog = {
|
|
|
162
180
|
},
|
|
163
181
|
cloudflare: {
|
|
164
182
|
id: "cloudflare",
|
|
165
|
-
label:
|
|
166
|
-
credentialFields:
|
|
167
|
-
{ key: "api_token", label: "API Token", secret: true },
|
|
168
|
-
{ key: "worker_url", label: "Worker URL" }
|
|
169
|
-
],
|
|
183
|
+
label: sandboxDriverLabels.cloudflare,
|
|
184
|
+
credentialFields: sandboxDriverCredentialFields.cloudflare,
|
|
170
185
|
metadata: {
|
|
171
186
|
driverRunsIn: ["worker"],
|
|
172
187
|
hostStopBehavior: "not-supported",
|
|
173
188
|
hostResumeBehavior: "same-host",
|
|
174
189
|
targetAccess: "relay",
|
|
175
|
-
secretBrokering: "
|
|
176
|
-
//
|
|
177
|
-
// (drivers/cloudflare-egress.ts) is an OPT-IN proxy the sandbox chooses
|
|
178
|
-
// to route brokered-credential requests through, not a boundary. The
|
|
179
|
-
// driver therefore drops `net` entirely — which is exactly why this
|
|
180
|
-
// must be declared, so the manager refuses instead of provisioning a
|
|
181
|
-
// sandbox the caller believes is contained.
|
|
190
|
+
secretBrokering: "native",
|
|
191
|
+
// Credential handlers do not restrict unrelated destinations.
|
|
182
192
|
egressControl: "none",
|
|
183
193
|
persistence: {
|
|
184
194
|
resume: "replacement-restore",
|
|
@@ -192,8 +202,8 @@ var sandboxDriverCatalog = {
|
|
|
192
202
|
},
|
|
193
203
|
box: {
|
|
194
204
|
id: "box",
|
|
195
|
-
label:
|
|
196
|
-
credentialFields:
|
|
205
|
+
label: sandboxDriverLabels.box,
|
|
206
|
+
credentialFields: sandboxDriverCredentialFields.box,
|
|
197
207
|
metadata: {
|
|
198
208
|
driverRunsIn: ["node"],
|
|
199
209
|
hostStopBehavior: "suspends-host",
|
|
@@ -213,8 +223,8 @@ var sandboxDriverCatalog = {
|
|
|
213
223
|
},
|
|
214
224
|
docker: {
|
|
215
225
|
id: "docker",
|
|
216
|
-
label:
|
|
217
|
-
credentialFields:
|
|
226
|
+
label: sandboxDriverLabels.docker,
|
|
227
|
+
credentialFields: sandboxDriverCredentialFields.docker,
|
|
218
228
|
metadata: {
|
|
219
229
|
driverRunsIn: ["local"],
|
|
220
230
|
hostStopBehavior: "terminates-host",
|
|
@@ -234,8 +244,20 @@ var sandboxDriverCatalog = {
|
|
|
234
244
|
}
|
|
235
245
|
};
|
|
236
246
|
|
|
247
|
+
// ../claxedo-helpers/dist/guards.mjs
|
|
248
|
+
function isRecord(value) {
|
|
249
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
250
|
+
}
|
|
251
|
+
function asRecord(value) {
|
|
252
|
+
return isRecord(value) ? value : void 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/json.ts
|
|
256
|
+
function text(input) {
|
|
257
|
+
return typeof input === "string" && input.length > 0 ? input : void 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
237
260
|
// src/image.ts
|
|
238
|
-
import { Image, DaytonaNotFoundError } from "@daytona/sdk";
|
|
239
261
|
var SANDBOX_IMAGE_REPOSITORY = sandboxImageRepository();
|
|
240
262
|
var SANDBOX_IMAGE = process.env.CLAXEDO_SANDBOX_IMAGE || defaultSandboxImage();
|
|
241
263
|
var SNAPSHOT_NAME = process.env.CLAXEDO_SNAPSHOT_NAME || defaultSnapshotName();
|
|
@@ -269,7 +291,8 @@ var DEFAULT_HEALTH_TIMEOUT_MS = 6e4;
|
|
|
269
291
|
var DEFAULT_HEALTH_INTERVAL_MS = 1e3;
|
|
270
292
|
function exeWorkspaceName(workspaceId, epoch) {
|
|
271
293
|
const slug = workspaceId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30) || "workspace";
|
|
272
|
-
|
|
294
|
+
let hash = 2166136261;
|
|
295
|
+
for (const char of workspaceId) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619);
|
|
273
296
|
return `claxedo-ws-${slug}-${(hash >>> 0).toString(36).slice(0, 8)}-g${epoch}`.slice(0, 63).replace(/-+$/g, "");
|
|
274
297
|
}
|
|
275
298
|
function createExeSandboxDriver(options) {
|
|
@@ -292,17 +315,17 @@ function createExeSandboxDriver(options) {
|
|
|
292
315
|
body: command,
|
|
293
316
|
signal: AbortSignal.timeout(operationTimeoutMs)
|
|
294
317
|
});
|
|
295
|
-
const
|
|
296
|
-
const body =
|
|
318
|
+
const payload = await response.text();
|
|
319
|
+
const body = (payload ? asRecord(JSON.parse(payload)) : void 0) ?? {};
|
|
297
320
|
if (!response.ok || body.ok === false || typeof body.error === "string") {
|
|
298
|
-
|
|
321
|
+
const detail = text(body.error) ?? text(body.message) ?? response.statusText;
|
|
322
|
+
throw new Error(`exe.dev command failed (${response.status}): ${detail}`);
|
|
299
323
|
}
|
|
300
324
|
return body;
|
|
301
325
|
}
|
|
302
326
|
function vm(input) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (typeof value.vm_name !== "string") return;
|
|
327
|
+
const value = asRecord(input);
|
|
328
|
+
if (!value || typeof value.vm_name !== "string") return void 0;
|
|
306
329
|
return {
|
|
307
330
|
vm_name: value.vm_name,
|
|
308
331
|
...typeof value.https_url === "string" ? { https_url: value.https_url } : {},
|
|
@@ -315,11 +338,11 @@ function createExeSandboxDriver(options) {
|
|
|
315
338
|
const cached = labels.get(item.vm_name);
|
|
316
339
|
if (cached) return cached;
|
|
317
340
|
const tags = item.tags ?? [];
|
|
318
|
-
if (!tags.includes("claxedo")) return;
|
|
341
|
+
if (!tags.includes("claxedo")) return void 0;
|
|
319
342
|
const workspaceId = tags.find((tag) => tag.startsWith("workspace-"))?.slice("workspace-".length);
|
|
320
|
-
if (!workspaceId) return;
|
|
343
|
+
if (!workspaceId) return void 0;
|
|
321
344
|
const epoch = item.vm_name.match(/-g(\d+)$/)?.[1];
|
|
322
|
-
if (!epoch) return;
|
|
345
|
+
if (!epoch) return void 0;
|
|
323
346
|
return { app: DEFAULT_EXE_APP_LABEL, workspaceId, epoch };
|
|
324
347
|
}
|
|
325
348
|
async function inspect(name) {
|
|
@@ -340,7 +363,7 @@ function createExeSandboxDriver(options) {
|
|
|
340
363
|
}
|
|
341
364
|
async function execute(name, command) {
|
|
342
365
|
const body = await api(`ssh ${shell(name)} ${shell(command)}`);
|
|
343
|
-
const result = body.result
|
|
366
|
+
const result = asRecord(body.result) ?? body;
|
|
344
367
|
return {
|
|
345
368
|
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
346
369
|
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
// ../claxedo-helpers/dist/guards.mjs
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function asRecord(value) {
|
|
6
|
+
return isRecord(value) ? value : void 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
// src/drivers/fetch-bridge.ts
|
|
2
10
|
function cleanUrl(input) {
|
|
3
11
|
return input.replace(/\/+$/, "");
|
|
@@ -10,6 +18,12 @@ function assertLifecycleSettings(options) {
|
|
|
10
18
|
throw new Error("Fetch bridge sandbox driver requires a finite auto-delete policy");
|
|
11
19
|
}
|
|
12
20
|
}
|
|
21
|
+
function stringMap(input) {
|
|
22
|
+
const row = asRecord(input);
|
|
23
|
+
if (!row) return void 0;
|
|
24
|
+
const entries = Object.entries(row).filter((entry) => typeof entry[1] === "string");
|
|
25
|
+
return Object.fromEntries(entries);
|
|
26
|
+
}
|
|
13
27
|
function headers(token) {
|
|
14
28
|
return {
|
|
15
29
|
"content-type": "application/json",
|
|
@@ -17,8 +31,8 @@ function headers(token) {
|
|
|
17
31
|
};
|
|
18
32
|
}
|
|
19
33
|
function target(input) {
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
const row = asRecord(input);
|
|
35
|
+
if (!row) throw new Error("Fetch bridge sandbox driver returned an invalid response");
|
|
22
36
|
if (row.status === "provisioning") {
|
|
23
37
|
return {
|
|
24
38
|
provisioning: true,
|
|
@@ -34,7 +48,7 @@ function target(input) {
|
|
|
34
48
|
url: row.url,
|
|
35
49
|
hostId: row.hostId,
|
|
36
50
|
driverResourceId: typeof row.driverResourceId === "string" ? row.driverResourceId : void 0,
|
|
37
|
-
labels:
|
|
51
|
+
labels: stringMap(row.labels)
|
|
38
52
|
};
|
|
39
53
|
}
|
|
40
54
|
function bridgeTarget(input) {
|
|
@@ -43,7 +57,8 @@ function bridgeTarget(input) {
|
|
|
43
57
|
return result;
|
|
44
58
|
}
|
|
45
59
|
function targetList(input) {
|
|
46
|
-
const
|
|
60
|
+
const targets = asRecord(input)?.targets;
|
|
61
|
+
const rows = Array.isArray(input) ? input : Array.isArray(targets) ? targets : void 0;
|
|
47
62
|
if (!rows) throw new Error("Fetch bridge sandbox driver returned an invalid target list");
|
|
48
63
|
return rows.map(bridgeTarget);
|
|
49
64
|
}
|
package/dist/drivers/modal.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SandboxDriver, SandboxDriverEnsureInput } from "..";
|
|
2
|
+
import { type WorkspaceRuntimeControlEnv } from "../runtime-env";
|
|
2
3
|
type ModalAppLike = unknown;
|
|
3
4
|
type ModalImageLike = {
|
|
4
5
|
imageId?: string;
|
|
@@ -49,11 +50,7 @@ export type ModalSandboxDriverOptions = {
|
|
|
49
50
|
runtimeCommand?: string;
|
|
50
51
|
workspaceDir?: string;
|
|
51
52
|
runner?: string;
|
|
52
|
-
controlEnv?:
|
|
53
|
-
relayJwksUrl?: string;
|
|
54
|
-
relayVerifyPem?: string;
|
|
55
|
-
managementJwksUrl?: string;
|
|
56
|
-
};
|
|
53
|
+
controlEnv?: WorkspaceRuntimeControlEnv;
|
|
57
54
|
env?: (input: SandboxDriverEnsureInput, host: {
|
|
58
55
|
id: string;
|
|
59
56
|
}) => Record<string, string> | Promise<Record<string, string>>;
|