@themoltnet/sandbox-gondolin 0.2.0 → 0.3.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/dist/index.d.ts +57 -1
- package/dist/index.js +169 -9
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -109,6 +109,9 @@ export declare function findMainWorktree(startPath?: string): string;
|
|
|
109
109
|
/** Commands guaranteed by the base Gondolin snapshot. */
|
|
110
110
|
export declare const GONDOLIN_BASE_EXECUTABLES: readonly string[];
|
|
111
111
|
|
|
112
|
+
/** Guest directory holding one PID file per projected service. */
|
|
113
|
+
export declare const GUEST_SERVICE_PID_DIR = "/run/moltnet/services";
|
|
114
|
+
|
|
112
115
|
/**
|
|
113
116
|
* Memory-backed VFS mount used by the daemon to inject task context
|
|
114
117
|
* (#943 slice 1.5). This is a separate top-level mount because Gondolin
|
|
@@ -141,6 +144,39 @@ export declare class GuestEnvironmentBoundaryError extends Error {
|
|
|
141
144
|
constructor(refusedNames: readonly string[]);
|
|
142
145
|
}
|
|
143
146
|
|
|
147
|
+
/** Declarative, trusted-host guest projection (env, files, services). */
|
|
148
|
+
export declare interface GuestProjectionInput {
|
|
149
|
+
env?: Record<string, string>;
|
|
150
|
+
files?: {
|
|
151
|
+
path: string;
|
|
152
|
+
content: string | Uint8Array;
|
|
153
|
+
mode?: number;
|
|
154
|
+
}[];
|
|
155
|
+
services?: {
|
|
156
|
+
id: string;
|
|
157
|
+
command: readonly string[];
|
|
158
|
+
env?: Record<string, string>;
|
|
159
|
+
/** Guest path to wait for before the session starts. */
|
|
160
|
+
readiness?: {
|
|
161
|
+
path: string;
|
|
162
|
+
timeoutMs?: number;
|
|
163
|
+
required?: boolean;
|
|
164
|
+
};
|
|
165
|
+
}[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Lifecycle handle for projected guest services. */
|
|
169
|
+
export declare interface GuestServices {
|
|
170
|
+
stop(): Promise<void>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Host origins: hostnames the guest can reach whose responses are produced by
|
|
175
|
+
* host code inside the proxy (`onRequest` short-circuit). Nothing is listened
|
|
176
|
+
* on, nothing is forwarded; the sandbox knows only the origin → handler map.
|
|
177
|
+
*/
|
|
178
|
+
declare type HostOriginHandler = (request: Request) => Promise<Response>;
|
|
179
|
+
|
|
144
180
|
/**
|
|
145
181
|
* Check containment for already-resolved lexical or real paths.
|
|
146
182
|
*
|
|
@@ -158,6 +194,8 @@ export declare interface ManagedVm {
|
|
|
158
194
|
credentials: VmCredentials;
|
|
159
195
|
/** Host-only rotation and revocation handle. It cannot widen destinations. */
|
|
160
196
|
secretManager: BrokeredHttpSecretManager;
|
|
197
|
+
/** Projected guest services; `stop()` before closing the VM. Always present. */
|
|
198
|
+
services: GuestServices;
|
|
161
199
|
mountPath: string;
|
|
162
200
|
guestWorkspace: string;
|
|
163
201
|
agentDir: string;
|
|
@@ -383,6 +421,19 @@ export declare interface VmConfig {
|
|
|
383
421
|
* raw values or select host secret-provider coordinates.
|
|
384
422
|
*/
|
|
385
423
|
brokeredSecrets?: readonly BrokeredHttpSecretBinding[];
|
|
424
|
+
/**
|
|
425
|
+
* Trusted-host origins answered in-process by the proxy. Keys are full
|
|
426
|
+
* origins (`https://name.moltnet.internal`). Like brokered secrets these are
|
|
427
|
+
* outside SandboxConfig: a remotely stored profile cannot register one.
|
|
428
|
+
*/
|
|
429
|
+
hostOrigins?: Record<string, HostOriginHandler>;
|
|
430
|
+
/**
|
|
431
|
+
* Trusted-host guest projection: env merged last (after broker
|
|
432
|
+
* placeholders), files written before the session starts, services run in
|
|
433
|
+
* the guest for the session's lifetime. Not subject to the profile env
|
|
434
|
+
* reserved-name guard — the runtime, not the profile, declares it.
|
|
435
|
+
*/
|
|
436
|
+
guestProjection?: GuestProjectionInput;
|
|
386
437
|
/** Structured credential-boundary diagnostics for daemon loggers. */
|
|
387
438
|
onDiagnostic?: (diagnostic: VmDiagnostic) => void;
|
|
388
439
|
/** Abort resume/setup work, closing any live VM owned by resumeVm. */
|
|
@@ -420,12 +471,17 @@ export declare interface VmCredentials {
|
|
|
420
471
|
}
|
|
421
472
|
|
|
422
473
|
export declare interface VmDiagnostic {
|
|
423
|
-
event: 'vm.credentials.mode' | 'vm.credentials.github_key_missing' | 'vm.http_secrets.bound';
|
|
474
|
+
event: 'vm.credentials.mode' | 'vm.credentials.github_key_missing' | 'vm.http_secrets.bound' | 'vm.host_origins.bound' | 'vm.guest_projection.applied' | 'vm.guest_service.not_ready';
|
|
424
475
|
level: 'info' | 'warning';
|
|
425
476
|
message: string;
|
|
426
477
|
credentialMode: GuestCredentialMode;
|
|
427
478
|
/** Present only for the value-free broker summary event. */
|
|
428
479
|
brokeredSecretCount?: number;
|
|
480
|
+
/** Present only for the host-origins summary event. */
|
|
481
|
+
hostOriginCount?: number;
|
|
482
|
+
/** Present only for the guest-projection summary event. */
|
|
483
|
+
projectedFileCount?: number;
|
|
484
|
+
projectedServiceCount?: number;
|
|
429
485
|
}
|
|
430
486
|
|
|
431
487
|
export { }
|
package/dist/index.js
CHANGED
|
@@ -100,6 +100,7 @@ var BASE_ALPINE_PACKAGE_EXECUTABLES = {
|
|
|
100
100
|
file: "file",
|
|
101
101
|
git: "git",
|
|
102
102
|
jq: "jq",
|
|
103
|
+
"openssh-keygen": "ssh-keygen",
|
|
103
104
|
ripgrep: "rg",
|
|
104
105
|
tar: "tar",
|
|
105
106
|
xz: "xz"
|
|
@@ -292,6 +293,35 @@ function pruneOldSnapshots(maxCached, currentDir) {
|
|
|
292
293
|
});
|
|
293
294
|
}
|
|
294
295
|
//#endregion
|
|
296
|
+
//#region src/host-origins.ts
|
|
297
|
+
function hostOriginHostnames(origins) {
|
|
298
|
+
return Object.keys(origins).map((origin) => new URL(origin).hostname).sort();
|
|
299
|
+
}
|
|
300
|
+
function blocked() {
|
|
301
|
+
return new Response(JSON.stringify({
|
|
302
|
+
code: "host_origin_blocked",
|
|
303
|
+
message: "origin not served"
|
|
304
|
+
}), {
|
|
305
|
+
status: 421,
|
|
306
|
+
headers: { "content-type": "application/json" }
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Serve a registered origin in-process. Fails closed for any request whose
|
|
311
|
+
* hostname belongs to a virtual origin but whose exact origin (scheme, host,
|
|
312
|
+
* port) is not registered: such a request must never fall through to normal
|
|
313
|
+
* outbound proxy forwarding (an SSRF route to a host-controlled name).
|
|
314
|
+
*/
|
|
315
|
+
function createHostOriginsOnRequest(origins) {
|
|
316
|
+
const virtualHostnames = new Set(hostOriginHostnames(origins));
|
|
317
|
+
return async (request) => {
|
|
318
|
+
const url = new URL(request.url);
|
|
319
|
+
const handler = origins[url.origin];
|
|
320
|
+
if (handler) return handler(request);
|
|
321
|
+
if (virtualHostnames.has(url.hostname)) return blocked();
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
295
325
|
//#region src/vm-manager.ts
|
|
296
326
|
/**
|
|
297
327
|
* Memory-backed VFS mount used by the daemon to inject task context
|
|
@@ -314,6 +344,12 @@ function pruneOldSnapshots(maxCached, currentDir) {
|
|
|
314
344
|
* investigation and the alternatives we rejected.
|
|
315
345
|
*/
|
|
316
346
|
var GUEST_TASK_CONTEXT_MOUNT = "/moltnet-task-context";
|
|
347
|
+
/** Guest directory holding one PID file per projected service. */
|
|
348
|
+
var GUEST_SERVICE_PID_DIR = "/run/moltnet/services";
|
|
349
|
+
var GUEST_SERVICE_ID_RE = /^[a-z][a-z0-9-]{0,62}$/;
|
|
350
|
+
function assertGuestServiceId(id) {
|
|
351
|
+
if (!GUEST_SERVICE_ID_RE.test(id)) throw new Error(`Invalid guest service id "${id}": expected ${GUEST_SERVICE_ID_RE}`);
|
|
352
|
+
}
|
|
317
353
|
/** @deprecated Use GUEST_TASK_CONTEXT_MOUNT. */
|
|
318
354
|
var GUEST_TASK_SKILLS_MOUNT = GUEST_TASK_CONTEXT_MOUNT;
|
|
319
355
|
function resolveVfsShadowConfig(config) {
|
|
@@ -752,6 +788,10 @@ function assertInternalHostsDoNotOverlapProtectedHosts(internalHosts, protectedH
|
|
|
752
788
|
* surface immediately rather than fall through to cryptic agent
|
|
753
789
|
* errors later.
|
|
754
790
|
*/
|
|
791
|
+
/** Single-quote a POSIX shell argument (paths/ids are pre-validated). */
|
|
792
|
+
function shellQuote(value) {
|
|
793
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
794
|
+
}
|
|
755
795
|
async function vmRun(vm, label, command, signal) {
|
|
756
796
|
const wrapped = `set -eu\nset -o pipefail\n${command}`;
|
|
757
797
|
throwIfAborted(signal, `resume step "${label}"`);
|
|
@@ -806,9 +846,14 @@ async function resumeVm(config) {
|
|
|
806
846
|
])];
|
|
807
847
|
assertInternalHostsDoNotOverlapProtectedHosts(runtimeAllowedInternalHosts, protectedExternalHosts);
|
|
808
848
|
const allowedHosts = [...new Set([...protectedExternalHosts, ...runtimeAllowedHosts])];
|
|
849
|
+
const hostOrigins = config.hostOrigins ?? {};
|
|
850
|
+
const hostOriginHosts = hostOriginHostnames(hostOrigins);
|
|
851
|
+
assertInternalHostsDoNotOverlapProtectedHosts(hostOriginHosts, protectedExternalHosts);
|
|
852
|
+
const allowedInternalHosts = [...new Set([...runtimeAllowedInternalHosts, ...hostOriginHosts])];
|
|
853
|
+
const projectedEnv = config.guestProjection?.env ?? {};
|
|
809
854
|
const brokeredSecrets = prepareBrokeredHttpSecrets({
|
|
810
855
|
bindings: config.brokeredSecrets,
|
|
811
|
-
allowedHosts: [...allowedHosts, ...
|
|
856
|
+
allowedHosts: [...allowedHosts, ...allowedInternalHosts],
|
|
812
857
|
occupiedGuestEnvNames: [
|
|
813
858
|
"PATH",
|
|
814
859
|
"HOME",
|
|
@@ -817,17 +862,19 @@ async function resumeVm(config) {
|
|
|
817
862
|
"MOLTNET_GUEST_WORKSPACE",
|
|
818
863
|
...config.forwardEnv ?? [],
|
|
819
864
|
...Object.keys(creds.agentEnv),
|
|
820
|
-
...Object.keys(config.sandboxConfig?.env ?? {})
|
|
865
|
+
...Object.keys(config.sandboxConfig?.env ?? {}),
|
|
866
|
+
...Object.keys(projectedEnv)
|
|
821
867
|
]
|
|
822
868
|
});
|
|
823
869
|
const brokeredSecretOriginPolicy = createBrokeredHttpSecretOriginPolicy(config.brokeredSecrets ?? []);
|
|
824
870
|
const { httpHooks, env: secretEnv, secretManager: gondolinSecretManager } = createHttpHooks({
|
|
825
871
|
allowedHosts,
|
|
826
|
-
allowedInternalHosts
|
|
872
|
+
allowedInternalHosts,
|
|
827
873
|
...Object.keys(brokeredSecrets).length > 0 && {
|
|
828
874
|
secrets: brokeredSecrets,
|
|
829
875
|
isRequestAllowed: brokeredSecretOriginPolicy.isRequestAllowed
|
|
830
|
-
}
|
|
876
|
+
},
|
|
877
|
+
...hostOriginHosts.length > 0 && { onRequest: createHostOriginsOnRequest(hostOrigins) }
|
|
831
878
|
});
|
|
832
879
|
const secretManager = Object.freeze({
|
|
833
880
|
rotateSecret(guestEnv, value) {
|
|
@@ -847,6 +894,13 @@ async function resumeVm(config) {
|
|
|
847
894
|
brokeredSecretCount,
|
|
848
895
|
message: `Bound ${brokeredSecretCount} HTTP secret placeholder${brokeredSecretCount === 1 ? "" : "s"} to the host proxy`
|
|
849
896
|
});
|
|
897
|
+
if (hostOriginHosts.length > 0) config.onDiagnostic?.({
|
|
898
|
+
event: "vm.host_origins.bound",
|
|
899
|
+
level: "info",
|
|
900
|
+
credentialMode: guestCredentialMode,
|
|
901
|
+
hostOriginCount: hostOriginHosts.length,
|
|
902
|
+
message: `Bound ${hostOriginHosts.length} host origin${hostOriginHosts.length === 1 ? "" : "s"} to the host proxy`
|
|
903
|
+
});
|
|
850
904
|
const vmAgentDir = `/home/agent/.moltnet/${config.agentName}`;
|
|
851
905
|
const vmAgentEnv = {};
|
|
852
906
|
for (const [k, v] of Object.entries(creds.agentEnv)) {
|
|
@@ -892,7 +946,8 @@ async function resumeVm(config) {
|
|
|
892
946
|
NODE_EXTRA_CA_CERTS: "/etc/ssl/certs/ca-certificates.crt",
|
|
893
947
|
...envOverrides,
|
|
894
948
|
MOLTNET_GUEST_WORKSPACE: guestWorkspace,
|
|
895
|
-
...secretEnv
|
|
949
|
+
...secretEnv,
|
|
950
|
+
...projectedEnv
|
|
896
951
|
};
|
|
897
952
|
const resources = config.sandboxConfig?.resources;
|
|
898
953
|
const workspaceMode = config.workspaceMode ?? "shared_mount";
|
|
@@ -915,6 +970,27 @@ async function resumeVm(config) {
|
|
|
915
970
|
process.stderr.write(`[vm] aborted resume late vm.close() failed: ${message}\n`);
|
|
916
971
|
}
|
|
917
972
|
});
|
|
973
|
+
const servicesAbort = new AbortController();
|
|
974
|
+
const serviceHandles = [];
|
|
975
|
+
const serviceIds = [];
|
|
976
|
+
const services = { async stop() {
|
|
977
|
+
if (serviceIds.length > 0) {
|
|
978
|
+
const pidFiles = serviceIds.map((id) => `${GUEST_SERVICE_PID_DIR}/${id}.pid`).join(" ");
|
|
979
|
+
try {
|
|
980
|
+
await vm.exec([
|
|
981
|
+
"sh",
|
|
982
|
+
"-c",
|
|
983
|
+
`for f in ${pidFiles}; do [ -f "$f" ] || continue; pid=$(cat "$f"); kill -TERM -- "-\$pid" 2>/dev/null || kill -TERM "\$pid" 2>/dev/null || true; done; sleep 0.2; for f in ${pidFiles}; do [ -f "$f" ] || continue; pid=$(cat "$f"); kill -KILL -- "-\$pid" 2>/dev/null || kill -KILL "\$pid" 2>/dev/null || true; rm -f "\$f"; done`
|
|
984
|
+
], {
|
|
985
|
+
stdout: "ignore",
|
|
986
|
+
stderr: "ignore"
|
|
987
|
+
});
|
|
988
|
+
} catch {}
|
|
989
|
+
serviceIds.length = 0;
|
|
990
|
+
}
|
|
991
|
+
servicesAbort.abort();
|
|
992
|
+
await Promise.allSettled(serviceHandles);
|
|
993
|
+
} };
|
|
918
994
|
try {
|
|
919
995
|
await vmRun(vm, "TLS certificates", `
|
|
920
996
|
cp /etc/gondolin/mitm/ca.crt /usr/local/share/ca-certificates/gondolin-mitm.crt
|
|
@@ -950,8 +1026,14 @@ async function resumeVm(config) {
|
|
|
950
1026
|
const vmSshDir = `${vmAgentDir}/ssh`;
|
|
951
1027
|
const hasAgentFiles = guestCredentialMode === "guest-config";
|
|
952
1028
|
const providerAuthDir = config.providerAuth ? path.posix.dirname(config.providerAuth.guestPath) : null;
|
|
953
|
-
const
|
|
954
|
-
|
|
1029
|
+
const projectedFiles = config.guestProjection?.files ?? [];
|
|
1030
|
+
const projectedDirs = [...new Set(projectedFiles.map((file) => path.posix.dirname(file.path)))];
|
|
1031
|
+
const guestDirs = [
|
|
1032
|
+
...hasAgentFiles ? [`${vmAgentDir}/ssh`] : [],
|
|
1033
|
+
...providerAuthDir ? [providerAuthDir] : [],
|
|
1034
|
+
...projectedDirs
|
|
1035
|
+
];
|
|
1036
|
+
if (guestDirs.length > 0) await vmRun(vm, "create guest directories", `mkdir -p ${guestDirs.map(shellQuote).join(" ")}`, config.signal);
|
|
955
1037
|
if (creds.providerAuthJson !== null && config.providerAuth) await vm.fs.writeFile(config.providerAuth.guestPath, creds.providerAuthJson, {
|
|
956
1038
|
mode: 384,
|
|
957
1039
|
signal: config.signal
|
|
@@ -990,16 +1072,94 @@ async function resumeVm(config) {
|
|
|
990
1072
|
signal: config.signal
|
|
991
1073
|
});
|
|
992
1074
|
}
|
|
993
|
-
|
|
1075
|
+
for (const file of projectedFiles) {
|
|
1076
|
+
await vm.fs.writeFile(file.path, file.content, {
|
|
1077
|
+
mode: file.mode ?? 420,
|
|
1078
|
+
signal: config.signal
|
|
1079
|
+
});
|
|
1080
|
+
if (file.mode !== void 0) await vmRun(vm, `chmod projected file ${file.path}`, `chmod ${file.mode.toString(8)} ${shellQuote(file.path)}`, config.signal);
|
|
1081
|
+
}
|
|
1082
|
+
await vmRun(vm, "chown guest directories", `set -e; for d in ${[
|
|
1083
|
+
"/home/agent/.pi",
|
|
1084
|
+
...hasAgentFiles ? ["/home/agent/.moltnet"] : [],
|
|
1085
|
+
...projectedDirs.filter((dir) => dir.startsWith("/home/agent/"))
|
|
1086
|
+
].map(shellQuote).join(" ")}; do if [ -e "$d" ]; then chown -R agent:agent "$d"; fi; done`, config.signal);
|
|
1087
|
+
const projectedServices = config.guestProjection?.services ?? [];
|
|
1088
|
+
if (projectedServices.length > 0) await vmRun(vm, "create service pid directory", `mkdir -p ${GUEST_SERVICE_PID_DIR}`, config.signal);
|
|
1089
|
+
for (const service of projectedServices) {
|
|
1090
|
+
assertGuestServiceId(service.id);
|
|
1091
|
+
const handle = vm.exec([
|
|
1092
|
+
"setsid",
|
|
1093
|
+
"sh",
|
|
1094
|
+
"-c",
|
|
1095
|
+
`echo $$ > ${GUEST_SERVICE_PID_DIR}/${service.id}.pid && exec "$@"`,
|
|
1096
|
+
"moltnet-service",
|
|
1097
|
+
...service.command
|
|
1098
|
+
], {
|
|
1099
|
+
stdout: "ignore",
|
|
1100
|
+
stderr: "ignore",
|
|
1101
|
+
...service.env && { env: service.env },
|
|
1102
|
+
signal: servicesAbort.signal
|
|
1103
|
+
});
|
|
1104
|
+
serviceHandles.push(Promise.resolve(handle).catch(() => void 0));
|
|
1105
|
+
serviceIds.push(service.id);
|
|
1106
|
+
}
|
|
1107
|
+
async function awaitServiceReady(service) {
|
|
1108
|
+
if (!service.readiness) return;
|
|
1109
|
+
const deadline = Date.now() + (service.readiness.timeoutMs ?? 1e4);
|
|
1110
|
+
const pidFile = `${GUEST_SERVICE_PID_DIR}/${service.id}.pid`;
|
|
1111
|
+
let ready = false;
|
|
1112
|
+
while (Date.now() < deadline) {
|
|
1113
|
+
throwIfAborted(config.signal, `service "${service.id}" readiness`);
|
|
1114
|
+
if ((await vm.exec([
|
|
1115
|
+
"sh",
|
|
1116
|
+
"-c",
|
|
1117
|
+
"[ -e \"$1\" ] && [ -r \"$2\" ] && kill -0 \"$(cat \"$2\" 2>/dev/null)\" 2>/dev/null",
|
|
1118
|
+
"moltnet-readiness",
|
|
1119
|
+
service.readiness.path,
|
|
1120
|
+
pidFile
|
|
1121
|
+
], {
|
|
1122
|
+
stdout: "ignore",
|
|
1123
|
+
stderr: "ignore",
|
|
1124
|
+
signal: config.signal
|
|
1125
|
+
})).exitCode === 0) {
|
|
1126
|
+
ready = true;
|
|
1127
|
+
break;
|
|
1128
|
+
}
|
|
1129
|
+
await new Promise((resolve) => {
|
|
1130
|
+
setTimeout(resolve, 200);
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
if (!ready) {
|
|
1134
|
+
if (service.readiness.required) throw new Error(`Projected guest service "${service.id}" did not become ready: ${service.readiness.path} absent or its process exited`);
|
|
1135
|
+
config.onDiagnostic?.({
|
|
1136
|
+
event: "vm.guest_service.not_ready",
|
|
1137
|
+
level: "warning",
|
|
1138
|
+
credentialMode: guestCredentialMode,
|
|
1139
|
+
message: `Projected guest service "${service.id}" did not become ready (${service.readiness.path} absent or its process exited); continuing without it`
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
await Promise.all(projectedServices.map((service) => awaitServiceReady(service)));
|
|
1144
|
+
if (projectedFiles.length > 0 || projectedServices.length > 0) config.onDiagnostic?.({
|
|
1145
|
+
event: "vm.guest_projection.applied",
|
|
1146
|
+
level: "info",
|
|
1147
|
+
credentialMode: guestCredentialMode,
|
|
1148
|
+
projectedFileCount: projectedFiles.length,
|
|
1149
|
+
projectedServiceCount: projectedServices.length,
|
|
1150
|
+
message: `Applied guest projection: ${projectedFiles.length} file(s), ${projectedServices.length} service(s)`
|
|
1151
|
+
});
|
|
994
1152
|
return {
|
|
995
1153
|
vm,
|
|
996
1154
|
credentials: creds,
|
|
997
1155
|
secretManager,
|
|
1156
|
+
services,
|
|
998
1157
|
mountPath: config.mountPath,
|
|
999
1158
|
guestWorkspace,
|
|
1000
1159
|
agentDir
|
|
1001
1160
|
};
|
|
1002
1161
|
} catch (err) {
|
|
1162
|
+
servicesAbort.abort();
|
|
1003
1163
|
try {
|
|
1004
1164
|
await vm.close();
|
|
1005
1165
|
} catch (closeErr) {
|
|
@@ -1071,4 +1231,4 @@ function rewriteMoltnetJsonPaths(moltnetJson, vmAgentDir, vmSshDir, githubAppPem
|
|
|
1071
1231
|
return JSON.stringify(config);
|
|
1072
1232
|
}
|
|
1073
1233
|
//#endregion
|
|
1074
|
-
export { AutoParentMemoryProvider, BrokeredHttpSecretBoundaryError, GONDOLIN_BASE_EXECUTABLES, GUEST_TASK_CONTEXT_MOUNT, GUEST_TASK_SKILLS_MOUNT, GuestEnvironmentBoundaryError, abortableResource, activateAgentEnv, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, canonicalizeBrokeredHttpSecretDescriptor, delay, ensureSnapshot, findMainWorktree, isResolvedPathInsideRoot, loadCredentials, prepareBrokeredHttpSecrets, resolveVfsShadowConfig, resolveVmAgentDir, resumeVm, rewriteGitconfigPaths, rewriteMoltnetJsonPaths, shouldRunResumeCommand, shouldShadowNodeModulesPath, throwIfAborted };
|
|
1234
|
+
export { AutoParentMemoryProvider, BrokeredHttpSecretBoundaryError, GONDOLIN_BASE_EXECUTABLES, GUEST_SERVICE_PID_DIR, GUEST_TASK_CONTEXT_MOUNT, GUEST_TASK_SKILLS_MOUNT, GuestEnvironmentBoundaryError, abortableResource, activateAgentEnv, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, canonicalizeBrokeredHttpSecretDescriptor, delay, ensureSnapshot, findMainWorktree, isResolvedPathInsideRoot, loadCredentials, prepareBrokeredHttpSecrets, resolveVfsShadowConfig, resolveVmAgentDir, resumeVm, rewriteGitconfigPaths, rewriteMoltnetJsonPaths, shouldRunResumeCommand, shouldShadowNodeModulesPath, throwIfAborted };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/sandbox-gondolin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Gondolin microVM sandbox lifecycle for MoltNet runtimes: checkpoint resume, egress policy, VFS shadowing, guest credential boundary, host-brokered secrets",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"moltnet",
|