@questpie/sandbox 3.16.0 → 3.18.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 CHANGED
@@ -1,15 +1,18 @@
1
1
  # @questpie/sandbox
2
2
 
3
- Hardened sandboxed code execution for QUESTPIE executor workloads. The package ships the HTTP adapter used by a QUESTPIE app and the Deno supervisor source that executes untrusted code in a separate process with capability-scoped network, import, file, and app bindings.
3
+ Hardened isolated-script execution for QUESTPIE. The package ships the HTTP
4
+ adapter used by a QUESTPIE app and the Deno supervisor source that executes each
5
+ untrusted script in a fresh, capability-scoped subprocess.
4
6
 
5
7
  ## Features
6
8
 
7
- - **HTTP executor adapter** - connect `ctx.executor.run()` to a standalone sandbox service.
8
- - **Deno supervisor** - run guest code outside the main Bun/Node app process.
9
- - **Capability-scoped bindings** - expose only declared collections, stores, files, and fetch access.
10
- - **SSRF protection** - validate literal IPs, DNS rebinding, redirects, and private/metadata ranges.
11
- - **Network isolation support** - Linux network namespace/firewall planning for defense in depth.
12
- - **Structured failures** - timeouts, non-JSON responses, and network failures return predictable results.
9
+ - HTTP `ExecutorAdapter` for `ctx.executor.run()`
10
+ - fresh Deno subprocess with bounded wall time and memory
11
+ - capability-scoped collections, stores, files, and network bindings
12
+ - explicitly released custom MCP tools through a separate guest namespace
13
+ - SSRF, redirect, private-address, and metadata-address protection
14
+ - optional Linux network namespace/firewall defense in depth
15
+ - generic, consumer-supplied workload authorization
13
16
 
14
17
  ## Installation
15
18
 
@@ -17,10 +20,9 @@ Hardened sandboxed code execution for QUESTPIE executor workloads. The package s
17
20
  bun add @questpie/sandbox
18
21
  ```
19
22
 
20
- ## Configure The Adapter
23
+ ## Trusted framework execution
21
24
 
22
25
  ```ts
23
- // config/app.ts
24
26
  import { httpSandboxAdapter } from "@questpie/sandbox/adapter";
25
27
  import { appConfig } from "questpie";
26
28
 
@@ -28,30 +30,213 @@ export default appConfig({
28
30
  executor: {
29
31
  sandboxed: httpSandboxAdapter({
30
32
  url: process.env.SANDBOX_URL,
33
+ hostAdmissionSecret: process.env.SANDBOX_HOST_ADMISSION_SECRET,
31
34
  }),
32
35
  },
33
36
  });
