@themoltnet/sandbox-gondolin 0.4.0 → 0.5.1
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 +12 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +125 -31
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -87,6 +87,18 @@ queries. Gondolin also decodes, substitutes, and re-encodes HTTP Basic
|
|
|
87
87
|
authorization, covering the password encoding used by HTTPS Git credential
|
|
88
88
|
helpers; OAuth client secrets sent in form bodies remain host-only.
|
|
89
89
|
|
|
90
|
+
`vm.network.policy_bound` reports the complete hostname inputs passed to
|
|
91
|
+
Gondolin; it is configuration evidence, not independent proof of Gondolin's
|
|
92
|
+
resolved state. Brokered requests emit `vm.network.origin_checked` for
|
|
93
|
+
canonical protocol/hostname/port decisions at request and IP phases. Denials
|
|
94
|
+
remain available as `vm.network.origin_denied`. All diagnostics are
|
|
95
|
+
value-free.
|
|
96
|
+
|
|
97
|
+
Gondolin 0.12 pins the actual upstream connection to the policy-checked IP only
|
|
98
|
+
when using its default fetch. This package therefore does not expose a custom
|
|
99
|
+
fetch or fixture-routing seam: doing so would retain a preflight IP check while
|
|
100
|
+
losing connect-time pinning and reopening a DNS-rebinding window.
|
|
101
|
+
|
|
90
102
|
Rotation and revocation do not require exposing or changing the guest
|
|
91
103
|
placeholder:
|
|
92
104
|
|
package/dist/index.d.ts
CHANGED
|
@@ -91,8 +91,9 @@ export declare function canonicalizeBrokeredHttpSecretDescriptor(descriptor: Bro
|
|
|
91
91
|
* enforce the descriptor protocol and port before request or IP dispatch.
|
|
92
92
|
*/
|
|
93
93
|
export declare function createBrokeredHttpNetworkOriginPolicy(bindings: readonly BrokeredHttpSecretDescriptor[], options?: {
|
|
94
|
-
|
|
94
|
+
onDecision?: (decision: RequestOrigin & {
|
|
95
95
|
phase: 'request' | 'ip';
|
|
96
|
+
allowed: boolean;
|
|
96
97
|
}) => void;
|
|
97
98
|
}): {
|
|
98
99
|
isRequestAllowed: (request: Request) => boolean;
|
|
@@ -461,7 +462,7 @@ export declare interface VmCredentials {
|
|
|
461
462
|
}
|
|
462
463
|
|
|
463
464
|
export declare interface VmDiagnostic {
|
|
464
|
-
event: 'vm.credentials.mode' | 'vm.http_secrets.bound' | 'vm.host_origins.bound' | 'vm.guest_projection.applied' | 'vm.guest_service.not_ready' | 'vm.network.origin_denied';
|
|
465
|
+
event: 'vm.credentials.mode' | 'vm.http_secrets.bound' | 'vm.host_origins.bound' | 'vm.guest_projection.applied' | 'vm.guest_service.not_ready' | 'vm.network.policy_bound' | 'vm.network.origin_checked' | 'vm.network.origin_denied';
|
|
465
466
|
level: 'info' | 'warning';
|
|
466
467
|
message: string;
|
|
467
468
|
/** Present only for the value-free broker summary event. */
|
|
@@ -471,12 +472,18 @@ export declare interface VmDiagnostic {
|
|
|
471
472
|
/** Present only for the guest-projection summary event. */
|
|
472
473
|
projectedFileCount?: number;
|
|
473
474
|
projectedServiceCount?: number;
|
|
474
|
-
/**
|
|
475
|
+
/** Complete value-free hostname inputs passed to Gondolin. */
|
|
476
|
+
hostnamePolicy?: {
|
|
477
|
+
allowedHosts: readonly string[];
|
|
478
|
+
allowedInternalHosts: readonly string[];
|
|
479
|
+
};
|
|
480
|
+
/** Canonical, value-free decision for an origin check or denial. */
|
|
475
481
|
origin?: {
|
|
476
482
|
hostname: string;
|
|
477
483
|
protocol: string;
|
|
478
484
|
port: number;
|
|
479
485
|
phase: 'request' | 'ip';
|
|
486
|
+
allowed: boolean;
|
|
480
487
|
};
|
|
481
488
|
}
|
|
482
489
|
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createHash } from "node:crypto";
|
|
|
4
4
|
import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
5
5
|
import { MemoryProvider, RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, createShadowPathPredicate, ensureImageSelector, isWriteFlag, loadGuestAssets } from "@earendil-works/gondolin";
|
|
6
6
|
import { isIP } from "node:net";
|
|
7
|
+
import { domainToASCII } from "node:url";
|
|
7
8
|
//#region src/abort-utils.ts
|
|
8
9
|
function throwIfAborted(signal, label) {
|
|
9
10
|
if (!signal?.aborted) return;
|
|
@@ -290,12 +291,24 @@ function getCacheDir() {
|
|
|
290
291
|
const base = process.env.XDG_CACHE_HOME ?? path.join(process.env.HOME ?? "/tmp", ".cache");
|
|
291
292
|
return path.join(base, "moltnet", "gondolin");
|
|
292
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* The VM backend the snapshot will be built with. Gondolin checkpoints
|
|
296
|
+
* record `compatibleVmm` and refuse to resume under a backend the build
|
|
297
|
+
* did not produce assets for, so the backend MUST be part of the cache
|
|
298
|
+
* key: a qemu-built snapshot resumed under `GONDOLIN_VMM=krun` (the
|
|
299
|
+
* signed bundle's default) would otherwise fail every run until the
|
|
300
|
+
* cache is wiped by hand.
|
|
301
|
+
*/
|
|
302
|
+
function resolveSnapshotVmm(env = process.env) {
|
|
303
|
+
return env.GONDOLIN_VMM?.trim().toLowerCase() === "krun" ? "krun" : "qemu";
|
|
304
|
+
}
|
|
293
305
|
function computeConfigHash(config) {
|
|
294
306
|
const h = createHash("sha256");
|
|
295
307
|
h.update(JSON.stringify({
|
|
296
308
|
baseAlpine: BASE_ALPINE_PACKAGES,
|
|
297
309
|
ghVersion: GH_VERSION,
|
|
298
310
|
cliVersion: MOLTNET_CLI_VERSION,
|
|
311
|
+
vmm: resolveSnapshotVmm(),
|
|
299
312
|
config
|
|
300
313
|
}));
|
|
301
314
|
return h.digest("hex").slice(0, 12);
|
|
@@ -425,6 +438,76 @@ function pruneOldSnapshots(maxCached, currentDir) {
|
|
|
425
438
|
});
|
|
426
439
|
}
|
|
427
440
|
//#endregion
|
|
441
|
+
//#region src/canonical-host.ts
|
|
442
|
+
var DECIMAL_IPV4_COMPONENTS = /^\d+(?:\.\d+){0,3}$/;
|
|
443
|
+
var NON_DECIMAL_IPV4 = /^(?:0x[\da-f]+|0[0-7]+)(?:\.(?:0x[\da-f]+|0[0-7]+|\d+)){0,3}$/i;
|
|
444
|
+
function stripTrailingDots(value) {
|
|
445
|
+
let end = value.length;
|
|
446
|
+
while (end > 0 && value.charCodeAt(end - 1) === 46) end -= 1;
|
|
447
|
+
return value.slice(0, end);
|
|
448
|
+
}
|
|
449
|
+
function rejectAlternateNumericAddress(hostname) {
|
|
450
|
+
if (isIP(hostname) === 0 && (DECIMAL_IPV4_COMPONENTS.test(hostname) || NON_DECIMAL_IPV4.test(hostname) || /^\d+$/.test(hostname))) throw new Error("alternate numeric IP forms are not admitted");
|
|
451
|
+
}
|
|
452
|
+
/** Canonicalize one concrete DNS name or IP literal for policy comparison. */
|
|
453
|
+
function canonicalizeHostname(input) {
|
|
454
|
+
const withoutTrailingDot = stripTrailingDots(input.trim().replace(/^\[|\]$/g, ""));
|
|
455
|
+
if (!withoutTrailingDot) throw new Error("hostname is required");
|
|
456
|
+
rejectAlternateNumericAddress(withoutTrailingDot);
|
|
457
|
+
const ipVersion = isIP(withoutTrailingDot);
|
|
458
|
+
if (ipVersion === 4) return withoutTrailingDot;
|
|
459
|
+
if (ipVersion === 6) return new URL(`http://[${withoutTrailingDot}]/`).hostname.slice(1, -1);
|
|
460
|
+
if (/[\s/:@?#[\]]/.test(withoutTrailingDot)) throw new Error("invalid hostname");
|
|
461
|
+
const ascii = domainToASCII(withoutTrailingDot.toLowerCase());
|
|
462
|
+
if (!ascii || ascii.length > 253) throw new Error("invalid hostname");
|
|
463
|
+
return ascii;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Credential destinations deliberately support only exact hosts, a global
|
|
467
|
+
* wildcard, or a leading one-label wildcard. Arbitrary globs are too
|
|
468
|
+
* ambiguous for a secret-delivery boundary.
|
|
469
|
+
*/
|
|
470
|
+
function canonicalizeCredentialHostPattern(input) {
|
|
471
|
+
const pattern = input.trim();
|
|
472
|
+
if (pattern === "*") return pattern;
|
|
473
|
+
if (pattern.startsWith("*.") && pattern.indexOf("*", 1) === -1) return `*.${canonicalizeHostname(pattern.slice(2))}`;
|
|
474
|
+
if (pattern.includes("*")) throw new Error("credential host patterns allow only a leading *.");
|
|
475
|
+
return canonicalizeHostname(pattern);
|
|
476
|
+
}
|
|
477
|
+
function credentialHostMatches(hostnameInput, patternInput) {
|
|
478
|
+
const hostname = canonicalizeHostname(hostnameInput);
|
|
479
|
+
const pattern = canonicalizeCredentialHostPattern(patternInput);
|
|
480
|
+
if (pattern === "*") return true;
|
|
481
|
+
if (!pattern.startsWith("*.")) return hostname === pattern;
|
|
482
|
+
const suffix = pattern.slice(2);
|
|
483
|
+
if (!hostname.endsWith(`.${suffix}`)) return false;
|
|
484
|
+
const prefix = hostname.slice(0, -(suffix.length + 1));
|
|
485
|
+
return prefix.length > 0 && !prefix.includes(".");
|
|
486
|
+
}
|
|
487
|
+
/** Keep Gondolin's wider network glob syntax separate from secret patterns. */
|
|
488
|
+
function normalizeNetworkHostPattern(input) {
|
|
489
|
+
const pattern = input.trim();
|
|
490
|
+
if (pattern === "*") return pattern;
|
|
491
|
+
if (!pattern.includes("*")) return canonicalizeHostname(pattern);
|
|
492
|
+
const normalized = stripTrailingDots(pattern.toLowerCase());
|
|
493
|
+
if (normalized === "" || normalized.includes("://") || normalized.includes("/") || normalized.includes(":") || /\s/.test(normalized)) throw new Error("invalid network host pattern");
|
|
494
|
+
return normalized;
|
|
495
|
+
}
|
|
496
|
+
function networkPatternMatchesHostname(networkPatternInput, hostnameInput) {
|
|
497
|
+
const networkPattern = normalizeNetworkHostPattern(networkPatternInput);
|
|
498
|
+
const hostname = canonicalizeHostname(hostnameInput);
|
|
499
|
+
const expression = networkPattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
500
|
+
return new RegExp(`^${expression}$`, "i").test(hostname);
|
|
501
|
+
}
|
|
502
|
+
/** Prove that a network grant covers a narrower credential destination. */
|
|
503
|
+
function networkPatternCoversCredentialPattern(networkPatternInput, credentialPatternInput) {
|
|
504
|
+
const networkPattern = normalizeNetworkHostPattern(networkPatternInput);
|
|
505
|
+
const credentialPattern = canonicalizeCredentialHostPattern(credentialPatternInput);
|
|
506
|
+
if (networkPattern === "*") return true;
|
|
507
|
+
if (credentialPattern.includes("*")) return networkPattern === credentialPattern;
|
|
508
|
+
return networkPatternMatchesHostname(networkPattern, credentialPattern);
|
|
509
|
+
}
|
|
510
|
+
//#endregion
|
|
428
511
|
//#region src/host-origins.ts
|
|
429
512
|
function hostOriginHostnames(origins) {
|
|
430
513
|
return Object.keys(origins).map((origin) => new URL(origin).hostname).sort();
|
|
@@ -660,16 +743,6 @@ var OBJECT_META_PROPERTY_NAMES = new Set([
|
|
|
660
743
|
"constructor",
|
|
661
744
|
"prototype"
|
|
662
745
|
]);
|
|
663
|
-
function normalizeHostPattern(pattern) {
|
|
664
|
-
let normalized = pattern.trim().toLowerCase();
|
|
665
|
-
if (normalized.startsWith("[") && normalized.endsWith("]")) normalized = normalized.slice(1, -1);
|
|
666
|
-
if (normalized.endsWith(".")) normalized = normalized.slice(0, -1);
|
|
667
|
-
return normalized;
|
|
668
|
-
}
|
|
669
|
-
function isValidHostPattern(pattern) {
|
|
670
|
-
if (isIP(pattern) !== 0) return true;
|
|
671
|
-
return pattern !== "" && !pattern.includes("://") && !pattern.includes("/") && !pattern.includes(":") && !/\s/.test(pattern);
|
|
672
|
-
}
|
|
673
746
|
/**
|
|
674
747
|
* Canonicalize and validate the value-free portion of a brokered binding.
|
|
675
748
|
* Runtime attestation and Gondolin enforcement both consume this function so
|
|
@@ -679,15 +752,20 @@ function canonicalizeBrokeredHttpSecretDescriptor(descriptor) {
|
|
|
679
752
|
const issues = [];
|
|
680
753
|
const id = descriptor.id.trim();
|
|
681
754
|
const guestEnv = descriptor.guestEnv.trim();
|
|
682
|
-
const hosts = [
|
|
755
|
+
const hosts = [];
|
|
683
756
|
const protocolInput = descriptor.protocol ?? "https";
|
|
684
757
|
const protocol = protocolInput === "http" ? "http" : "https";
|
|
685
758
|
const ports = [...new Set(descriptor.ports ?? [protocol === "https" ? 443 : 80])].sort((left, right) => left - right);
|
|
686
759
|
if (!BROKERED_SECRET_ID_REGEXP.test(id)) issues.push(`invalid requirement id "${id || "<empty>"}"`);
|
|
687
760
|
if (!GUEST_ENV_NAME_REGEXP.test(guestEnv)) issues.push(`requirement "${id}" has invalid guest env "${guestEnv}"`);
|
|
688
761
|
else if (isReservedGuestEnvironmentName(guestEnv) || OBJECT_META_PROPERTY_NAMES.has(guestEnv)) issues.push(`requirement "${id}" uses reserved guest env "${guestEnv}"`);
|
|
689
|
-
|
|
690
|
-
|
|
762
|
+
for (const hostInput of descriptor.hosts) try {
|
|
763
|
+
hosts.push(canonicalizeCredentialHostPattern(hostInput));
|
|
764
|
+
} catch {
|
|
765
|
+
issues.push(`requirement "${id}" has invalid host pattern "${hostInput.trim()}"`);
|
|
766
|
+
}
|
|
767
|
+
const uniqueHosts = [...new Set(hosts)].sort();
|
|
768
|
+
if (descriptor.hosts.length === 0) issues.push(`requirement "${id}" has no destination hosts`);
|
|
691
769
|
if (protocolInput !== "https" && protocolInput !== "http") issues.push(`requirement "${id}" has invalid protocol "${String(protocolInput)}"`);
|
|
692
770
|
if (ports.length === 0) issues.push(`requirement "${id}" has no destination ports`);
|
|
693
771
|
for (const port of ports) if (!Number.isInteger(port) || port < 1 || port > 65535) issues.push(`requirement "${id}" has invalid port "${port}"`);
|
|
@@ -695,16 +773,12 @@ function canonicalizeBrokeredHttpSecretDescriptor(descriptor) {
|
|
|
695
773
|
return Object.freeze({
|
|
696
774
|
id,
|
|
697
775
|
guestEnv,
|
|
698
|
-
hosts: Object.freeze(
|
|
776
|
+
hosts: Object.freeze(uniqueHosts),
|
|
699
777
|
protocol,
|
|
700
778
|
ports: Object.freeze(ports),
|
|
701
779
|
required: descriptor.required !== false
|
|
702
780
|
});
|
|
703
781
|
}
|
|
704
|
-
function hostMatchesPattern(hostname, pattern) {
|
|
705
|
-
const expression = normalizeHostPattern(pattern).split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
706
|
-
return new RegExp(`^${expression}$`, "i").test(normalizeHostPattern(hostname));
|
|
707
|
-
}
|
|
708
782
|
/**
|
|
709
783
|
* Conservative subset check for Gondolin hostname globs. Exact secret hosts
|
|
710
784
|
* may sit below a broader network wildcard. A wildcard secret destination must
|
|
@@ -712,9 +786,7 @@ function hostMatchesPattern(hostname, pattern) {
|
|
|
712
786
|
* through an ambiguous glob comparison.
|
|
713
787
|
*/
|
|
714
788
|
function networkPatternCoversSecretPattern(networkPattern, secretPattern) {
|
|
715
|
-
|
|
716
|
-
if (secretPattern.includes("*")) return networkPattern === secretPattern;
|
|
717
|
-
return hostMatchesPattern(secretPattern, networkPattern);
|
|
789
|
+
return networkPatternCoversCredentialPattern(networkPattern, secretPattern);
|
|
718
790
|
}
|
|
719
791
|
/**
|
|
720
792
|
* Validate value-free descriptors, host-local bindings, environment
|
|
@@ -725,7 +797,7 @@ function prepareBrokeredHttpSecrets(options) {
|
|
|
725
797
|
const ids = /* @__PURE__ */ new Set();
|
|
726
798
|
const guestEnvNames = /* @__PURE__ */ new Set();
|
|
727
799
|
const occupiedGuestEnvNames = new Set(options.occupiedGuestEnvNames ?? []);
|
|
728
|
-
const allowedHosts = [...new Set(options.allowedHosts.map(
|
|
800
|
+
const allowedHosts = [...new Set(options.allowedHosts.map(normalizeNetworkHostPattern))];
|
|
729
801
|
const secrets = Object.create(null);
|
|
730
802
|
for (const binding of options.bindings ?? []) {
|
|
731
803
|
let descriptor;
|
|
@@ -780,7 +852,7 @@ function parseRequestOrigin(url) {
|
|
|
780
852
|
const parsed = new URL(url);
|
|
781
853
|
const protocol = parsed.protocol.slice(0, -1);
|
|
782
854
|
return {
|
|
783
|
-
hostname:
|
|
855
|
+
hostname: canonicalizeHostname(parsed.hostname),
|
|
784
856
|
protocol,
|
|
785
857
|
port: parsed.port ? Number(parsed.port) : protocol === "https" ? 443 : 80
|
|
786
858
|
};
|
|
@@ -808,7 +880,7 @@ function createBrokeredHttpSecretOriginPolicy(bindings) {
|
|
|
808
880
|
if (!requestOrigin) return false;
|
|
809
881
|
for (const entry of entries.values()) {
|
|
810
882
|
if (entry.deleted || !headersContainSecretValue(request.headers, entry.value)) continue;
|
|
811
|
-
if (requestOrigin.protocol !== entry.protocol || !entry.ports.includes(requestOrigin.port) || !entry.hosts.some((host) =>
|
|
883
|
+
if (requestOrigin.protocol !== entry.protocol || !entry.ports.includes(requestOrigin.port) || !entry.hosts.some((host) => credentialHostMatches(requestOrigin.hostname, host))) return false;
|
|
812
884
|
}
|
|
813
885
|
return true;
|
|
814
886
|
},
|
|
@@ -833,15 +905,21 @@ function createBrokeredHttpSecretOriginPolicy(bindings) {
|
|
|
833
905
|
function createBrokeredHttpNetworkOriginPolicy(bindings, options = {}) {
|
|
834
906
|
const origins = bindings.map(canonicalizeBrokeredHttpSecretDescriptor);
|
|
835
907
|
const isAllowed = (input) => {
|
|
836
|
-
|
|
837
|
-
|
|
908
|
+
let hostname;
|
|
909
|
+
try {
|
|
910
|
+
hostname = canonicalizeHostname(input.hostname);
|
|
911
|
+
} catch {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
const matching = origins.filter((origin) => origin.hosts.some((host) => credentialHostMatches(hostname, host)));
|
|
838
915
|
if (matching.length === 0) return true;
|
|
839
916
|
const allowed = matching.some((origin) => origin.protocol === input.protocol && origin.ports.includes(input.port));
|
|
840
|
-
|
|
917
|
+
options.onDecision?.({
|
|
841
918
|
hostname,
|
|
842
919
|
protocol: input.protocol,
|
|
843
920
|
port: input.port,
|
|
844
|
-
phase: input.phase
|
|
921
|
+
phase: input.phase,
|
|
922
|
+
allowed
|
|
845
923
|
});
|
|
846
924
|
return allowed;
|
|
847
925
|
};
|
|
@@ -970,6 +1048,16 @@ async function resumeVm(config) {
|
|
|
970
1048
|
const hostOriginHosts = hostOriginHostnames(hostOrigins);
|
|
971
1049
|
assertInternalHostsDoNotOverlapProtectedHosts(hostOriginHosts, protectedExternalHosts);
|
|
972
1050
|
const allowedInternalHosts = [...new Set([...runtimeAllowedInternalHosts, ...hostOriginHosts])];
|
|
1051
|
+
const requestedHostnamePolicy = Object.freeze({
|
|
1052
|
+
allowedHosts: Object.freeze([...allowedHosts].sort()),
|
|
1053
|
+
allowedInternalHosts: Object.freeze([...allowedInternalHosts].sort())
|
|
1054
|
+
});
|
|
1055
|
+
config.onDiagnostic?.({
|
|
1056
|
+
event: "vm.network.policy_bound",
|
|
1057
|
+
level: "info",
|
|
1058
|
+
message: "Passed the complete requested hostname policy to Gondolin",
|
|
1059
|
+
hostnamePolicy: requestedHostnamePolicy
|
|
1060
|
+
});
|
|
973
1061
|
const projectedEnv = config.guestProjection?.env ?? {};
|
|
974
1062
|
const brokeredSecrets = prepareBrokeredHttpSecrets({
|
|
975
1063
|
bindings: config.brokeredSecrets,
|
|
@@ -987,12 +1075,18 @@ async function resumeVm(config) {
|
|
|
987
1075
|
]
|
|
988
1076
|
});
|
|
989
1077
|
const brokeredSecretOriginPolicy = createBrokeredHttpSecretOriginPolicy(config.brokeredSecrets ?? []);
|
|
990
|
-
const brokeredNetworkOriginPolicy = createBrokeredHttpNetworkOriginPolicy(config.brokeredSecrets ?? [], {
|
|
1078
|
+
const brokeredNetworkOriginPolicy = createBrokeredHttpNetworkOriginPolicy(config.brokeredSecrets ?? [], { onDecision(decision) {
|
|
991
1079
|
config.onDiagnostic?.({
|
|
1080
|
+
event: "vm.network.origin_checked",
|
|
1081
|
+
level: "info",
|
|
1082
|
+
message: "Checked a canonical brokered credential origin",
|
|
1083
|
+
origin: decision
|
|
1084
|
+
});
|
|
1085
|
+
if (!decision.allowed) config.onDiagnostic?.({
|
|
992
1086
|
event: "vm.network.origin_denied",
|
|
993
1087
|
level: "warning",
|
|
994
|
-
message: "
|
|
995
|
-
origin
|
|
1088
|
+
message: "Denied a request outside a brokered credential origin",
|
|
1089
|
+
origin: decision
|
|
996
1090
|
});
|
|
997
1091
|
} });
|
|
998
1092
|
const { httpHooks, env: secretEnv, secretManager: gondolinSecretManager } = createHttpHooks({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/sandbox-gondolin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
},
|
|
34
34
|
"nx": {
|
|
35
35
|
"tags": [
|
|
36
|
-
"type:
|
|
36
|
+
"type:sandbox",
|
|
37
37
|
"scope:agent",
|
|
38
|
-
"platform:
|
|
38
|
+
"platform:server"
|
|
39
39
|
],
|
|
40
40
|
"targets": {
|
|
41
41
|
"test-ci": {
|