@veris-ai/daytona 0.1.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 +127 -0
- package/dist/control-plane.d.ts +89 -0
- package/dist/daytona.d.ts +92 -0
- package/dist/errors.d.ts +56 -0
- package/dist/gateway.d.ts +65 -0
- package/dist/index.cjs +904 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +855 -0
- package/dist/index.js.map +1 -0
- package/dist/network.d.ts +74 -0
- package/dist/receipt.d.ts +36 -0
- package/dist/trust.d.ts +55 -0
- package/dist/veris-api.d.ts +74 -0
- package/dist/version.d.ts +1 -0
- package/package.json +62 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
CA_CERT_PATH: () => CA_CERT_PATH,
|
|
25
|
+
ControlPlane: () => ControlPlane,
|
|
26
|
+
DEFAULT_REGISTRY_HOSTS: () => DEFAULT_REGISTRY_HOSTS,
|
|
27
|
+
Daytona: () => Daytona,
|
|
28
|
+
MissingCredentialsError: () => MissingCredentialsError,
|
|
29
|
+
ReceiptIntegrityError: () => ReceiptIntegrityError,
|
|
30
|
+
SDK_VERSION: () => SDK_VERSION,
|
|
31
|
+
SYSTEM_BUNDLE: () => SYSTEM_BUNDLE,
|
|
32
|
+
SnapshotUnsupportedError: () => SnapshotUnsupportedError,
|
|
33
|
+
TwinExpiredError: () => TwinExpiredError,
|
|
34
|
+
UnsupportedOperationError: () => UnsupportedOperationError,
|
|
35
|
+
VERIS_BUNDLE: () => VERIS_BUNDLE,
|
|
36
|
+
VERIS_CA_FILE: () => VERIS_CA_FILE,
|
|
37
|
+
VerisError: () => VerisError,
|
|
38
|
+
VerisGatewayNotOfferedError: () => VerisGatewayNotOfferedError,
|
|
39
|
+
VerisGatewayUnreachableError: () => VerisGatewayUnreachableError,
|
|
40
|
+
VerisUntouchedError: () => VerisUntouchedError,
|
|
41
|
+
dataPlaneHosts: () => dataPlaneHosts,
|
|
42
|
+
default: () => daytona_default,
|
|
43
|
+
gatewayProxyUrl: () => gatewayProxyUrl,
|
|
44
|
+
isVerisSandbox: () => isVerisSandbox,
|
|
45
|
+
twinHosts: () => twinHosts,
|
|
46
|
+
vendorHosts: () => vendorHosts,
|
|
47
|
+
vendoredTrustEnv: () => vendoredTrustEnv
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(index_exports);
|
|
50
|
+
__reExport(index_exports, require("@daytona/sdk"), module.exports);
|
|
51
|
+
|
|
52
|
+
// src/daytona.ts
|
|
53
|
+
var import_sdk = require("@daytona/sdk");
|
|
54
|
+
|
|
55
|
+
// src/errors.ts
|
|
56
|
+
var VerisError = class extends Error {
|
|
57
|
+
phase;
|
|
58
|
+
/** The Veris twin's sandbox id, when one exists yet. */
|
|
59
|
+
verisSandboxId;
|
|
60
|
+
/** Verbatim control-plane response body, when the failure came from an API call. */
|
|
61
|
+
responseBody;
|
|
62
|
+
constructor(message, opts = {}) {
|
|
63
|
+
super(message, opts.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
64
|
+
this.name = new.target.name;
|
|
65
|
+
this.phase = opts.phase;
|
|
66
|
+
this.verisSandboxId = opts.verisSandboxId;
|
|
67
|
+
this.responseBody = opts.responseBody;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var MissingCredentialsError = class extends VerisError {
|
|
71
|
+
};
|
|
72
|
+
var VerisGatewayUnreachableError = class extends VerisError {
|
|
73
|
+
};
|
|
74
|
+
var VerisGatewayNotOfferedError = class extends VerisError {
|
|
75
|
+
/** Server-announced minimum SDK version, when the refusal carried one. */
|
|
76
|
+
minSdk;
|
|
77
|
+
constructor(message, opts = {}) {
|
|
78
|
+
super(message, opts);
|
|
79
|
+
this.minSdk = opts.minSdk;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var ReceiptIntegrityError = class extends VerisError {
|
|
83
|
+
};
|
|
84
|
+
var VerisUntouchedError = class extends VerisError {
|
|
85
|
+
service;
|
|
86
|
+
constructor(message, service, opts = {}) {
|
|
87
|
+
super(message, opts);
|
|
88
|
+
this.service = service;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var TwinExpiredError = class extends VerisError {
|
|
92
|
+
};
|
|
93
|
+
var SnapshotUnsupportedError = class extends VerisError {
|
|
94
|
+
};
|
|
95
|
+
var UnsupportedOperationError = class extends VerisError {
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/control-plane.ts
|
|
99
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
100
|
+
var ControlPlane = class {
|
|
101
|
+
apiBase;
|
|
102
|
+
headers;
|
|
103
|
+
constructor(opts) {
|
|
104
|
+
this.apiBase = opts.apiBase.replace(/\/$/, "");
|
|
105
|
+
this.headers = {
|
|
106
|
+
"X-API-Key": opts.apiKey,
|
|
107
|
+
"X-Veris-SDK": opts.sdkVersion,
|
|
108
|
+
"Content-Type": "application/json"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async request(method, path, body) {
|
|
112
|
+
let res;
|
|
113
|
+
try {
|
|
114
|
+
res = await fetch(`${this.apiBase}${path}`, {
|
|
115
|
+
method,
|
|
116
|
+
headers: this.headers,
|
|
117
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
118
|
+
});
|
|
119
|
+
} catch (cause) {
|
|
120
|
+
throw new VerisError(`Veris control plane unreachable (${method} ${path})`, { cause });
|
|
121
|
+
}
|
|
122
|
+
return res;
|
|
123
|
+
}
|
|
124
|
+
async json(res, context, phase) {
|
|
125
|
+
const text = await res.text();
|
|
126
|
+
let parsed;
|
|
127
|
+
try {
|
|
128
|
+
parsed = text ? JSON.parse(text) : void 0;
|
|
129
|
+
} catch {
|
|
130
|
+
parsed = text;
|
|
131
|
+
}
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
throw new VerisError(`${context}: ${res.status}`, { phase, responseBody: parsed });
|
|
134
|
+
}
|
|
135
|
+
if (parsed === void 0) {
|
|
136
|
+
throw new VerisError(`${context}: empty response body`, { phase, responseBody: text });
|
|
137
|
+
}
|
|
138
|
+
return parsed;
|
|
139
|
+
}
|
|
140
|
+
async createTwin(environmentId, opts = {}) {
|
|
141
|
+
const res = await this.request("POST", `/v1/environments/${environmentId}/sandboxes`, {
|
|
142
|
+
ttl_minutes: opts.ttlMinutes,
|
|
143
|
+
metadata: opts.metadata
|
|
144
|
+
});
|
|
145
|
+
return this.json(res, `create sandbox in environment ${environmentId}`, "twin-provision");
|
|
146
|
+
}
|
|
147
|
+
async getTwin(sandboxId) {
|
|
148
|
+
const res = await this.request("GET", `/v1/sandboxes/${sandboxId}`);
|
|
149
|
+
if (res.status === 404) return null;
|
|
150
|
+
return this.json(res, `get sandbox ${sandboxId}`);
|
|
151
|
+
}
|
|
152
|
+
/** Poll until the twin reports ready. "failed" is terminal per the API docs. */
|
|
153
|
+
async waitReady(sandboxId, timeoutMs) {
|
|
154
|
+
const deadline = Date.now() + timeoutMs;
|
|
155
|
+
for (; ; ) {
|
|
156
|
+
const twin = await this.getTwin(sandboxId);
|
|
157
|
+
if (!twin) throw new TwinExpiredError(`Veris sandbox ${sandboxId} disappeared while provisioning`, { verisSandboxId: sandboxId });
|
|
158
|
+
if (twin.status === "ready") return twin;
|
|
159
|
+
if (twin.status === "failed") {
|
|
160
|
+
throw new VerisError(
|
|
161
|
+
`Veris sandbox ${sandboxId} failed to provision: ${twin.failure_reason ?? "no failure_reason"}`,
|
|
162
|
+
{ phase: "twin-provision", verisSandboxId: sandboxId }
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (Date.now() > deadline) {
|
|
166
|
+
throw new VerisError(
|
|
167
|
+
`Veris sandbox ${sandboxId} not ready after ${timeoutMs}ms (status: ${twin.status})`,
|
|
168
|
+
{ phase: "twin-provision", verisSandboxId: sandboxId }
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
await sleep(1500);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async services(sandboxId) {
|
|
175
|
+
const res = await this.request("GET", `/v1/sandboxes/${sandboxId}/services`);
|
|
176
|
+
if (res.status === 404) {
|
|
177
|
+
throw new TwinExpiredError(`Veris sandbox ${sandboxId} not found \u2014 expired or deleted`, { verisSandboxId: sandboxId });
|
|
178
|
+
}
|
|
179
|
+
return this.json(res, `services of sandbox ${sandboxId}`, "receipt");
|
|
180
|
+
}
|
|
181
|
+
async deleteTwin(environmentId, sandboxId) {
|
|
182
|
+
const res = await this.request("DELETE", `/v1/environments/${environmentId}/sandboxes/${sandboxId}`);
|
|
183
|
+
if (res.status === 404) return false;
|
|
184
|
+
if (!res.ok) await this.json(res, `delete sandbox ${sandboxId}`);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Mint (or re-mint) the gateway egress credential for a twin. Returns null
|
|
189
|
+
* when the control plane does not offer gateway mode at all (404 — route
|
|
190
|
+
* absent), so `mode: 'auto'` can fall back; throws VerisGatewayNotOfferedError
|
|
191
|
+
* on an explicit version refusal (409 sdk_too_old).
|
|
192
|
+
*/
|
|
193
|
+
async mintEgressCredential(environmentId, sandboxId) {
|
|
194
|
+
const res = await this.request("POST", `/v1/environments/${environmentId}/sandboxes/${sandboxId}/egress-credential`);
|
|
195
|
+
if (res.status === 404) return null;
|
|
196
|
+
if (res.status === 409) {
|
|
197
|
+
const body = await res.json().catch(() => ({}));
|
|
198
|
+
throw new VerisGatewayNotOfferedError(
|
|
199
|
+
`this SDK version is below the control plane's minimum for gateway mode${body.min_sdk ? ` (min_sdk ${body.min_sdk})` : ""} \u2014 upgrade @veris-ai/daytona`,
|
|
200
|
+
{ phase: "credential-mint", verisSandboxId: sandboxId, minSdk: body.min_sdk, responseBody: body }
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return this.json(res, `mint egress credential for ${sandboxId}`, "credential-mint");
|
|
204
|
+
}
|
|
205
|
+
/** PATCH the twin resource. Omitted fields are untouched by the server. */
|
|
206
|
+
async updateSandbox(environmentId, sandboxId, patch) {
|
|
207
|
+
const res = await this.request("PATCH", `/v1/environments/${environmentId}/sandboxes/${sandboxId}`, patch);
|
|
208
|
+
if (res.status === 404) {
|
|
209
|
+
throw new TwinExpiredError(`Veris sandbox ${sandboxId} not found`, { verisSandboxId: sandboxId });
|
|
210
|
+
}
|
|
211
|
+
if (!res.ok) await this.json(res, `update sandbox ${sandboxId}`);
|
|
212
|
+
}
|
|
213
|
+
/** Extend a twin's TTL so it stays in lockstep with an extended Daytona sandbox. */
|
|
214
|
+
async extendTtl(environmentId, sandboxId, ttlMinutes) {
|
|
215
|
+
const res = await this.request("PATCH", `/v1/environments/${environmentId}/sandboxes/${sandboxId}`, { ttl_minutes: ttlMinutes });
|
|
216
|
+
if (res.status === 404) {
|
|
217
|
+
throw new TwinExpiredError(`Veris sandbox ${sandboxId} not found \u2014 cannot extend TTL`, { verisSandboxId: sandboxId });
|
|
218
|
+
}
|
|
219
|
+
if (!res.ok && res.status !== 405) await this.json(res, `extend TTL of ${sandboxId}`);
|
|
220
|
+
}
|
|
221
|
+
/** Create-time preflight: is the gateway infrastructure up, per the control plane? */
|
|
222
|
+
async gatewayHealth() {
|
|
223
|
+
const res = await this.request("GET", "/v1/gateway/health");
|
|
224
|
+
if (res.status === 404) return;
|
|
225
|
+
if (!res.ok) {
|
|
226
|
+
throw new VerisGatewayUnreachableError(
|
|
227
|
+
`Veris gateway reported unhealthy (${res.status})`,
|
|
228
|
+
{ phase: "gateway-preflight" }
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/receipt.ts
|
|
235
|
+
function parseRequestsBody(body) {
|
|
236
|
+
const rows = Array.isArray(body?.requests) ? body.requests : [];
|
|
237
|
+
const entries = rows.map((r) => {
|
|
238
|
+
const row = r;
|
|
239
|
+
return {
|
|
240
|
+
method: String(row.method ?? ""),
|
|
241
|
+
path: String(row.path ?? ""),
|
|
242
|
+
status: typeof row.status === "number" ? row.status : null
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
return { count: entries.length, entries };
|
|
246
|
+
}
|
|
247
|
+
async function fetchReceiptEntry(svc) {
|
|
248
|
+
const url = `${svc.control_url}/veris/requests`;
|
|
249
|
+
const res = await fetch(url);
|
|
250
|
+
const text = await res.text();
|
|
251
|
+
if (!res.ok) {
|
|
252
|
+
throw new VerisError(`could not read receipt for service '${svc.name}' (${res.status})`, {
|
|
253
|
+
phase: "receipt",
|
|
254
|
+
responseBody: text.slice(0, 500)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
let raw;
|
|
258
|
+
try {
|
|
259
|
+
raw = JSON.parse(text);
|
|
260
|
+
} catch {
|
|
261
|
+
throw new VerisError(`service '${svc.name}' returned a non-JSON receipt body`, {
|
|
262
|
+
phase: "receipt",
|
|
263
|
+
responseBody: text.slice(0, 500)
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
const { count, entries } = parseRequestsBody(raw);
|
|
267
|
+
return { requests: count, controlUrl: svc.control_url, entries, raw };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/network.ts
|
|
271
|
+
var isHttpUrl = (u) => /^https?:/.test(u);
|
|
272
|
+
function vendorHosts(services) {
|
|
273
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
274
|
+
for (const svc of services) {
|
|
275
|
+
for (const r of svc.routes ?? []) hosts.add(r.host);
|
|
276
|
+
}
|
|
277
|
+
return [...hosts].sort();
|
|
278
|
+
}
|
|
279
|
+
function twinHosts(services) {
|
|
280
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
281
|
+
for (const svc of services) {
|
|
282
|
+
for (const u of [svc.control_url, svc.url]) {
|
|
283
|
+
if (!u || !isHttpUrl(u)) continue;
|
|
284
|
+
try {
|
|
285
|
+
hosts.add(new URL(u).hostname);
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return [...hosts].sort();
|
|
291
|
+
}
|
|
292
|
+
function dataPlaneHosts(services) {
|
|
293
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
294
|
+
for (const svc of services) {
|
|
295
|
+
if (!svc.url || isHttpUrl(svc.url)) continue;
|
|
296
|
+
for (const h of hostsFromDsn(svc.url)) hosts.add(h);
|
|
297
|
+
}
|
|
298
|
+
return [...hosts].sort();
|
|
299
|
+
}
|
|
300
|
+
function hostsFromDsn(dsn) {
|
|
301
|
+
const out = [];
|
|
302
|
+
try {
|
|
303
|
+
const u = new URL(dsn);
|
|
304
|
+
if (u.hostname) out.push(u.hostname.replace(/^\[|\]$/g, ""));
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
const authority = dsn.replace(/^[^:]+:\/\//, "").split(/[/?]/)[0] ?? "";
|
|
308
|
+
const afterAt = authority.includes("@") ? authority.slice(authority.lastIndexOf("@") + 1) : authority;
|
|
309
|
+
for (const part of afterAt.split(",")) {
|
|
310
|
+
const m = part.match(/^\[?([A-Za-z0-9_.:-]+?)\]?(?::\d+)?$/);
|
|
311
|
+
if (m?.[1] && !/^\d+$/.test(m[1])) out.push(m[1].replace(/^\[|\]$/g, ""));
|
|
312
|
+
}
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
function dataPlaneEnv(services) {
|
|
316
|
+
const envs = {};
|
|
317
|
+
for (const svc of services) {
|
|
318
|
+
if (!svc.env_hint || !svc.url || isHttpUrl(svc.url)) continue;
|
|
319
|
+
if (!isSafeEnvName(svc.env_hint)) continue;
|
|
320
|
+
envs[svc.env_hint] = svc.url;
|
|
321
|
+
}
|
|
322
|
+
return envs;
|
|
323
|
+
}
|
|
324
|
+
var PROCESS_CONTROLLING = /* @__PURE__ */ new Set([
|
|
325
|
+
"PATH",
|
|
326
|
+
"LD_PRELOAD",
|
|
327
|
+
"LD_LIBRARY_PATH",
|
|
328
|
+
"NODE_OPTIONS",
|
|
329
|
+
"BASH_ENV",
|
|
330
|
+
"ENV",
|
|
331
|
+
"PYTHONPATH",
|
|
332
|
+
"PYTHONSTARTUP",
|
|
333
|
+
"SHELL",
|
|
334
|
+
"IFS",
|
|
335
|
+
"HOME",
|
|
336
|
+
"PROMPT_COMMAND"
|
|
337
|
+
]);
|
|
338
|
+
function isSafeEnvName(name) {
|
|
339
|
+
return /^[A-Z][A-Z0-9_]{0,63}$/.test(name) && !PROCESS_CONTROLLING.has(name);
|
|
340
|
+
}
|
|
341
|
+
var DEFAULT_REGISTRY_HOSTS = [
|
|
342
|
+
// JS
|
|
343
|
+
"registry.npmjs.org",
|
|
344
|
+
"registry.yarnpkg.com",
|
|
345
|
+
// Python
|
|
346
|
+
"pypi.org",
|
|
347
|
+
"files.pythonhosted.org",
|
|
348
|
+
// Go
|
|
349
|
+
"proxy.golang.org",
|
|
350
|
+
"sum.golang.org",
|
|
351
|
+
// Rust
|
|
352
|
+
"crates.io",
|
|
353
|
+
"static.crates.io",
|
|
354
|
+
"index.crates.io",
|
|
355
|
+
// Debian/Ubuntu
|
|
356
|
+
"deb.debian.org",
|
|
357
|
+
"security.debian.org",
|
|
358
|
+
"archive.ubuntu.com",
|
|
359
|
+
"security.ubuntu.com",
|
|
360
|
+
// Source + container hosts the above routinely redirect to
|
|
361
|
+
"github.com",
|
|
362
|
+
"codeload.github.com",
|
|
363
|
+
"objects.githubusercontent.com",
|
|
364
|
+
"raw.githubusercontent.com",
|
|
365
|
+
"ghcr.io"
|
|
366
|
+
];
|
|
367
|
+
function buildNetwork(args) {
|
|
368
|
+
const { services, mode, gatewayHosts, allowOut = [], allowRegistries = true } = args;
|
|
369
|
+
if (mode === "open") return {};
|
|
370
|
+
const domains = [
|
|
371
|
+
...vendorHosts(services),
|
|
372
|
+
...gatewayHosts,
|
|
373
|
+
...dataPlaneHosts(services),
|
|
374
|
+
...allowRegistries ? DEFAULT_REGISTRY_HOSTS : [],
|
|
375
|
+
...allowOut
|
|
376
|
+
].filter((h) => Boolean(h));
|
|
377
|
+
return {
|
|
378
|
+
// NOT networkBlockAll: that blocks everything including the gateway, and the
|
|
379
|
+
// allowlist is what Daytona documents as "unbypassable network-layer
|
|
380
|
+
// enforcement". Blocking all and then allowing is not a shape the API
|
|
381
|
+
// offers; a non-empty domainAllowList IS the deny-by-default.
|
|
382
|
+
domainAllowList: [...new Set(domains)].sort().join(",")
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/trust.ts
|
|
387
|
+
var CA_CERT_PATH = "/usr/local/share/ca-certificates/veris-ca.crt";
|
|
388
|
+
var SYSTEM_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
|
|
389
|
+
var VERIS_CA_FILE = "/tmp/veris-ca.crt";
|
|
390
|
+
var VERIS_BUNDLE = "/tmp/veris-ca-bundle.crt";
|
|
391
|
+
function vendoredTrustEnv() {
|
|
392
|
+
return {
|
|
393
|
+
SSL_CERT_FILE: VERIS_BUNDLE,
|
|
394
|
+
REQUESTS_CA_BUNDLE: VERIS_BUNDLE,
|
|
395
|
+
CURL_CA_BUNDLE: VERIS_BUNDLE,
|
|
396
|
+
GIT_SSL_CAINFO: VERIS_BUNDLE,
|
|
397
|
+
AWS_CA_BUNDLE: VERIS_BUNDLE,
|
|
398
|
+
CARGO_HTTP_CAINFO: VERIS_BUNDLE,
|
|
399
|
+
DENO_CERT: VERIS_BUNDLE,
|
|
400
|
+
PIP_CERT: VERIS_BUNDLE,
|
|
401
|
+
npm_config_cafile: VERIS_BUNDLE,
|
|
402
|
+
GRPC_DEFAULT_SSL_ROOTS_FILE_PATH: VERIS_BUNDLE,
|
|
403
|
+
BUNDLE_SSL_CA_CERT: VERIS_BUNDLE,
|
|
404
|
+
COMPOSER_CAFILE: VERIS_BUNDLE,
|
|
405
|
+
HEX_CACERTS_PATH: VERIS_BUNDLE,
|
|
406
|
+
JULIA_SSL_CA_ROOTS_PATH: VERIS_BUNDLE,
|
|
407
|
+
NIX_SSL_CERT_FILE: VERIS_BUNDLE,
|
|
408
|
+
PERL_LWP_SSL_CA_FILE: VERIS_BUNDLE,
|
|
409
|
+
CLOUDSDK_CORE_CUSTOM_CA_CERTS_FILE: VERIS_BUNDLE,
|
|
410
|
+
NODE_EXTRA_CA_CERTS: VERIS_CA_FILE
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
var CA_INSTALL_CMD = [
|
|
414
|
+
"update-ca-certificates",
|
|
415
|
+
`(keytool -importcert -noprompt -cacerts -storepass changeit -alias veris -file ${CA_CERT_PATH} 2>/dev/null || true)`,
|
|
416
|
+
`(command -v certutil >/dev/null 2>&1 && for db in $(find /home /root -maxdepth 4 -name "cert9.db" 2>/dev/null | xargs -r -n1 dirname); do certutil -A -n veris -t "C,," -i ${CA_CERT_PATH} -d "sql:$db" 2>/dev/null || true; done || true)`
|
|
417
|
+
].join(" && ");
|
|
418
|
+
function sanitizeTrustEnv(served) {
|
|
419
|
+
const vendored = vendoredTrustEnv();
|
|
420
|
+
const out = { ...vendored };
|
|
421
|
+
for (const [k, val] of Object.entries(served ?? {})) {
|
|
422
|
+
if (!(k in vendored)) continue;
|
|
423
|
+
if (typeof val !== "string") continue;
|
|
424
|
+
if (!/^\/[\w./+~-]+$/.test(val)) continue;
|
|
425
|
+
out[k] = val;
|
|
426
|
+
}
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/gateway.ts
|
|
431
|
+
var HOSTNAME_RE = /^[A-Za-z0-9.-]+$/;
|
|
432
|
+
var HOSTPORT_RE = /^[A-Za-z0-9.-]+:\d{1,5}$/;
|
|
433
|
+
function shellQuote(s) {
|
|
434
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
435
|
+
}
|
|
436
|
+
var sh = (sandbox, cmd, timeoutSec = 60) => sandbox.process.executeCommand(`sh -lc ${shellQuote(cmd)}`, void 0, void 0, timeoutSec);
|
|
437
|
+
function gatewayProxyUrl(credential) {
|
|
438
|
+
if (credential.http_proxy_url) return assertProxyUrl(credential.http_proxy_url);
|
|
439
|
+
if (!credential.connect_address || !HOSTPORT_RE.test(credential.connect_address)) {
|
|
440
|
+
throw new VerisError(
|
|
441
|
+
`the control plane returned a malformed gateway address: ${JSON.stringify(credential.connect_address)} (expected host:port)`,
|
|
442
|
+
{ phase: "credential-mint" }
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
return `http://${encodeURIComponent(credential.username)}:x@${credential.connect_address}`;
|
|
446
|
+
}
|
|
447
|
+
function assertProxyUrl(raw) {
|
|
448
|
+
let u;
|
|
449
|
+
try {
|
|
450
|
+
u = new URL(raw);
|
|
451
|
+
} catch {
|
|
452
|
+
throw new VerisError(
|
|
453
|
+
`the control plane returned an unparseable gateway proxy URL: ${JSON.stringify(raw)}`,
|
|
454
|
+
{ phase: "credential-mint" }
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
458
|
+
throw new VerisError(
|
|
459
|
+
`the gateway proxy URL uses scheme "${u.protocol.replace(":", "")}", and Daytona accepts only http or https outbound proxies`,
|
|
460
|
+
{ phase: "credential-mint" }
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
if (!u.hostname || !u.port) {
|
|
464
|
+
throw new VerisError(
|
|
465
|
+
`the gateway proxy URL is missing a host or port: ${JSON.stringify(raw)}`,
|
|
466
|
+
{ phase: "credential-mint" }
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
return raw;
|
|
470
|
+
}
|
|
471
|
+
async function installCa(sandbox, caPem) {
|
|
472
|
+
await sandbox.fs.uploadFile(Buffer.from(caPem, "utf8"), VERIS_CA_FILE);
|
|
473
|
+
const script = [
|
|
474
|
+
`chmod 0644 ${VERIS_CA_FILE}`,
|
|
475
|
+
// Public roots first so they keep working; ours appended. `cat` of a
|
|
476
|
+
// missing file is tolerated — an image with no roots at all still gets a
|
|
477
|
+
// bundle containing the one CA that matters here.
|
|
478
|
+
`{ cat ${SYSTEM_BUNDLE} 2>/dev/null; cat ${VERIS_CA_FILE}; } > ${VERIS_BUNDLE}`,
|
|
479
|
+
`chmod 0644 ${VERIS_BUNDLE}`,
|
|
480
|
+
// Best-effort, for the stacks that read a store rather than a variable:
|
|
481
|
+
// the system bundle, the JVM truststore, and NSS databases. All of it needs
|
|
482
|
+
// root and tooling that may not be there, so none of it is load-bearing —
|
|
483
|
+
// but a Java client honours no CA env var at all, so where we CAN do it,
|
|
484
|
+
// we should.
|
|
485
|
+
`SUDO=; [ "$(id -u)" = 0 ] || SUDO="sudo -n"`,
|
|
486
|
+
`($SUDO install -m 0644 -D ${VERIS_CA_FILE} ${CA_CERT_PATH} 2>/dev/null && $SUDO sh -c ${shellQuote(CA_INSTALL_CMD)} 2>/dev/null) || true`,
|
|
487
|
+
// The bundle is the load-bearing one: fail loudly if it is not there.
|
|
488
|
+
`[ -s ${VERIS_BUNDLE} ] && echo __VERIS_CA_OK__`
|
|
489
|
+
].join("; ");
|
|
490
|
+
const r = await sh(sandbox, script, 120).catch((e) => ({ exitCode: 1, result: String(e) }));
|
|
491
|
+
if (!(r.result ?? "").includes("__VERIS_CA_OK__")) {
|
|
492
|
+
throw new SnapshotUnsupportedError(
|
|
493
|
+
`could not assemble a CA bundle at ${VERIS_BUNDLE}, so the gateway's certificates cannot be trusted (${(r.result ?? "").trim().slice(0, 200)})`,
|
|
494
|
+
{ phase: "ca-install" }
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
async function probeCanary(sandbox, canaryHost, expectedTwinId) {
|
|
499
|
+
if (!HOSTNAME_RE.test(canaryHost)) {
|
|
500
|
+
throw new ReceiptIntegrityError(
|
|
501
|
+
`refusing to probe a malformed canary host from the control plane: ${JSON.stringify(canaryHost)}`,
|
|
502
|
+
{ phase: "canary", verisSandboxId: expectedTwinId }
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
const r = await sh(
|
|
506
|
+
sandbox,
|
|
507
|
+
`curl -sS --cacert ${VERIS_BUNDLE} --max-time 20 https://${canaryHost}/ || echo __VERIS_CANARY_FAIL__`,
|
|
508
|
+
45
|
|
509
|
+
).catch((e) => ({ exitCode: 1, result: String(e) }));
|
|
510
|
+
let body = {};
|
|
511
|
+
try {
|
|
512
|
+
body = JSON.parse(r.result ?? "");
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
if (body.veris_sandbox_id !== expectedTwinId) {
|
|
516
|
+
throw new ReceiptIntegrityError(
|
|
517
|
+
`canary probe failed: egress from this Daytona sandbox is not tunnelled through the Veris gateway (expected twin ${expectedTwinId}, canary answered: ${(r.result || "nothing").trim().slice(0, 200)})`,
|
|
518
|
+
{ phase: "canary", verisSandboxId: expectedTwinId }
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/veris-api.ts
|
|
524
|
+
var VerisApiImpl = class {
|
|
525
|
+
constructor(ctx) {
|
|
526
|
+
this.ctx = ctx;
|
|
527
|
+
}
|
|
528
|
+
ctx;
|
|
529
|
+
get sandboxId() {
|
|
530
|
+
return this.ctx.twinId;
|
|
531
|
+
}
|
|
532
|
+
get mode() {
|
|
533
|
+
return "gateway";
|
|
534
|
+
}
|
|
535
|
+
services() {
|
|
536
|
+
return this.ctx.controlPlane.services(this.ctx.twinId);
|
|
537
|
+
}
|
|
538
|
+
async receipt(service) {
|
|
539
|
+
await probeCanary(this.ctx.sandbox, this.ctx.canaryHost, this.ctx.twinId);
|
|
540
|
+
const services = await this.services();
|
|
541
|
+
if (service !== void 0) {
|
|
542
|
+
const svc = services.find((s) => s.name === service);
|
|
543
|
+
if (!svc) {
|
|
544
|
+
throw new VerisError(
|
|
545
|
+
`unknown service '${service}' \u2014 the twin has no service by that name (available: ${services.map((s) => s.name).join(", ") || "none"})`,
|
|
546
|
+
{ verisSandboxId: this.ctx.twinId }
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
return fetchReceiptEntry(svc);
|
|
550
|
+
}
|
|
551
|
+
const entries = await Promise.all(
|
|
552
|
+
services.filter((s) => isHttpUrl(s.control_url)).map(async (svc) => [svc.name, await fetchReceiptEntry(svc)])
|
|
553
|
+
);
|
|
554
|
+
return {
|
|
555
|
+
services: Object.fromEntries(entries),
|
|
556
|
+
mode: "gateway",
|
|
557
|
+
integrity: "verified",
|
|
558
|
+
leaks: this.leaks()
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* What this receipt cannot see. The gateway relays TCP, so QUIC/HTTP3 and ECH
|
|
563
|
+
* ride around it — named rather than rounded off.
|
|
564
|
+
*/
|
|
565
|
+
leaks() {
|
|
566
|
+
return ["udp-quic-possible", "ech-possible"];
|
|
567
|
+
}
|
|
568
|
+
async assertTouched(service, match) {
|
|
569
|
+
const entry = await this.receipt(service);
|
|
570
|
+
const need = match?.minRequests ?? 1;
|
|
571
|
+
const matched = match ? entry.entries.filter((r) => (match.method === void 0 || r.method.toUpperCase() === match.method.toUpperCase()) && (match.path === void 0 || r.path.includes(match.path))) : entry.entries;
|
|
572
|
+
if (matched.length < need) {
|
|
573
|
+
const what = match ? `matching ${match.method ?? "ANY"} ${match.path ?? "*"} (${matched.length}/${need})` : "any intercepted requests";
|
|
574
|
+
throw new VerisUntouchedError(
|
|
575
|
+
`service '${service}' saw no ${what} \u2014 the code under test never reached it (a green run that skipped its dependency looks identical to a working one)`,
|
|
576
|
+
service,
|
|
577
|
+
{ verisSandboxId: this.ctx.twinId }
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async getDataPlaneEnv() {
|
|
582
|
+
return dataPlaneEnv(await this.services());
|
|
583
|
+
}
|
|
584
|
+
/** The CA trust vars injected at create, for callers building their own env. */
|
|
585
|
+
getTrustEnv() {
|
|
586
|
+
return vendoredTrustEnv();
|
|
587
|
+
}
|
|
588
|
+
async deliverTo(target, opts = {}) {
|
|
589
|
+
const url = typeof target === "number" ? (await this.ctx.sandbox.getPreviewLink(target)).url : target;
|
|
590
|
+
await this.ctx.controlPlane.updateSandbox(
|
|
591
|
+
this.ctx.environmentId,
|
|
592
|
+
this.ctx.twinId,
|
|
593
|
+
{ client_base_url: url }
|
|
594
|
+
);
|
|
595
|
+
if (url !== null && opts.probe !== false) await this.probeDelivery(url);
|
|
596
|
+
return url;
|
|
597
|
+
}
|
|
598
|
+
/** Ask each service to re-probe the registered destination; throw if none can reach it. */
|
|
599
|
+
async probeDelivery(url) {
|
|
600
|
+
const services = (await this.services()).filter((s) => isHttpUrl(s.control_url));
|
|
601
|
+
if (!services.length) return;
|
|
602
|
+
const probes = await Promise.all(services.map(async (svc) => {
|
|
603
|
+
try {
|
|
604
|
+
const res = await fetch(`${svc.control_url}/veris/client/probe`, { method: "POST" });
|
|
605
|
+
return res.ok ? await res.json() : null;
|
|
606
|
+
} catch {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
}));
|
|
610
|
+
if (!probes.some((p) => p?.answered)) {
|
|
611
|
+
throw new VerisError(
|
|
612
|
+
`no service could reach ${url} \u2014 is your app listening on that port inside the Daytona sandbox?`,
|
|
613
|
+
{ phase: "receipt", verisSandboxId: this.ctx.twinId, responseBody: probes }
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// src/version.ts
|
|
620
|
+
var SDK_VERSION = true ? "0.1.0" : "0.0.0-dev";
|
|
621
|
+
|
|
622
|
+
// src/daytona.ts
|
|
623
|
+
var LABEL = {
|
|
624
|
+
twinId: "veris_twin_id",
|
|
625
|
+
envId: "veris_env_id",
|
|
626
|
+
apiBase: "veris_api_base",
|
|
627
|
+
mode: "veris_mode",
|
|
628
|
+
egress: "veris_egress",
|
|
629
|
+
ownsTwin: "veris_owns_twin",
|
|
630
|
+
canaryHost: "veris_canary_host"
|
|
631
|
+
};
|
|
632
|
+
var VERIS_LABEL_KEYS = Object.values(LABEL);
|
|
633
|
+
function isVerisSandbox(sbx) {
|
|
634
|
+
return typeof sbx.verisSandboxId === "string";
|
|
635
|
+
}
|
|
636
|
+
var Daytona = class extends import_sdk.Daytona {
|
|
637
|
+
verisDefaults;
|
|
638
|
+
constructor(config) {
|
|
639
|
+
super(config);
|
|
640
|
+
this.verisDefaults = config?.veris ?? {};
|
|
641
|
+
}
|
|
642
|
+
async create(params, options) {
|
|
643
|
+
const v = { ...this.verisDefaults, ...params?.veris ?? {} };
|
|
644
|
+
const rest = stripVeris(params);
|
|
645
|
+
if (v.disabled) return this.baseCreate(rest, options);
|
|
646
|
+
const coords = resolveCoordinates(v);
|
|
647
|
+
const controlPlane = new ControlPlane({
|
|
648
|
+
apiKey: coords.apiKey,
|
|
649
|
+
apiBase: coords.apiBase,
|
|
650
|
+
sdkVersion: SDK_VERSION
|
|
651
|
+
});
|
|
652
|
+
const egress = v.egress ?? "strict";
|
|
653
|
+
const ttlMinutes = v.ttlMinutes ?? 60;
|
|
654
|
+
const ownsTwin = !v.attachSandboxId;
|
|
655
|
+
const twin = await this.provisionTwin(controlPlane, v, coords, ttlMinutes);
|
|
656
|
+
const cleanupTwin = async () => {
|
|
657
|
+
if (ownsTwin) await controlPlane.deleteTwin(twin.environment_id, twin.id).catch(() => {
|
|
658
|
+
});
|
|
659
|
+
};
|
|
660
|
+
let sandbox;
|
|
661
|
+
let credential;
|
|
662
|
+
try {
|
|
663
|
+
credential = await controlPlane.mintEgressCredential(twin.environment_id, twin.id);
|
|
664
|
+
if (!credential) {
|
|
665
|
+
throw new VerisGatewayNotOfferedError(
|
|
666
|
+
"this Veris control plane does not offer egress credentials, so there is no gateway for the sandbox to route through",
|
|
667
|
+
{ phase: "credential-mint", verisSandboxId: twin.id }
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
if (!credential.connect_address && !credential.http_proxy_url) {
|
|
671
|
+
throw new VerisGatewayNotOfferedError(
|
|
672
|
+
'the Veris gateway offers SOCKS5 but no HTTP CONNECT endpoint, and Daytona accepts only http/https outbound proxies ("Unsupported outbound proxy scheme"). Upgrade the control plane to one that returns connect_address.',
|
|
673
|
+
{ phase: "credential-mint", verisSandboxId: twin.id }
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
const services = twin.services?.length ? twin.services : await controlPlane.services(twin.id);
|
|
677
|
+
const proxyUrl = gatewayProxyUrl(credential);
|
|
678
|
+
const network = buildNetwork({
|
|
679
|
+
services,
|
|
680
|
+
mode: egress,
|
|
681
|
+
// The gateway has to be reachable or nothing is: taken from the URL we
|
|
682
|
+
// are actually going to use, so the two can never disagree.
|
|
683
|
+
gatewayHosts: [new URL(proxyUrl).hostname, credential.canary_host].filter(Boolean),
|
|
684
|
+
allowOut: v.allowOut,
|
|
685
|
+
allowRegistries: v.allowRegistries
|
|
686
|
+
});
|
|
687
|
+
const verisManaged = {
|
|
688
|
+
...v.installCa !== false ? sanitizeTrustEnv(void 0) : {},
|
|
689
|
+
...v.dataPlaneEnv !== false ? dataPlaneEnv(services) : {},
|
|
690
|
+
VERIS_SANDBOX_ID: twin.id
|
|
691
|
+
};
|
|
692
|
+
const createParams = {
|
|
693
|
+
...rest,
|
|
694
|
+
envVars: { ...rest.envVars ?? {}, ...verisManaged },
|
|
695
|
+
labels: {
|
|
696
|
+
...reserveLabels(rest.labels),
|
|
697
|
+
[LABEL.twinId]: twin.id,
|
|
698
|
+
[LABEL.envId]: twin.environment_id,
|
|
699
|
+
[LABEL.apiBase]: coords.apiBase,
|
|
700
|
+
[LABEL.egress]: egress,
|
|
701
|
+
[LABEL.ownsTwin]: String(ownsTwin),
|
|
702
|
+
[LABEL.mode]: "gateway",
|
|
703
|
+
[LABEL.canaryHost]: credential.canary_host
|
|
704
|
+
},
|
|
705
|
+
...network,
|
|
706
|
+
// 3. Where Daytona forwards everything the allowlist permits. Chained,
|
|
707
|
+
// not advisory: an unreachable gateway makes allowed traffic 502
|
|
708
|
+
// rather than quietly going direct.
|
|
709
|
+
outboundProxyUrl: proxyUrl,
|
|
710
|
+
ttlMinutes: rest.ttlMinutes ?? ttlMinutes
|
|
711
|
+
};
|
|
712
|
+
sandbox = await this.baseCreate(createParams, options);
|
|
713
|
+
} catch (cause) {
|
|
714
|
+
await cleanupTwin();
|
|
715
|
+
if (cause instanceof VerisError) throw cause;
|
|
716
|
+
throw new VerisError("Daytona sandbox create failed", {
|
|
717
|
+
phase: "sandbox-create",
|
|
718
|
+
verisSandboxId: twin.id,
|
|
719
|
+
cause
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
try {
|
|
723
|
+
await installCa(sandbox, credential.ca_pem);
|
|
724
|
+
await probeCanary(sandbox, credential.canary_host, twin.id);
|
|
725
|
+
} catch (err) {
|
|
726
|
+
await sandbox.delete().catch(() => {
|
|
727
|
+
});
|
|
728
|
+
await cleanupTwin();
|
|
729
|
+
throw err;
|
|
730
|
+
}
|
|
731
|
+
return this.attach(sandbox, {
|
|
732
|
+
controlPlane,
|
|
733
|
+
environmentId: twin.environment_id,
|
|
734
|
+
twinId: twin.id,
|
|
735
|
+
egress,
|
|
736
|
+
ownsTwin,
|
|
737
|
+
canaryHost: credential.canary_host
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Rehydrate the Veris surface on an existing sandbox.
|
|
742
|
+
*
|
|
743
|
+
* Not an optimisation — a necessity. The OpenCode plugin reconnects to a
|
|
744
|
+
* sandbox with get() on every resumed session and deletes through get() too,
|
|
745
|
+
* so a get() that returned a bare Sandbox would mean no receipts after any
|
|
746
|
+
* restart and a leaked twin on every delete.
|
|
747
|
+
*/
|
|
748
|
+
async get(sandboxIdOrName) {
|
|
749
|
+
const sandbox = await super.get(sandboxIdOrName);
|
|
750
|
+
return this.rehydrate(sandbox);
|
|
751
|
+
}
|
|
752
|
+
/** Same rehydration for the sandboxes a list() streams. */
|
|
753
|
+
list(query) {
|
|
754
|
+
const inner = super.list(query);
|
|
755
|
+
const rehydrate = (s) => this.rehydrate(s);
|
|
756
|
+
return (async function* () {
|
|
757
|
+
for await (const sandbox of inner) yield rehydrate(sandbox);
|
|
758
|
+
})();
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Attach the Veris surface to a sandbox whose labels say it has a twin.
|
|
762
|
+
* A sandbox without our labels is passed through untouched — callers can use
|
|
763
|
+
* this client for ordinary Daytona work.
|
|
764
|
+
*/
|
|
765
|
+
rehydrate(sandbox) {
|
|
766
|
+
const labels = sandbox.labels ?? {};
|
|
767
|
+
const twinId = labels[LABEL.twinId];
|
|
768
|
+
if (!twinId) return sandbox;
|
|
769
|
+
const apiKey = this.verisDefaults.apiKey ?? process.env.VERIS_API_KEY;
|
|
770
|
+
if (!apiKey) return sandbox;
|
|
771
|
+
const trustedBase = this.verisDefaults.apiBase ?? process.env.VERIS_API_BASE;
|
|
772
|
+
const labelBase = labels[LABEL.apiBase];
|
|
773
|
+
if (trustedBase && labelBase && labelBase !== trustedBase) {
|
|
774
|
+
throw new VerisError(
|
|
775
|
+
`sandbox ${sandbox.id} labels name a different Veris control plane (${labelBase}) than your configuration (${trustedBase}) \u2014 refusing to send the API key to an unverified host`,
|
|
776
|
+
{ phase: "attach" }
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
const apiBase = trustedBase ?? labelBase ?? "https://svc.api.veris.ai";
|
|
780
|
+
return this.attach(sandbox, {
|
|
781
|
+
controlPlane: new ControlPlane({ apiKey, apiBase, sdkVersion: SDK_VERSION }),
|
|
782
|
+
environmentId: labels[LABEL.envId] ?? "",
|
|
783
|
+
twinId,
|
|
784
|
+
// Re-minted below when a receipt is actually asked for; the label only
|
|
785
|
+
// has to survive the reconnect.
|
|
786
|
+
canaryHost: labels[LABEL.canaryHost] ?? "",
|
|
787
|
+
egress: labels[LABEL.egress] ?? "strict",
|
|
788
|
+
ownsTwin: labels[LABEL.ownsTwin] !== "false"
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
async provisionTwin(controlPlane, v, coords, ttlMinutes) {
|
|
792
|
+
if (v.attachSandboxId) {
|
|
793
|
+
const existing = await controlPlane.getTwin(v.attachSandboxId);
|
|
794
|
+
if (!existing) {
|
|
795
|
+
throw new VerisError(`attach target ${v.attachSandboxId} not found`, {
|
|
796
|
+
phase: "twin-provision",
|
|
797
|
+
verisSandboxId: v.attachSandboxId
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
return existing.status === "ready" ? existing : controlPlane.waitReady(v.attachSandboxId, 24e4);
|
|
801
|
+
}
|
|
802
|
+
if (!coords.environmentId) {
|
|
803
|
+
throw new MissingCredentialsError(
|
|
804
|
+
"no Veris environment: set VERIS_ENVIRONMENT_ID, or pass veris.environmentId",
|
|
805
|
+
{ phase: "credentials" }
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
const created = await controlPlane.createTwin(coords.environmentId, { ttlMinutes });
|
|
809
|
+
try {
|
|
810
|
+
return await controlPlane.waitReady(created.id, 24e4);
|
|
811
|
+
} catch (e) {
|
|
812
|
+
await controlPlane.deleteTwin(coords.environmentId, created.id).catch(() => {
|
|
813
|
+
});
|
|
814
|
+
throw e;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Hang the Veris surface off the instance, and wrap delete() so teardown is
|
|
819
|
+
* automatic.
|
|
820
|
+
*
|
|
821
|
+
* Wrapping rather than asking callers to remember is the point: the OpenCode
|
|
822
|
+
* plugin's existing `sandbox.delete()` then removes the twin too, with no
|
|
823
|
+
* change to the plugin at all. A twin outlives its sandbox otherwise, until
|
|
824
|
+
* its TTL reaps it — invisible until the bill arrives.
|
|
825
|
+
*/
|
|
826
|
+
attach(sandbox, ctx) {
|
|
827
|
+
if (isVerisSandbox(sandbox)) return sandbox;
|
|
828
|
+
const veris = new VerisApiImpl({ ...ctx, sandbox });
|
|
829
|
+
const originalDelete = sandbox.delete.bind(sandbox);
|
|
830
|
+
Object.defineProperties(sandbox, {
|
|
831
|
+
veris: { value: veris, enumerable: true, configurable: true },
|
|
832
|
+
verisSandboxId: { value: ctx.twinId, enumerable: true, configurable: true },
|
|
833
|
+
delete: {
|
|
834
|
+
configurable: true,
|
|
835
|
+
value: async (timeout, wait) => {
|
|
836
|
+
if (ctx.ownsTwin) {
|
|
837
|
+
await ctx.controlPlane.deleteTwin(ctx.environmentId, ctx.twinId).catch(() => {
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
return originalDelete(timeout, wait);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
return sandbox;
|
|
845
|
+
}
|
|
846
|
+
/** super.create through the overload the params actually match. */
|
|
847
|
+
baseCreate(params, options) {
|
|
848
|
+
return super.create.call(this, params, options);
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
var daytona_default = Daytona;
|
|
852
|
+
function resolveCoordinates(v) {
|
|
853
|
+
const apiKey = v.apiKey ?? process.env.VERIS_API_KEY;
|
|
854
|
+
if (!apiKey) {
|
|
855
|
+
throw new MissingCredentialsError(
|
|
856
|
+
"no Veris API key: set VERIS_API_KEY in your environment, or pass veris.apiKey. Get one at https://studio.veris.ai",
|
|
857
|
+
{ phase: "credentials" }
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
return {
|
|
861
|
+
apiKey,
|
|
862
|
+
environmentId: v.environmentId ?? process.env.VERIS_ENVIRONMENT_ID,
|
|
863
|
+
apiBase: (v.apiBase ?? process.env.VERIS_API_BASE ?? "https://svc.api.veris.ai").replace(/\/$/, "")
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function stripVeris(params) {
|
|
867
|
+
const { veris: _veris, ...rest } = params ?? {};
|
|
868
|
+
return rest;
|
|
869
|
+
}
|
|
870
|
+
function reserveLabels(labels) {
|
|
871
|
+
const out = {};
|
|
872
|
+
for (const [k, val] of Object.entries(labels ?? {})) {
|
|
873
|
+
if (!VERIS_LABEL_KEYS.includes(k)) out[k] = val;
|
|
874
|
+
}
|
|
875
|
+
return out;
|
|
876
|
+
}
|
|
877
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
878
|
+
0 && (module.exports = {
|
|
879
|
+
CA_CERT_PATH,
|
|
880
|
+
ControlPlane,
|
|
881
|
+
DEFAULT_REGISTRY_HOSTS,
|
|
882
|
+
Daytona,
|
|
883
|
+
MissingCredentialsError,
|
|
884
|
+
ReceiptIntegrityError,
|
|
885
|
+
SDK_VERSION,
|
|
886
|
+
SYSTEM_BUNDLE,
|
|
887
|
+
SnapshotUnsupportedError,
|
|
888
|
+
TwinExpiredError,
|
|
889
|
+
UnsupportedOperationError,
|
|
890
|
+
VERIS_BUNDLE,
|
|
891
|
+
VERIS_CA_FILE,
|
|
892
|
+
VerisError,
|
|
893
|
+
VerisGatewayNotOfferedError,
|
|
894
|
+
VerisGatewayUnreachableError,
|
|
895
|
+
VerisUntouchedError,
|
|
896
|
+
dataPlaneHosts,
|
|
897
|
+
gatewayProxyUrl,
|
|
898
|
+
isVerisSandbox,
|
|
899
|
+
twinHosts,
|
|
900
|
+
vendorHosts,
|
|
901
|
+
vendoredTrustEnv,
|
|
902
|
+
...require("@daytona/sdk")
|
|
903
|
+
});
|
|
904
|
+
//# sourceMappingURL=index.cjs.map
|