34
37
  ```
35
38
 
36
- Guest bindings require a host-side broker route in the app or control-plane layer. Compute-only sandboxed execution only needs the adapter above.
39
+ The adapter and supervisor must share the same random
40
+ `SANDBOX_HOST_ADMISSION_SECRET` of at least 32 bytes. Omitting the credential
41
+ fails closed. `SANDBOX_URL` must be a canonical HTTP(S) origin with no userinfo,
42
+ fragment, query, or path prefix. The adapter never follows `/run` redirects, so
43
+ the host admission credential cannot cross origins. Broker coordinates are still minted by QUESTPIE's core executor
44
+ and remain capability-scoped. Any execution with bindings also requires the
45
+ supervisor's canonical `SANDBOX_BROKER_URL`; missing, malformed, or non-matching
46
+ protocol, host, effective port, path, or query is denied before spawn. Userinfo
47
+ and fragments are forbidden; only one trailing path slash is treated as
48
+ equivalent. The supervisor fetches the validated canonical URL, never the raw
49
+ request spelling.
50
+
51
+ Bindings guests always run with `--allow-net=[]`. Their declared `net`
52
+ allowlist remains host-side and is used only by the resolve/validate/pin HTTP
53
+ broker. The backwards-compatible compute-only path may still receive a direct
54
+ Deno network grant.
55
+
56
+ ## Generic workload authorization
57
+
58
+ A remote consumer supplies its own opaque envelope and authorizer. QUESTPIE does
59
+ not prescribe, inspect, or persist the envelope. The authorizer returns the
60
+ complete sandbox policy for one execution; no source, input, process capability,
61
+ secret, or broker binding can be supplied beside the envelope.
37
62
 
38
- ## Run The Supervisor
63
+ ```ts
64
+ import { httpSandboxAdapter } from "@questpie/sandbox";
65
+
66
+ const adapter = httpSandboxAdapter({
67
+ url: process.env.SANDBOX_URL,
68
+ workload: {
69
+ admission: {
70
+ keyId: process.env.SANDBOX_WORKLOAD_ADMISSION_KEY_ID!,
71
+ secret: new TextEncoder().encode(
72
+ process.env.SANDBOX_WORKLOAD_ADMISSION_SECRET!,
73
+ ),
74
+ instanceId: process.env.SANDBOX_INSTANCE_ID!,
75
+ },
76
+ authorize: async (opaqueEnvelope, { phase, signal }) => {
77
+ const authorization = await consumerAuthority.authorize(opaqueEnvelope, {
78
+ phase,
79
+ signal,
80
+ });
81
+ if (!authorization) return null;
82
+
83
+ return {
84
+ source: authorization.source,
85
+ input: authorization.input,
86
+ capabilities: {
87
+ net: authorization.networkHosts,
88
+ import: authorization.importHosts,
89
+ timeoutMs: 5_000,
90
+ memoryMb: 128,
91
+ },
92
+ secrets: {},
93
+ validUntil: authorization.validUntil,
94
+ };
95
+ },
96
+ audit: writeRedactedSandboxDecision,
97
+ },
98
+ });
99
+
100
+ const result = await adapter.runWorkload({ envelope: consumerEnvelope });
101
+ ```
102
+
103
+ The authorizer is called at preparation and immediately before dispatch. Both
104
+ normalized policies must be identical. Authorization and audit callbacks have
105
+ hard deadlines, caller cancellation is propagated, and malformed/missing
106
+ authorization fails closed. Policy input, secrets, bindings, source, combined
107
+ egress hosts, wall time, memory, and the final request body all have hard bounds.
108
+ Cancellation after dispatch aborts broker relays and terminates the guest with a
109
+ bounded `SIGTERM`/`SIGKILL` sequence.
110
+
111
+ The subprocess boundary uses one 5 MiB decoded HTTP body budget. Its
112
+ method-aware request, response, frame, and cumulative-output limits are derived
113
+ from the exact base64 expansion plus bounded JSON-envelope headroom. Native
114
+ binding results and custom-tool results remain capped at 768 KiB. Stderr is
115
+ independently capped at 256 KiB, binding calls at 256 per run, and concurrent
116
+ broker relays at 16. Crossing a limit kills the guest and aborts outstanding
117
+ relays. Broker and supervisor HTTP responses are streamed through byte caps and
118
+ parsed against exact schemas; broker-provided error text is replaced by
119
+ supervisor-owned stable messages before it can enter the guest. Broker and
120
+ `/run` redirects are rejected. Any broker denial or malformed response latches
121
+ a terminal run failure; guest `catch` code cannot turn it back into success. A
122
+ valid result frame is terminal immediately, and a result racing an in-flight
123
+ RPC is rejected.
124
+
125
+ The adapter then signs a minimal, product-neutral transport admission. It is
126
+ bound to the exact request body and one supervisor instance, expires within five
127
+ seconds, and is consumed once. The supervisor rejects missing, forged, expired,
128
+ replayed, wrong-instance, and body-mismatched admissions before spawning a
129
+ subprocess. Consumer audit events distinguish authorization decisions from the
130
+ terminal transport outcome and include only decision plus phase/reason. The
131
+ supervisor's transport log additionally includes its own instance id. Neither
132
+ surface contains the opaque envelope, source, inputs, secrets, or broker token.
133
+
134
+ `SANDBOX_INSTANCE_ID` is a process incarnation, not a deployment or replica id.
135
+ It must be newly generated for every supervisor process and restart. The
136
+ adapter's `SANDBOX_URL` must route directly to the one process named by its
137
+ configured instance id; do not put this admission path behind an unpinned
138
+ round-robin load balancer. Reusing one instance id across replicas is forbidden.
139
+ Replay state is deliberately process-local because the admission is
140
+ cryptographically bound to that unique process incarnation.
141
+
142
+ ## Custom tools
143
+
144
+ Custom `mcpTool(...)` definitions can be exposed to a sandbox without turning
145
+ MCP CRUD into the sandbox data API. Collections, files, and stores keep using
146
+ their native broker primitives; only explicitly released custom tools enter the
147
+ separate `questpie.tools` namespace.
148
+
149
+ Configure the host-side consumer authorization port in `questpie.config.ts`:
150
+
151
+ ```ts
152
+ import { sandboxCustomTools } from "@questpie/sandbox";
153
+ import { runtimeConfig } from "questpie/app";
154
+
155
+ export default runtimeConfig({
156
+ // db, executor, …
157
+ sandboxCustomTools: sandboxCustomTools({
158
+ authorizer: consumerToolAuthority,
159
+ contextBinder: consumerQuestpieContextBinder,
160
+ evidence: async (event) => {
161
+ // Product-neutral: phase, tool name, result, reason, duration only.
162
+ await evidenceSink.write(event);
163
+ },
164
+ }),
165
+ });
166
+ ```
167
+
168
+ The tool itself must be in the released MCP custom-tool catalog and declare an
169
+ explicit workload requirement. Omitted exposure or workload policy remains
170
+ invisible and uncallable. Every call repeats consumer authorization, binds a
171
+ fresh user-mode QUESTPIE context, rechecks the tool access/scope policy, parses
172
+ the original input schema, and invokes the original handler.
173
+
174
+ The host supplies only an opaque envelope for the run:
175
+
176
+ ```ts
177
+ const result = await ctx.executor.run({
178
+ isolation: "sandboxed",
179
+ source: `
180
+ export default async () => {
181
+ const { tools } = await globalThis.questpie.tools.list();
182
+ return globalThis.questpie.tools.call("reports.generate", {
183
+ period: "week",
184
+ });
185
+ }
186
+ `,
187
+ brokerUrl: "https://app.internal/api/sandbox/rpc",
188
+ sandboxTools: { envelope: consumerEnvelope },
189
+ capabilities: {
190
+ net: [],
191
+ import: [],
192
+ timeoutMs: 5_000,
193
+ memoryMb: 128,
194
+ },
195
+ });
196
+ ```
197
+
198
+ The adapter keeps the envelope in the app host. The broker endpoint and token
199
+ are sent only to the trusted supervisor through the existing bindings
200
+ coordinate; none of them is exposed to guest code or `globalThis.questpie`.
201
+ Discovery, arguments, results, operation count, wall time, and concurrency are
202
+ bounded. Evidence callbacks receive no envelope, arguments, result body,
203
+ bearer credential, app instance, database handle, or request context.
204
+
205
+ Defaults/hard maxima are: 64/256 discovered tools, 256 KiB/768 KiB discovery
206
+ payload, 32 KiB/63 KiB arguments, 256 KiB/768 KiB result, 64/256 operations,
207
+ 5 s/30 s per operation, 4/16 concurrent operations, and 250 ms/1 s for
208
+ observational evidence. Overrides must be positive safe integers at or below
209
+ the hard maximum; `sandboxCustomTools(...)` rejects invalid configuration.
210
+ Denied operations consume the call budget. A timed-out handler keeps its
211
+ concurrency slot until the underlying MCP call settles, even if it ignores
212
+ cancellation.
39
213
 
40
- The Deno supervisor source is published with the package:
214
+ ## Run the supervisor
41
215
 
42
216
  ```bash
