@questpie/sandbox 3.15.2 → 3.17.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 +203 -18
- package/dist/adapter-http-Coy4jqsj.mjs +865 -0
- package/dist/adapter-http-DDeL-Ym3.d.mts +259 -0
- package/dist/adapter.d.mts +3 -2
- package/dist/adapter.mjs +3 -2
- package/dist/custom-tools-CgfgyVNv.d.mts +55 -0
- package/dist/custom-tools-DxI7JvUn.mjs +407 -0
- package/dist/index.d.mts +40 -115
- package/dist/index.mjs +11 -14
- package/dist/modules/sandbox.d.mts +40 -0
- package/dist/modules/sandbox.mjs +211 -0
- package/package.json +20 -5
- package/src/broker-wire.ts +94 -0
- package/src/egress-firewall.ts +9 -3
- package/src/guest-bindings.ts +43 -1
- package/src/guest-entry.ts +132 -22
- package/src/guest-runtime-source.ts +38 -0
- package/src/net-validation.ts +204 -51
- package/src/sandbox-server.ts +860 -144
- package/src/server-internals.ts +195 -12
- package/src/types.ts +38 -10
- package/src/workload-admission.ts +289 -0
- package/src/workload.ts +441 -0
- package/dist/adapter-http-7ypWHBck.d.mts +0 -43
- package/dist/adapter-http-w7SjAfcC.mjs +0 -298
package/src/workload.ts
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import type { SandboxCustomToolsRunOptions } from "./custom-tools.js";
|
|
2
|
+
import type {
|
|
3
|
+
SandboxBindings,
|
|
4
|
+
SandboxCapabilities,
|
|
5
|
+
SandboxRunRequest,
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
|
|
8
|
+
export const SANDBOX_WORKLOAD_DENIAL_MESSAGE =
|
|
9
|
+
"The workload is not authorized for this sandbox operation.";
|
|
10
|
+
|
|
11
|
+
export class SandboxWorkloadDeniedError extends Error {
|
|
12
|
+
readonly code = "sandbox_authority_denied" as const;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super(SANDBOX_WORKLOAD_DENIAL_MESSAGE);
|
|
16
|
+
this.name = "SandboxWorkloadDeniedError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SandboxWorkloadPolicy {
|
|
21
|
+
readonly source: string;
|
|
22
|
+
readonly input?: unknown;
|
|
23
|
+
readonly capabilities: SandboxCapabilities;
|
|
24
|
+
readonly secrets?: Readonly<Record<string, string>>;
|
|
25
|
+
readonly bindings?: SandboxBindings;
|
|
26
|
+
/** Host-only custom-tool authority. Never serialized to the supervisor. */
|
|
27
|
+
readonly sandboxTools?: SandboxCustomToolsRunOptions;
|
|
28
|
+
/** Optional consumer deadline; transport admission is additionally capped at 5s. */
|
|
29
|
+
readonly validUntil?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type SandboxWorkloadAuthorizationPhase = "prepare" | "dispatch";
|
|
33
|
+
|
|
34
|
+
export interface SandboxWorkloadAuthorizationContext {
|
|
35
|
+
readonly phase: SandboxWorkloadAuthorizationPhase;
|
|
36
|
+
readonly signal: AbortSignal;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type SandboxWorkloadAuthorizer = (
|
|
40
|
+
envelope: unknown,
|
|
41
|
+
context: SandboxWorkloadAuthorizationContext,
|
|
42
|
+
) => SandboxWorkloadPolicy | null | Promise<SandboxWorkloadPolicy | null>;
|
|
43
|
+
|
|
44
|
+
export interface SandboxWorkloadAuditEvent {
|
|
45
|
+
readonly boundary: "sandbox.workload";
|
|
46
|
+
readonly phase: SandboxWorkloadAuthorizationPhase | "transport";
|
|
47
|
+
readonly decision: "allowed" | "denied";
|
|
48
|
+
readonly reason:
|
|
49
|
+
| "authorization_failed"
|
|
50
|
+
| "authorization_timed_out"
|
|
51
|
+
| "cancelled"
|
|
52
|
+
| "egress_denied"
|
|
53
|
+
| "policy_changed"
|
|
54
|
+
| "policy_invalid"
|
|
55
|
+
| "authorization_succeeded"
|
|
56
|
+
| "transport_completed"
|
|
57
|
+
| "transport_denied"
|
|
58
|
+
| "transport_cancelled"
|
|
59
|
+
| "transport_timed_out"
|
|
60
|
+
| "transport_invalid_response"
|
|
61
|
+
| "transport_failed";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface SandboxWorkloadAuditContext {
|
|
65
|
+
readonly signal: AbortSignal;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SandboxWorkloadRunOptions {
|
|
69
|
+
/** Consumer-owned opaque value. QUESTPIE never parses or persists it. */
|
|
70
|
+
readonly envelope: unknown;
|
|
71
|
+
readonly signal?: AbortSignal;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const MAX_SOURCE_BYTES = 1_048_576;
|
|
75
|
+
const MAX_NAMED_VALUES = 64;
|
|
76
|
+
const MAX_HOSTS_PER_AXIS = 32;
|
|
77
|
+
const MAX_TOTAL_HOSTS = 32;
|
|
78
|
+
const MAX_SECRET_VALUE_BYTES = 65_536;
|
|
79
|
+
const MAX_TOTAL_SECRET_BYTES = 262_144;
|
|
80
|
+
const MAX_INPUT_DEPTH = 32;
|
|
81
|
+
const MAX_INPUT_NODES = 10_000;
|
|
82
|
+
const MAX_INPUT_BYTES = 1_048_576;
|
|
83
|
+
const MAX_BINDINGS_URL_BYTES = 2_048;
|
|
84
|
+
const MAX_BINDINGS_TOKEN_BYTES = 8_192;
|
|
85
|
+
const utf8Encoder = new TextEncoder();
|
|
86
|
+
|
|
87
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
88
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Copy enumerable data descriptors without invoking getters. Proxy traps and
|
|
93
|
+
* exotic prototypes are rejected by the caller's fail-closed catch.
|
|
94
|
+
*/
|
|
95
|
+
function snapshotDataRecord(value: unknown): Record<string, unknown> | null {
|
|
96
|
+
if (!isRecord(value)) return null;
|
|
97
|
+
const prototype = Object.getPrototypeOf(value);
|
|
98
|
+
if (prototype !== Object.prototype && prototype !== null) return null;
|
|
99
|
+
const result: Record<string, unknown> = Object.create(null);
|
|
100
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
101
|
+
if (typeof key !== "string") return null;
|
|
102
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
103
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
result[key] = descriptor.value;
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function exactOrOptionalKeys(
|
|
112
|
+
value: Record<string, unknown>,
|
|
113
|
+
required: readonly string[],
|
|
114
|
+
optional: readonly string[],
|
|
115
|
+
): boolean {
|
|
116
|
+
const keys = Object.keys(value);
|
|
117
|
+
return (
|
|
118
|
+
required.every((key) => keys.includes(key)) &&
|
|
119
|
+
keys.every((key) => required.includes(key) || optional.includes(key))
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeHosts(value: unknown): readonly string[] | null {
|
|
124
|
+
if (!Array.isArray(value) || value.length > MAX_HOSTS_PER_AXIS) return null;
|
|
125
|
+
const hosts: string[] = [];
|
|
126
|
+
for (const host of value) {
|
|
127
|
+
if (
|
|
128
|
+
typeof host !== "string" ||
|
|
129
|
+
host.length === 0 ||
|
|
130
|
+
host.length > 253 ||
|
|
131
|
+
host.includes("*") ||
|
|
132
|
+
host.includes("/") ||
|
|
133
|
+
/\s/.test(host)
|
|
134
|
+
) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
hosts.push(host);
|
|
138
|
+
}
|
|
139
|
+
return Object.freeze(hosts);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeCapabilities(value: unknown): SandboxCapabilities | null {
|
|
143
|
+
const record = snapshotDataRecord(value);
|
|
144
|
+
if (
|
|
145
|
+
!record ||
|
|
146
|
+
!exactOrOptionalKeys(record, ["net", "import", "timeoutMs", "memoryMb"], [])
|
|
147
|
+
) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const net = normalizeHosts(record.net);
|
|
151
|
+
const imports = normalizeHosts(record.import);
|
|
152
|
+
if (
|
|
153
|
+
!net ||
|
|
154
|
+
!imports ||
|
|
155
|
+
net.length + imports.length > MAX_TOTAL_HOSTS ||
|
|
156
|
+
typeof record.timeoutMs !== "number" ||
|
|
157
|
+
!Number.isInteger(record.timeoutMs) ||
|
|
158
|
+
record.timeoutMs < 1 ||
|
|
159
|
+
record.timeoutMs > 30_000 ||
|
|
160
|
+
typeof record.memoryMb !== "number" ||
|
|
161
|
+
!Number.isInteger(record.memoryMb) ||
|
|
162
|
+
record.memoryMb < 16 ||
|
|
163
|
+
record.memoryMb > 1_024
|
|
164
|
+
) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
return Object.freeze({
|
|
168
|
+
net: [...net],
|
|
169
|
+
import: [...imports],
|
|
170
|
+
timeoutMs: record.timeoutMs,
|
|
171
|
+
memoryMb: record.memoryMb,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizeSecrets(
|
|
176
|
+
value: unknown,
|
|
177
|
+
): Readonly<Record<string, string>> | null {
|
|
178
|
+
const record = snapshotDataRecord(value);
|
|
179
|
+
if (!record || Object.keys(record).length > MAX_NAMED_VALUES) return null;
|
|
180
|
+
const result: Record<string, string> = Object.create(null);
|
|
181
|
+
let totalBytes = 0;
|
|
182
|
+
for (const [name, secret] of Object.entries(record)) {
|
|
183
|
+
const nameBytes = utf8Encoder.encode(name).byteLength;
|
|
184
|
+
const secretBytes =
|
|
185
|
+
typeof secret === "string"
|
|
186
|
+
? utf8Encoder.encode(secret).byteLength
|
|
187
|
+
: Number.POSITIVE_INFINITY;
|
|
188
|
+
totalBytes += nameBytes + secretBytes;
|
|
189
|
+
if (
|
|
190
|
+
!/^[A-Za-z0-9_.-]+$/.test(name) ||
|
|
191
|
+
typeof secret !== "string" ||
|
|
192
|
+
secretBytes > MAX_SECRET_VALUE_BYTES ||
|
|
193
|
+
totalBytes > MAX_TOTAL_SECRET_BYTES
|
|
194
|
+
) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
result[name] = secret;
|
|
198
|
+
}
|
|
199
|
+
return Object.freeze(result);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function normalizeBindings(value: unknown): SandboxBindings | null {
|
|
203
|
+
const record = snapshotDataRecord(value);
|
|
204
|
+
if (
|
|
205
|
+
!record ||
|
|
206
|
+
!exactOrOptionalKeys(record, ["url", "token"], []) ||
|
|
207
|
+
typeof record.url !== "string" ||
|
|
208
|
+
record.url.length === 0 ||
|
|
209
|
+
utf8Encoder.encode(record.url).byteLength > MAX_BINDINGS_URL_BYTES ||
|
|
210
|
+
typeof record.token !== "string" ||
|
|
211
|
+
record.token.length === 0 ||
|
|
212
|
+
utf8Encoder.encode(record.token).byteLength > MAX_BINDINGS_TOKEN_BYTES
|
|
213
|
+
) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (!URL.canParse(record.url)) return null;
|
|
217
|
+
return Object.freeze({ url: record.url, token: record.token });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
interface JsonSnapshotBudget {
|
|
221
|
+
nodes: number;
|
|
222
|
+
bytes: number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function consumeJsonBudget(budget: JsonSnapshotBudget, bytes: number): boolean {
|
|
226
|
+
budget.nodes += 1;
|
|
227
|
+
budget.bytes += bytes;
|
|
228
|
+
return budget.nodes <= MAX_INPUT_NODES && budget.bytes <= MAX_INPUT_BYTES;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function snapshotJson(
|
|
232
|
+
value: unknown,
|
|
233
|
+
seen = new Set<object>(),
|
|
234
|
+
budget: JsonSnapshotBudget = { nodes: 0, bytes: 0 },
|
|
235
|
+
depth = 0,
|
|
236
|
+
): { ok: true; value: unknown } | { ok: false } {
|
|
237
|
+
if (depth > MAX_INPUT_DEPTH) return { ok: false };
|
|
238
|
+
if (
|
|
239
|
+
value === null ||
|
|
240
|
+
typeof value === "string" ||
|
|
241
|
+
typeof value === "boolean"
|
|
242
|
+
) {
|
|
243
|
+
const bytes =
|
|
244
|
+
typeof value === "string" ? utf8Encoder.encode(value).byteLength : 5;
|
|
245
|
+
return consumeJsonBudget(budget, bytes)
|
|
246
|
+
? { ok: true, value }
|
|
247
|
+
: { ok: false };
|
|
248
|
+
}
|
|
249
|
+
if (typeof value === "number") {
|
|
250
|
+
return Number.isFinite(value) && consumeJsonBudget(budget, 24)
|
|
251
|
+
? { ok: true, value }
|
|
252
|
+
: { ok: false };
|
|
253
|
+
}
|
|
254
|
+
if (typeof value !== "object" || seen.has(value)) return { ok: false };
|
|
255
|
+
if (!consumeJsonBudget(budget, 2)) return { ok: false };
|
|
256
|
+
seen.add(value);
|
|
257
|
+
|
|
258
|
+
if (Array.isArray(value)) {
|
|
259
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
260
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
261
|
+
const arrayLength =
|
|
262
|
+
lengthDescriptor && "value" in lengthDescriptor
|
|
263
|
+
? lengthDescriptor.value
|
|
264
|
+
: null;
|
|
265
|
+
if (
|
|
266
|
+
typeof arrayLength !== "number" ||
|
|
267
|
+
!Number.isInteger(arrayLength) ||
|
|
268
|
+
arrayLength < 0 ||
|
|
269
|
+
arrayLength > MAX_INPUT_NODES ||
|
|
270
|
+
Object.keys(descriptors).some(
|
|
271
|
+
(key) => key !== "length" && !/^(0|[1-9]\d*)$/.test(key),
|
|
272
|
+
)
|
|
273
|
+
) {
|
|
274
|
+
return { ok: false };
|
|
275
|
+
}
|
|
276
|
+
const result: unknown[] = [];
|
|
277
|
+
for (let index = 0; index < arrayLength; index += 1) {
|
|
278
|
+
const descriptor = descriptors[String(index)];
|
|
279
|
+
if (!descriptor) {
|
|
280
|
+
if (!consumeJsonBudget(budget, 4)) return { ok: false };
|
|
281
|
+
result.push(null);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
285
|
+
return { ok: false };
|
|
286
|
+
}
|
|
287
|
+
const item = snapshotJson(descriptor.value, seen, budget, depth + 1);
|
|
288
|
+
if (!item.ok) return item;
|
|
289
|
+
result.push(item.value);
|
|
290
|
+
}
|
|
291
|
+
seen.delete(value);
|
|
292
|
+
return { ok: true, value: Object.freeze(result) };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const record = snapshotDataRecord(value);
|
|
296
|
+
if (!record) return { ok: false };
|
|
297
|
+
const result: Record<string, unknown> = Object.create(null);
|
|
298
|
+
for (const [key, itemValue] of Object.entries(record)) {
|
|
299
|
+
if (!consumeJsonBudget(budget, utf8Encoder.encode(key).byteLength)) {
|
|
300
|
+
return { ok: false };
|
|
301
|
+
}
|
|
302
|
+
const item = snapshotJson(itemValue, seen, budget, depth + 1);
|
|
303
|
+
if (!item.ok) return item;
|
|
304
|
+
result[key] = item.value;
|
|
305
|
+
}
|
|
306
|
+
seen.delete(value);
|
|
307
|
+
return { ok: true, value: Object.freeze(result) };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function snapshotSandboxWorkloadPolicy(
|
|
311
|
+
value: unknown,
|
|
312
|
+
now = new Date(),
|
|
313
|
+
): SandboxWorkloadPolicy | null {
|
|
314
|
+
try {
|
|
315
|
+
const policy = snapshotDataRecord(value);
|
|
316
|
+
if (
|
|
317
|
+
!policy ||
|
|
318
|
+
!exactOrOptionalKeys(
|
|
319
|
+
policy,
|
|
320
|
+
["source", "capabilities"],
|
|
321
|
+
["input", "secrets", "bindings", "sandboxTools", "validUntil"],
|
|
322
|
+
) ||
|
|
323
|
+
typeof policy.source !== "string" ||
|
|
324
|
+
policy.source.length === 0 ||
|
|
325
|
+
new TextEncoder().encode(policy.source).byteLength > MAX_SOURCE_BYTES
|
|
326
|
+
) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
const capabilities = normalizeCapabilities(policy.capabilities);
|
|
330
|
+
const secrets = normalizeSecrets(policy.secrets ?? {});
|
|
331
|
+
const bindings =
|
|
332
|
+
policy.bindings === undefined
|
|
333
|
+
? undefined
|
|
334
|
+
: normalizeBindings(policy.bindings);
|
|
335
|
+
const sandboxTools =
|
|
336
|
+
policy.sandboxTools === undefined
|
|
337
|
+
? undefined
|
|
338
|
+
: snapshotDataRecord(policy.sandboxTools);
|
|
339
|
+
if (
|
|
340
|
+
!capabilities ||
|
|
341
|
+
!secrets ||
|
|
342
|
+
(policy.bindings !== undefined && !bindings) ||
|
|
343
|
+
(policy.sandboxTools !== undefined &&
|
|
344
|
+
(!sandboxTools ||
|
|
345
|
+
!exactOrOptionalKeys(sandboxTools, ["envelope"], []) ||
|
|
346
|
+
sandboxTools.envelope === undefined ||
|
|
347
|
+
!bindings))
|
|
348
|
+
) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
if (
|
|
352
|
+
policy.validUntil !== undefined &&
|
|
353
|
+
(typeof policy.validUntil !== "string" ||
|
|
354
|
+
!Number.isFinite(Date.parse(policy.validUntil)) ||
|
|
355
|
+
Date.parse(policy.validUntil) <= now.getTime())
|
|
356
|
+
) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
const input = snapshotJson(policy.input ?? null);
|
|
360
|
+
if (!input.ok) return null;
|
|
361
|
+
return Object.freeze({
|
|
362
|
+
source: policy.source,
|
|
363
|
+
input: input.value,
|
|
364
|
+
capabilities,
|
|
365
|
+
secrets,
|
|
366
|
+
...(bindings ? { bindings } : {}),
|
|
367
|
+
...(sandboxTools
|
|
368
|
+
? {
|
|
369
|
+
sandboxTools: Object.freeze({
|
|
370
|
+
envelope: sandboxTools.envelope,
|
|
371
|
+
}),
|
|
372
|
+
}
|
|
373
|
+
: {}),
|
|
374
|
+
...(typeof policy.validUntil === "string"
|
|
375
|
+
? { validUntil: policy.validUntil }
|
|
376
|
+
: {}),
|
|
377
|
+
});
|
|
378
|
+
} catch {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function serializeSandboxWorkloadPolicy(
|
|
384
|
+
policy: SandboxWorkloadPolicy,
|
|
385
|
+
): string {
|
|
386
|
+
return JSON.stringify({
|
|
387
|
+
source: policy.source,
|
|
388
|
+
input: policy.input ?? null,
|
|
389
|
+
capabilities: policy.capabilities,
|
|
390
|
+
secrets: policy.secrets ?? {},
|
|
391
|
+
...(policy.bindings ? { bindings: policy.bindings } : {}),
|
|
392
|
+
...(policy.validUntil ? { validUntil: policy.validUntil } : {}),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function parseSandboxRunRequest(
|
|
397
|
+
value: unknown,
|
|
398
|
+
): SandboxRunRequest | null {
|
|
399
|
+
try {
|
|
400
|
+
const request = snapshotDataRecord(value);
|
|
401
|
+
if (
|
|
402
|
+
!request ||
|
|
403
|
+
!exactOrOptionalKeys(
|
|
404
|
+
request,
|
|
405
|
+
["mode", "source", "input", "capabilities"],
|
|
406
|
+
["secrets", "bindings"],
|
|
407
|
+
) ||
|
|
408
|
+
(request.mode !== "host" && request.mode !== "workload") ||
|
|
409
|
+
typeof request.source !== "string" ||
|
|
410
|
+
request.source.length === 0 ||
|
|
411
|
+
utf8Encoder.encode(request.source).byteLength > MAX_SOURCE_BYTES
|
|
412
|
+
) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
const input = snapshotJson(request.input);
|
|
416
|
+
const capabilities = normalizeCapabilities(request.capabilities);
|
|
417
|
+
const secrets = normalizeSecrets(request.secrets ?? {});
|
|
418
|
+
const bindings =
|
|
419
|
+
request.bindings === undefined
|
|
420
|
+
? undefined
|
|
421
|
+
: normalizeBindings(request.bindings);
|
|
422
|
+
if (
|
|
423
|
+
!input.ok ||
|
|
424
|
+
!capabilities ||
|
|
425
|
+
!secrets ||
|
|
426
|
+
(request.bindings !== undefined && !bindings)
|
|
427
|
+
) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
return Object.freeze({
|
|
431
|
+
mode: request.mode,
|
|
432
|
+
source: request.source,
|
|
433
|
+
input: input.value,
|
|
434
|
+
capabilities,
|
|
435
|
+
secrets,
|
|
436
|
+
...(bindings ? { bindings } : {}),
|
|
437
|
+
});
|
|
438
|
+
} catch {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { ExecutorAdapter, ExecutorRunOptions, ExecutorRunResult } from "questpie/executor";
|
|
2
|
-
|
|
3
|
-
//#region src/adapter-http.d.ts
|
|
4
|
-
interface HttpSandboxAdapterOptions {
|
|
5
|
-
/**
|
|
6
|
-
* Base URL of the standalone Deno sandbox service (`sandbox-server.ts`).
|
|
7
|
-
* Defaults to `process.env.SANDBOX_URL`. Throws on `run` if neither is set.
|
|
8
|
-
*/
|
|
9
|
-
url?: string;
|
|
10
|
-
/**
|
|
11
|
-
* Per-request HTTP fetch timeout (ms). A safety margin above the guest
|
|
12
|
-
* wall-timeout so the server's own kill fires first; if the server is
|
|
13
|
-
* unreachable this bounds the client wait. Default: guest timeout + 10s.
|
|
14
|
-
*/
|
|
15
|
-
fetchTimeoutMs?: number;
|
|
16
|
-
/** Optional custom `fetch` (e.g. for auth headers / mTLS). */
|
|
17
|
-
fetch?: typeof fetch;
|
|
18
|
-
/**
|
|
19
|
-
* Validate net/import hosts against the private-IP policy BEFORE sending to
|
|
20
|
-
* the sandbox (defense-in-depth; the server validates too). Default: true.
|
|
21
|
-
*/
|
|
22
|
-
validateEgress?: boolean;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Production sandboxed `ExecutorAdapter` — POSTs the run to the standalone Deno
|
|
26
|
-
* sandbox service over HTTP. The main app (Bun/Node) holds NO Deno; it only
|
|
27
|
-
* speaks this JSON contract.
|
|
28
|
-
*
|
|
29
|
-
* Use as the `sandboxed` adapter in the executor config:
|
|
30
|
-
* executor: { sandboxed: httpSandboxAdapter({ url: process.env.SANDBOX_URL }) }
|
|
31
|
-
*/
|
|
32
|
-
declare class HttpSandboxAdapter implements ExecutorAdapter {
|
|
33
|
-
private readonly options;
|
|
34
|
-
private readonly fetchImpl;
|
|
35
|
-
private readonly validateEgress;
|
|
36
|
-
constructor(options?: HttpSandboxAdapterOptions);
|
|
37
|
-
private resolveUrl;
|
|
38
|
-
run(options: ExecutorRunOptions): Promise<ExecutorRunResult>;
|
|
39
|
-
}
|
|
40
|
-
/** Factory for the production sandboxed adapter. */
|
|
41
|
-
declare function httpSandboxAdapter(options?: HttpSandboxAdapterOptions): HttpSandboxAdapter;
|
|
42
|
-
//#endregion
|
|
43
|
-
export { HttpSandboxAdapterOptions as n, httpSandboxAdapter as r, HttpSandboxAdapter as t };
|