43
- deno run --allow-net --allow-env --allow-run --allow-read --allow-write=$TMPDIR \
217
+ export SANDBOX_WORKLOAD_ADMISSION_KEY_ID=sandbox-workload-v1
218
+ export SANDBOX_WORKLOAD_ADMISSION_SECRET='replace-with-at-least-32-random-bytes'
219
+ export SANDBOX_HOST_ADMISSION_SECRET='replace-with-another-32-byte-secret'
220
+ # Generate a new value for every supervisor process/restart.
221
+ export SANDBOX_INSTANCE_ID=sandbox_instance_01HXYZ
222
+ # Required whenever a policy can receive QUESTPIE broker bindings.
223
+ export SANDBOX_BROKER_URL='https://app.internal/api/sandbox/rpc'
224
+
225
+ deno run --allow-net --allow-env --allow-run --allow-read \
226
+ --allow-write=$TMPDIR \
44
227
  node_modules/@questpie/sandbox/src/sandbox-server.ts
45
228
  ```
46
229
 
47
- Set `SANDBOX_URL` in the app to the supervisor URL.
230
+ Set `SANDBOX_URL` in the app to the directly addressed URL of that supervisor
231
+ instance.
48
232
 
49
233
  ## Exports
50
234
 
51
- | Entry Point | Purpose |
52
- | --------------------------- | ------------------------------------------ |
53
- | `@questpie/sandbox` | Adapter, validation, and binding utilities |
54
- | `@questpie/sandbox/adapter` | `httpSandboxAdapter()` only |
235
+ | Entry point | Purpose |
236
+ | ----------------------------------- | --------------------------------------------- |
237
+ | `@questpie/sandbox` | Adapter, authorization, validation, bindings |
238
+ | `@questpie/sandbox/adapter` | HTTP adapter and workload authorization types |
239
+ | `@questpie/sandbox/modules/sandbox` | Generated broker route module |
55
240
 
56
241
  ## License
57
242
 
@@ -0,0 +1,259 @@
1
+ import { a as SandboxCustomToolsRunOptions } from "./custom-tools-CbaRJ1ei.mjs";
2
+ import { ExecutorAdapter, ExecutorRunOptions, ExecutorRunResult } from "questpie/executor";
3
+
4
+ //#region src/workload-admission.d.ts
5
+ interface SandboxWorkloadAdmissionClaims {
6
+ readonly kind: "sandbox_workload_admission";
7
+ readonly version: 1;
8
+ readonly admissionId: string;
9
+ readonly supervisorInstanceId: string;
10
+ readonly expiresAt: string;
11
+ readonly requestSha256: string;
12
+ }
13
+ interface SandboxWorkloadAdmissionKey {
14
+ readonly keyId: string;
15
+ readonly secret: Uint8Array;
16
+ /** Unique per supervisor process; rotate on every restart. */
17
+ readonly instanceId: string;
18
+ }
19
+ type SandboxWorkloadAdmissionDenialReason = "invalid" | "wrong_instance" | "expired" | "body_mismatch";
20
+ type SandboxWorkloadAdmissionVerification = {
21
+ readonly ok: true;
22
+ readonly claims: SandboxWorkloadAdmissionClaims;
23
+ } | {
24
+ readonly ok: false;
25
+ readonly reason: SandboxWorkloadAdmissionDenialReason;
26
+ readonly claims?: SandboxWorkloadAdmissionClaims;
27
+ };
28
+ //#endregion
29
+ //#region src/types.d.ts
30
+ /**
31
+ * Wire contract between the `executor` adapter (Bun/Node, main app) and the
32
+ * standalone Deno sandbox server. Kept intentionally minimal: the main app
33
+ * never imports Deno; it only speaks this JSON over HTTP.
34
+ */
35
+ /**
36
+ * Per-request guest capabilities. `net` and `import` are INDEPENDENT axes
37
+ * (Deno enforces them separately — `--allow-net` gates `fetch`, `--allow-import`
38
+ * gates module imports). The richer app-binding capabilities (`data`, `services`,
39
+ * etc.) live on the core `ExecutorCapabilities` type and are NOT enforced by the
40
+ * sandbox server — they are the later bindings-broker's job.
41
+ */
42
+ interface SandboxCapabilities {
43
+ /** Runtime `fetch()` allowlist as `host[:port]`. Empty = no network. */
44
+ net: string[];
45
+ /** Module import allowlist as `host[:port]` (e.g. `esm.sh:443`). Empty = no remote imports. */
46
+ import: string[];
47
+ /** Hard wall-clock timeout; the subprocess is force-killed on exceed. */
48
+ timeoutMs: number;
49
+ /** Hard per-guest memory bound (`--v8-flags=--max-old-space-size`). */
50
+ memoryMb: number;
51
+ }
52
+ /**
53
+ * App-bindings wiring for the UNTRUSTED path. When present, the supervisor
54
+ * exposes a capability-scoped `globalThis.questpie` proxy to the guest whose
55
+ * methods RPC (over STDIO, never the network) to the host broker at {@link url},
56
+ * authenticated by the per-run scoped {@link token}.
57
+ *
58
+ * The token is OPAQUE and is held by the SUPERVISOR only — it is never written
59
+ * into the guest subprocess, so guest code cannot read or forge it to widen
60
+ * scope. The guest only ever sees the typed proxy.
61
+ *
62
+ * Critically, the broker is reached SERVER-TO-SERVER (supervisor → app), NOT by
63
+ * the guest's `fetch`: the guest's egress allowlist rejects loopback/private IPs
64
+ * (the SSRF defense), so a localhost broker is unreachable from guest code by
65
+ * design — we do not poke a hole in that.
66
+ */
67
+ interface SandboxBindings {
68
+ /** Absolute URL of the host broker endpoint (e.g. `https://app/sandbox/rpc`). */
69
+ url: string;
70
+ /** Per-run opaque scoped token. Supervisor-only; never exposed to the guest. */
71
+ token: string;
72
+ }
73
+ /** POST /run request body. */
74
+ interface SandboxRunRequest {
75
+ /** Explicit transport admission. The supervisor rejects omitted/unknown modes. */
76
+ mode: "workload" | "host";
77
+ /** Guest TypeScript source. Must `export default` a `function(input)`. */
78
+ source: string;
79
+ /** Payload passed to the guest entry function. */
80
+ input: unknown;
81
+ capabilities: SandboxCapabilities;
82
+ /**
83
+ * Secrets injected into the guest (NOT in source). Surfaced to the guest as
84
+ * `globalThis.__secrets`. The sandbox never persists these.
85
+ */
86
+ secrets?: Record<string, string>;
87
+ /**
88
+ * App-bindings broker wiring. When omitted, the guest gets no `questpie`
89
+ * proxy (the legacy compute-only path). When present, the supervisor brokers
90
+ * the guest's binding RPCs to the host. See {@link SandboxBindings}.
91
+ */
92
+ bindings?: SandboxBindings;
93
+ }
94
+ /** POST /run response body — also the `ExecutorAdapter.run` result shape. */
95
+ type SandboxRunResult = {
96
+ ok: true;
97
+ output?: unknown;
98
+ logs: string[];
99
+ error?: never;
100
+ timedOut?: never;
101
+ /** Wall-clock duration in ms (server-measured). */
102
+ ms?: number;
103
+ } | {
104
+ ok: false;
105
+ error: string;
106
+ logs: string[];
107
+ output?: never;
108
+ timedOut?: never;
109
+ /** Wall-clock duration in ms (server-measured). */
110
+ ms?: number;
111
+ } | {
112
+ ok: false;
113
+ error: string;
114
+ logs: string[];
115
+ output?: never;
116
+ /** True when the run was killed by the wall-clock timeout. */
117
+ timedOut: true;
118
+ /** Wall-clock duration in ms (server-measured). */
119
+ ms?: number;
120
+ };
121
+ /** Line prefix marking a framed protocol message on guest stdout. */
122
+ declare const FRAME_MARKER = "__QP_SANDBOX_MSG__";
123
+ /**
124
+ * Header carrying the per-run scoped token on the supervisor → broker request.
125
+ * Mirrors `questpie`'s `BINDINGS_TOKEN_HEADER`. Supervisor-only; never set by or
126
+ * exposed to guest code.
127
+ */
128
+ declare const BINDINGS_TOKEN_HEADER$1 = "x-questpie-sandbox-token";
129
+ /** Host-issued admission binding an authorized workload to one exact request. */
130
+ declare const WORKLOAD_ADMISSION_HEADER = "x-questpie-workload-admission";
131
+ /** Trusted service identity for the structurally separate direct-host path. */
132
+ declare const HOST_ADMISSION_HEADER = "x-questpie-sandbox-host-admission";
133
+ /** Structured error returned by the broker (mirrors `questpie` `BindingError`). */
134
+ interface SandboxBindingError {
135
+ code: string;
136
+ message: string;
137
+ correlationId?: string;
138
+ }
139
+ /** Guest → supervisor: a binding RPC awaiting a brokered result. */
140
+ interface GuestRpcMessage {
141
+ type: "rpc";
142
+ id: number;
143
+ method: string;
144
+ args: unknown;
145
+ }
146
+ /** Guest → supervisor: the run's final result. */
147
+ interface GuestResultMessage {
148
+ type: "result";
149
+ ok: boolean;
150
+ output?: unknown;
151
+ error?: string;
152
+ logs: string[];
153
+ }
154
+ /** Any message the guest emits on stdout (framed). */
155
+ type GuestMessage = GuestRpcMessage | GuestResultMessage;
156
+ /** Supervisor → guest: the brokered outcome of a prior {@link GuestRpcMessage}. */
157
+ interface HostRpcResultMessage {
158
+ type: "rpc-result";
159
+ id: number;
160
+ ok: boolean;
161
+ value?: unknown;
162
+ error?: SandboxBindingError;
163
+ }
164
+ //#endregion
165
+ //#region src/workload.d.ts
166
+ declare const SANDBOX_WORKLOAD_DENIAL_MESSAGE = "The workload is not authorized for this sandbox operation.";
167
+ declare class SandboxWorkloadDeniedError extends Error {
168
+ readonly code: "sandbox_authority_denied";
169
+ constructor();
170
+ }
171
+ interface SandboxWorkloadPolicy {
172
+ readonly source: string;
173
+ readonly input?: unknown;
174
+ readonly capabilities: SandboxCapabilities;
175
+ readonly secrets?: Readonly<Record<string, string>>;
176
+ readonly bindings?: SandboxBindings;
177
+ /** Host-only custom-tool authority. Never serialized to the supervisor. */
178
+ readonly sandboxTools?: SandboxCustomToolsRunOptions;
179
+ /** Optional consumer deadline; transport admission is additionally capped at 5s. */
180
+ readonly validUntil?: string;
181
+ }
182
+ type SandboxWorkloadAuthorizationPhase = "prepare" | "dispatch";
183
+ interface SandboxWorkloadAuthorizationContext {
184
+ readonly phase: SandboxWorkloadAuthorizationPhase;
185
+ readonly signal: AbortSignal;
186
+ }
187
+ type SandboxWorkloadAuthorizer = (envelope: unknown, context: SandboxWorkloadAuthorizationContext) => SandboxWorkloadPolicy | null | Promise<SandboxWorkloadPolicy | null>;
188
+ interface SandboxWorkloadAuditEvent {
189
+ readonly boundary: "sandbox.workload";
190
+ readonly phase: SandboxWorkloadAuthorizationPhase | "transport";
191
+ readonly decision: "allowed" | "denied";
192
+ readonly reason: "authorization_failed" | "authorization_timed_out" | "cancelled" | "egress_denied" | "policy_changed" | "policy_invalid" | "authorization_succeeded" | "transport_completed" | "transport_denied" | "transport_cancelled" | "transport_timed_out" | "transport_invalid_response" | "transport_failed";
193
+ }
194
+ interface SandboxWorkloadAuditContext {
195
+ readonly signal: AbortSignal;
196
+ }
197
+ interface SandboxWorkloadRunOptions {
198
+ /** Consumer-owned opaque value. QUESTPIE never parses or persists it. */
199
+ readonly envelope: unknown;
200
+ readonly signal?: AbortSignal;
201
+ }
202
+ //#endregion
203
+ //#region src/adapter-http.d.ts
204
+ interface WorkloadHttpSandboxOptions {
205
+ readonly authorize: SandboxWorkloadAuthorizer;
206
+ readonly admission: SandboxWorkloadAdmissionKey;
207
+ /** Hard bound around each consumer authorization read. Default 2s, max 5s. */
208
+ readonly authorizationTimeoutMs?: number;
209
+ /** Redacted audit seam. Allowed decisions fail closed when it cannot settle. */
210
+ readonly audit?: (event: SandboxWorkloadAuditEvent, context: SandboxWorkloadAuditContext) => void | Promise<void>;
211
+ /** Hard bound around the audit callback. Default 250ms, max 1s. */
212
+ readonly auditTimeoutMs?: number;
213
+ }
214
+ interface HttpSandboxAdapterOptions {
215
+ /**
216
+ * Base URL of the standalone Deno sandbox service (`sandbox-server.ts`).
217
+ * Defaults to `process.env.SANDBOX_URL`. Throws on `run` if neither is set.
218
+ */
219
+ url?: string;
220
+ /**
221
+ * Per-request HTTP fetch timeout (ms). A safety margin above the guest
222
+ * wall-timeout so the server's own kill fires first; if the server is
223
+ * unreachable this bounds the client wait. Default: guest timeout + 10s.
224
+ */
225
+ fetchTimeoutMs?: number;
226
+ /** Optional custom `fetch` (e.g. for auth headers / mTLS). */
227
+ fetch?: typeof fetch;
228
+ /**
229
+ * Validate net/import hosts against the private-IP policy BEFORE sending to
230
+ * the sandbox (defense-in-depth; the server validates too). Default: true.
231
+ */
232
+ validateEgress?: boolean;
233
+ /** Consumer-owned fail-closed admission for opaque remote workloads. */
234
+ workload?: WorkloadHttpSandboxOptions;
235
+ /** Trusted host credential for the direct framework `run()` path. */
236
+ hostAdmissionSecret?: string;
237
+ }
238
+ /**
239
+ * Production sandboxed `ExecutorAdapter` — POSTs the run to the standalone Deno
240
+ * sandbox service over HTTP. The main app (Bun/Node) holds NO Deno; it only
241
+ * speaks this JSON contract.
242
+ *
243
+ * Use as the `sandboxed` adapter in the executor config:
244
+ * executor: { sandboxed: httpSandboxAdapter({ url: process.env.SANDBOX_URL }) }
245
+ */
246
+ declare class HttpSandboxAdapter implements ExecutorAdapter {
247
+ private readonly options;
248
+ private readonly fetchImpl;
249
+ private readonly validateEgress;
250
+ constructor(options?: HttpSandboxAdapterOptions);
251
+ private resolveUrl;
252
+ private postRun;
253
+ run(options: ExecutorRunOptions): Promise<ExecutorRunResult>;
254
+ runWorkload(options: SandboxWorkloadRunOptions): Promise<ExecutorRunResult>;
255
+ }
256
+ /** Factory for the production sandboxed adapter. */
257
+ declare function httpSandboxAdapter(options?: HttpSandboxAdapterOptions): HttpSandboxAdapter;
258
+ //#endregion
259
+ export { SandboxWorkloadAdmissionVerification as A, SandboxCapabilities as C, SandboxWorkloadAdmissionClaims as D, WORKLOAD_ADMISSION_HEADER as E, SandboxWorkloadAdmissionDenialReason as O, SandboxBindings as S, SandboxRunResult as T, GuestResultMessage as _, SANDBOX_WORKLOAD_DENIAL_MESSAGE as a, HostRpcResultMessage as b, SandboxWorkloadAuthorizationContext as c, SandboxWorkloadDeniedError as d, SandboxWorkloadPolicy as f, GuestMessage as g, FRAME_MARKER as h, httpSandboxAdapter as i, SandboxWorkloadAdmissionKey as k, SandboxWorkloadAuthorizationPhase as l, BINDINGS_TOKEN_HEADER$1 as m, HttpSandboxAdapterOptions as n, SandboxWorkloadAuditContext as o, SandboxWorkloadRunOptions as p, WorkloadHttpSandboxOptions as r, SandboxWorkloadAuditEvent as s, HttpSandboxAdapter as t, SandboxWorkloadAuthorizer as u, GuestRpcMessage as v, SandboxRunRequest as w, SandboxBindingError as x, HOST_ADMISSION_HEADER as y };