@mono-agent/agent-app 0.9.0 → 0.10.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 +13 -1
- package/dist/background-runtime.d.ts +32 -0
- package/dist/background-runtime.d.ts.map +1 -1
- package/dist/background-runtime.js +396 -36
- package/dist/background-runtime.js.map +1 -1
- package/dist/background-snapshot-key.d.ts +2 -0
- package/dist/background-snapshot-key.d.ts.map +1 -1
- package/dist/background-snapshot-key.js +32 -1
- package/dist/background-snapshot-key.js.map +1 -1
- package/dist/channels.js +18 -3
- package/dist/channels.js.map +1 -1
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -1
- package/dist/launchd.d.ts +2 -0
- package/dist/launchd.d.ts.map +1 -1
- package/dist/launchd.js +46 -17
- package/dist/launchd.js.map +1 -1
- package/dist/managed-runtime-packages.js +1 -1
- package/dist/managed-runtime-packages.js.map +1 -1
- package/dist/memory-command.d.ts +6 -0
- package/dist/memory-command.d.ts.map +1 -1
- package/dist/memory-command.js +363 -3
- package/dist/memory-command.js.map +1 -1
- package/package.json +15 -15
- package/skills/mono-agent-composer/references/config-blueprint.md +7 -1
- package/skills/mono-agent-composer/references/discovery-questions.md +1 -1
- package/skills/mono-agent-composer/references/feature-coverage.md +2 -2
- package/skills/mono-agent-composer/references/playbooks.md +3 -3
|
@@ -11,6 +11,7 @@ export const MANAGED_BACKGROUND_WORKER_ENV = "MONO_AGENT_MANAGED_WORKER";
|
|
|
11
11
|
const LOCK_WAIT_TIMEOUT_MS = 30_000;
|
|
12
12
|
const LOCK_STALE_AFTER_MS = 5 * 60_000;
|
|
13
13
|
const LOCK_POLL_INTERVAL_MS = 200;
|
|
14
|
+
const PROVISIONAL_RUNTIME_INSTALLED_AT = "1970-01-01T00:00:00.000Z";
|
|
14
15
|
export function defaultManagedBackgroundRuntimeDeps() {
|
|
15
16
|
return {
|
|
16
17
|
now: () => Date.now(),
|
|
@@ -72,20 +73,23 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
|
|
|
72
73
|
const id = safeSegment(deps.randomId());
|
|
73
74
|
const layout = runtimeLayout(home, identity, id, deps.now());
|
|
74
75
|
await ensurePrivateRuntimeAncestors(home, layout.versionAbiDir);
|
|
75
|
-
if (await verifyRuntime(layout, identity)) {
|
|
76
|
+
if (await verifyRuntime(layout, identity, additionalPackages)) {
|
|
77
|
+
await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
|
|
76
78
|
return runtimeResult(layout, identity, input.nodePath);
|
|
77
79
|
}
|
|
78
|
-
const acquired = await acquireRuntimeLock(layout, identity, deps);
|
|
80
|
+
const acquired = await acquireRuntimeLock(layout, identity, additionalPackages, deps);
|
|
79
81
|
if (!acquired) {
|
|
80
82
|
// Another installer may have completed between the final lock poll and the
|
|
81
83
|
// timeout boundary. Verify one last time before reporting the contention.
|
|
82
|
-
if (await verifyRuntime(layout, identity)) {
|
|
84
|
+
if (await verifyRuntime(layout, identity, additionalPackages)) {
|
|
85
|
+
await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
|
|
83
86
|
return runtimeResult(layout, identity, input.nodePath);
|
|
84
87
|
}
|
|
85
88
|
throw new Error(`Timed out waiting for the managed runtime installation lock at ${layout.lockDir}.`);
|
|
86
89
|
}
|
|
87
90
|
try {
|
|
88
|
-
if (await verifyRuntime(layout, identity)) {
|
|
91
|
+
if (await verifyRuntime(layout, identity, additionalPackages)) {
|
|
92
|
+
await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
|
|
89
93
|
return runtimeResult(layout, identity, input.nodePath);
|
|
90
94
|
}
|
|
91
95
|
await assertSourceClosureUnchanged(packageSource, additionalPackages, sourceClosure, "before staging");
|
|
@@ -112,12 +116,24 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
|
|
|
112
116
|
throw new Error(`Installed ${PACKAGE_NAME}@${packageVersion} does not match the executing CLI SHA-256 ${identity.cliSha256}.`);
|
|
113
117
|
}
|
|
114
118
|
const closureManifestSha256 = await writeClosureManifest(staged);
|
|
119
|
+
const stagedPackageSource = dirname(dirname(staged.cliPath));
|
|
120
|
+
const stagedAdditionalPackages = additionalPackages.map(({ packageName }) => ({
|
|
121
|
+
packageName,
|
|
122
|
+
packageSource: join(staged.installRoot, "node_modules", ...packageNameSegments(packageName)),
|
|
123
|
+
}));
|
|
124
|
+
const stagedExecutionClosure = await captureExecutionClosure(stagedPackageSource, stagedAdditionalPackages, staged.installRoot);
|
|
125
|
+
if (stagedExecutionClosure.sourceClosureSha256 !== identity.sourceClosureSha256) {
|
|
126
|
+
throw new Error("The staged managed runtime execution closure does not match its source identity.");
|
|
127
|
+
}
|
|
115
128
|
const marker = {
|
|
116
|
-
schema: "mono-agent.managed-runtime.
|
|
129
|
+
schema: "mono-agent.managed-runtime.v4",
|
|
117
130
|
packageName: PACKAGE_NAME,
|
|
118
131
|
closureManifestSha256,
|
|
132
|
+
executionProofSha256: stagedExecutionClosure.filesystemProofSha256,
|
|
119
133
|
...identity,
|
|
120
|
-
|
|
134
|
+
// Provisional only. Promotion rewrites this with the post-proof launch
|
|
135
|
+
// boundary; a crash before that rewrite must never look finalized.
|
|
136
|
+
installedAt: PROVISIONAL_RUNTIME_INSTALLED_AT,
|
|
121
137
|
};
|
|
122
138
|
await writePrivateJson(staged.markerPath, marker);
|
|
123
139
|
await chmod(layout.stagingDir, 0o700);
|
|
@@ -125,22 +141,124 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
|
|
|
125
141
|
// runtime aside. This is essential when the executing CLI (and therefore
|
|
126
142
|
// packageSource) lives inside that invalid runtime: quarantine must never
|
|
127
143
|
// make the only repair source disappear halfway through installation.
|
|
128
|
-
await quarantineInvalidRuntime(layout, identity, deps);
|
|
129
|
-
await promoteStaging(layout, staged, identity);
|
|
144
|
+
await quarantineInvalidRuntime(layout, identity, additionalPackages, deps);
|
|
145
|
+
const promoted = await promoteStaging(layout, staged, identity, additionalPackages);
|
|
146
|
+
if (promoted) {
|
|
147
|
+
// Renaming the staging root changes that directory's ctime. Rebind the
|
|
148
|
+
// marker to the post-promotion resolution-path identities before any
|
|
149
|
+
// caller can persist or launch this runtime.
|
|
150
|
+
const promotedPackageSource = dirname(dirname(layout.cliPath));
|
|
151
|
+
const promotedAdditionalPackages = additionalPackages.map(({ packageName }) => ({
|
|
152
|
+
packageName,
|
|
153
|
+
packageSource: join(layout.installRoot, "node_modules", ...packageNameSegments(packageName)),
|
|
154
|
+
}));
|
|
155
|
+
const promotedClosure = await captureExecutionClosure(promotedPackageSource, promotedAdditionalPackages, layout.installRoot);
|
|
156
|
+
if (promotedClosure.sourceClosureSha256 !== identity.sourceClosureSha256) {
|
|
157
|
+
throw new Error("The promoted managed runtime execution closure does not match its source identity.");
|
|
158
|
+
}
|
|
159
|
+
await writeFinalizedRuntimeMarker(layout.markerPath, {
|
|
160
|
+
...marker,
|
|
161
|
+
executionProofSha256: promotedClosure.filesystemProofSha256,
|
|
162
|
+
}, deps);
|
|
163
|
+
}
|
|
130
164
|
}
|
|
131
165
|
catch (error) {
|
|
132
166
|
await rm(layout.stagingDir, { recursive: true, force: true }).catch(() => undefined);
|
|
133
167
|
throw error;
|
|
134
168
|
}
|
|
135
|
-
if (!(await verifyRuntime(layout, identity))) {
|
|
169
|
+
if (!(await verifyRuntime(layout, identity, additionalPackages))) {
|
|
136
170
|
throw new Error("The managed runtime failed verification after atomic promotion.");
|
|
137
171
|
}
|
|
172
|
+
await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
|
|
138
173
|
return runtimeResult(layout, identity, input.nodePath);
|
|
139
174
|
}
|
|
140
175
|
finally {
|
|
141
176
|
await removeSameRuntimeLockDirectory(layout.lockDir, acquired).catch(() => undefined);
|
|
142
177
|
}
|
|
143
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Prove, without installing or repairing anything, that a persisted managed
|
|
181
|
+
* runtime is the canonical private copy of the exact deploy-source execution
|
|
182
|
+
* closure (including config-selected plugin roots).
|
|
183
|
+
*/
|
|
184
|
+
export async function attestManagedBackgroundRuntime(input, deps = {}) {
|
|
185
|
+
const currentCliPath = resolve(input.currentCliPath);
|
|
186
|
+
await assertRegularFile(currentCliPath, "current mono-agent CLI");
|
|
187
|
+
const packageSource = resolve(input.packageSource ?? packageRootForCli(currentCliPath));
|
|
188
|
+
const sourceDetails = await lstat(packageSource);
|
|
189
|
+
if (!sourceDetails.isDirectory() || sourceDetails.isSymbolicLink()) {
|
|
190
|
+
throw new Error(`Managed runtime package source ${packageSource} must be a real directory.`);
|
|
191
|
+
}
|
|
192
|
+
const additionalPackages = await canonicalAdditionalPackages(input.additionalPackages ?? []);
|
|
193
|
+
const sourceClosureInitial = await captureExecutionClosure(packageSource, additionalPackages);
|
|
194
|
+
const packageVersion = input.packageVersion ?? await packageVersionAt(packageSource);
|
|
195
|
+
if (!isExactVersion(packageVersion)) {
|
|
196
|
+
throw new Error(`Cannot attest a durable managed runtime for invalid package version ${JSON.stringify(packageVersion)}.`);
|
|
197
|
+
}
|
|
198
|
+
const identity = {
|
|
199
|
+
packageVersion,
|
|
200
|
+
cliSha256: sha256(await readFile(currentCliPath)),
|
|
201
|
+
sourceClosureSha256: sourceClosureInitial.sourceClosureSha256,
|
|
202
|
+
nodeAbi: input.nodeAbi ?? requiredNodeAbi(),
|
|
203
|
+
platform: input.platform ?? process.platform,
|
|
204
|
+
arch: input.arch ?? process.arch,
|
|
205
|
+
};
|
|
206
|
+
const home = resolve(input.homeDir ?? accountHomeDirectory());
|
|
207
|
+
const layout = runtimeLayout(home, identity, "attestation", 0);
|
|
208
|
+
if (resolve(input.runtimeCliPath) !== layout.cliPath) {
|
|
209
|
+
throw new Error("The managed runtime CLI is not at the canonical execution-closure path.");
|
|
210
|
+
}
|
|
211
|
+
if (!(await verifyPrivateRuntimeAncestors(home, layout.versionAbiDir))) {
|
|
212
|
+
throw new Error("The managed runtime has an unsafe ancestor directory.");
|
|
213
|
+
}
|
|
214
|
+
const markerInitial = await verifiedRuntimeMarker(layout, identity);
|
|
215
|
+
if (markerInitial === undefined) {
|
|
216
|
+
throw new Error("The managed runtime marker or closure manifest is invalid.");
|
|
217
|
+
}
|
|
218
|
+
const runtimePackageSource = dirname(dirname(layout.cliPath));
|
|
219
|
+
const runtimeAdditionalPackages = additionalPackages.map(({ packageName }) => ({
|
|
220
|
+
packageName,
|
|
221
|
+
packageSource: join(layout.installRoot, "node_modules", ...packageNameSegments(packageName)),
|
|
222
|
+
}));
|
|
223
|
+
const runtimeClosureInitial = await captureExecutionClosure(runtimePackageSource, runtimeAdditionalPackages, layout.installRoot);
|
|
224
|
+
if (runtimeClosureInitial.sourceClosureSha256 !== sourceClosureInitial.sourceClosureSha256) {
|
|
225
|
+
throw new Error("The managed runtime execution closure does not match the deploy source closure.");
|
|
226
|
+
}
|
|
227
|
+
if (runtimeClosureInitial.filesystemProofSha256 !== markerInitial.executionProofSha256) {
|
|
228
|
+
throw new Error("The managed runtime filesystem proof does not match its install-time proof.");
|
|
229
|
+
}
|
|
230
|
+
await deps.afterInitialCaptures?.();
|
|
231
|
+
const sourceClosureFinal = await captureExecutionClosure(packageSource, additionalPackages);
|
|
232
|
+
if (sourceClosureFinal.sourceClosureSha256 !== sourceClosureInitial.sourceClosureSha256
|
|
233
|
+
|| sourceClosureFinal.sourceProofSha256 !== sourceClosureInitial.sourceProofSha256) {
|
|
234
|
+
throw new Error("The deploy source execution closure changed while it was attested.");
|
|
235
|
+
}
|
|
236
|
+
const runtimeClosureFinal = await captureExecutionClosure(runtimePackageSource, runtimeAdditionalPackages, layout.installRoot);
|
|
237
|
+
if (runtimeClosureFinal.sourceClosureSha256 !== runtimeClosureInitial.sourceClosureSha256
|
|
238
|
+
|| runtimeClosureFinal.sourceProofSha256 !== runtimeClosureInitial.sourceProofSha256) {
|
|
239
|
+
throw new Error("The managed runtime execution closure changed while it was attested.");
|
|
240
|
+
}
|
|
241
|
+
if (runtimeClosureFinal.filesystemProofSha256 !== markerInitial.executionProofSha256) {
|
|
242
|
+
throw new Error("The managed runtime filesystem proof changed after installation.");
|
|
243
|
+
}
|
|
244
|
+
const markerFinal = await verifiedRuntimeMarker(layout, identity);
|
|
245
|
+
if (markerFinal === undefined || JSON.stringify(markerFinal) !== JSON.stringify(markerInitial)) {
|
|
246
|
+
throw new Error("The managed runtime changed while it was attested.");
|
|
247
|
+
}
|
|
248
|
+
const fingerprint = sha256(Buffer.from(JSON.stringify({
|
|
249
|
+
schema: "mono-agent.managed-runtime-attestation.v1",
|
|
250
|
+
identity,
|
|
251
|
+
sourceProofSha256: sourceClosureFinal.sourceProofSha256,
|
|
252
|
+
runtimeProofSha256: runtimeClosureFinal.sourceProofSha256,
|
|
253
|
+
closureManifestSha256: markerInitial.closureManifestSha256,
|
|
254
|
+
installedAt: markerInitial.installedAt,
|
|
255
|
+
}), "utf8"));
|
|
256
|
+
return {
|
|
257
|
+
schema: "mono-agent.managed-runtime-attestation.v1",
|
|
258
|
+
fingerprint,
|
|
259
|
+
installedAt: markerInitial.installedAt,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
144
262
|
/**
|
|
145
263
|
* Copy the exact package graph the current CLI is already executing.
|
|
146
264
|
*
|
|
@@ -186,7 +304,7 @@ async function materializeExactExecutionClosure(input) {
|
|
|
186
304
|
}
|
|
187
305
|
for (const node of closure) {
|
|
188
306
|
const destination = requiredMapValue(destinations, node.id, "package destination");
|
|
189
|
-
for (const [dependencyName, dependencyId] of [...node.dependencies].sort(([left], [right]) => left
|
|
307
|
+
for (const [dependencyName, dependencyId] of [...node.dependencies].sort(([left], [right]) => compareCodeUnits(left, right))) {
|
|
190
308
|
const dependencyDestination = requiredMapValue(destinations, dependencyId, "dependency destination");
|
|
191
309
|
const linkPath = join(destination, "node_modules", ...packageNameSegments(dependencyName));
|
|
192
310
|
await mkdir(dirname(linkPath), { recursive: true, mode: 0o700 });
|
|
@@ -204,7 +322,7 @@ async function materializeExactExecutionClosure(input) {
|
|
|
204
322
|
return [portableRelativePath(input.stagingDir, destination), {
|
|
205
323
|
name: node.metadata.name,
|
|
206
324
|
version: node.metadata.version,
|
|
207
|
-
dependencies: Object.fromEntries([...node.dependencies].sort(([left], [right]) => left
|
|
325
|
+
dependencies: Object.fromEntries([...node.dependencies].sort(([left], [right]) => compareCodeUnits(left, right)).map(([dependencyName, dependencyId]) => [
|
|
208
326
|
dependencyName,
|
|
209
327
|
requiredClosureNode(closure, dependencyId).metadata.version,
|
|
210
328
|
])),
|
|
@@ -235,12 +353,18 @@ async function materializeExactExecutionClosure(input) {
|
|
|
235
353
|
throw new Error("The managed runtime package closure changed while it was staged.");
|
|
236
354
|
}
|
|
237
355
|
}
|
|
238
|
-
async function captureExecutionClosure(packageSource, additionalPackages) {
|
|
356
|
+
async function captureExecutionClosure(packageSource, additionalPackages, filesystemProofRoot) {
|
|
239
357
|
const nodes = [];
|
|
240
358
|
const bySourceRoot = new Map();
|
|
241
359
|
const proofs = new Map();
|
|
360
|
+
const dependencyProofs = [];
|
|
361
|
+
const resolutionPathProof = filesystemProofRoot === undefined
|
|
362
|
+
? undefined
|
|
363
|
+
: await createResolutionPathProof(filesystemProofRoot);
|
|
242
364
|
const visit = async (source) => {
|
|
365
|
+
await resolutionPathProof?.captureAncestors(source);
|
|
243
366
|
const sourceRoot = await realpath(source);
|
|
367
|
+
await resolutionPathProof?.captureAncestors(sourceRoot);
|
|
244
368
|
const existing = bySourceRoot.get(sourceRoot);
|
|
245
369
|
if (existing !== undefined)
|
|
246
370
|
return existing;
|
|
@@ -258,29 +382,51 @@ async function captureExecutionClosure(packageSource, additionalPackages) {
|
|
|
258
382
|
proofs.set(id, sourcePackage.proof);
|
|
259
383
|
bySourceRoot.set(sourceRoot, id);
|
|
260
384
|
for (const [dependencyName, optional] of dependencyRequirements(metadata)) {
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
385
|
+
const dependency = await resolveInstalledDependencyPackageRoot(sourceRoot, dependencyName);
|
|
386
|
+
if (dependency === undefined) {
|
|
263
387
|
if (optional)
|
|
264
388
|
continue;
|
|
265
389
|
throw new Error(`Cannot preserve the executing dependency closure: ${metadata.name}@${metadata.version} ` +
|
|
266
390
|
`cannot resolve required dependency ${dependencyName}. Build or install the package completely before starting it.`);
|
|
267
391
|
}
|
|
268
|
-
|
|
392
|
+
await resolutionPathProof?.captureAncestors(dependency.candidatePath);
|
|
393
|
+
const dependencyNodeId = await visit(dependency.packageRoot);
|
|
394
|
+
const [dependencyProofAfter, dependencyRootAfter] = await Promise.all([
|
|
395
|
+
captureFilesystemPathProof(dependency.candidatePath, dependencyName),
|
|
396
|
+
realpath(dependency.candidatePath),
|
|
397
|
+
]);
|
|
398
|
+
if (dependencyRootAfter !== dependency.packageRoot
|
|
399
|
+
|| !sameSourceProofEntry(dependency.proof, dependencyProofAfter)) {
|
|
400
|
+
throw new Error(`Installed dependency ${dependencyName} changed while its execution closure was inspected.`);
|
|
401
|
+
}
|
|
402
|
+
dependencyProofs.push({ ownerId: id, dependencyName, proof: dependencyProofAfter });
|
|
403
|
+
node.dependencies.set(dependencyName, dependencyNodeId);
|
|
269
404
|
}
|
|
270
405
|
return id;
|
|
271
406
|
};
|
|
272
407
|
await visit(packageSource);
|
|
273
408
|
const additionalRoots = [];
|
|
409
|
+
const additionalProofs = [];
|
|
274
410
|
for (const additional of additionalPackages) {
|
|
411
|
+
const additionalProof = await captureFilesystemPathProof(additional.packageSource, additional.packageName);
|
|
275
412
|
const nodeId = await visit(additional.packageSource);
|
|
276
413
|
const node = requiredClosureNode(nodes, nodeId);
|
|
277
414
|
if (node.metadata.name !== additional.packageName) {
|
|
278
415
|
throw new Error(`Additional managed runtime package ${additional.packageSource} declares ${node.metadata.name}, ` +
|
|
279
416
|
`expected ${additional.packageName}.`);
|
|
280
417
|
}
|
|
418
|
+
const [additionalProofAfter, additionalRootAfter] = await Promise.all([
|
|
419
|
+
captureFilesystemPathProof(additional.packageSource, additional.packageName),
|
|
420
|
+
realpath(additional.packageSource),
|
|
421
|
+
]);
|
|
422
|
+
if (additionalRootAfter !== node.sourceRoot || !sameSourceProofEntry(additionalProof, additionalProofAfter)) {
|
|
423
|
+
throw new Error(`Additional managed runtime package ${additional.packageName} changed while its execution closure was inspected.`);
|
|
424
|
+
}
|
|
425
|
+
additionalProofs.push({ packageName: additional.packageName, proof: additionalProofAfter });
|
|
281
426
|
additionalRoots.push({ packageName: additional.packageName, nodeId });
|
|
282
427
|
}
|
|
283
428
|
const closure = nodes;
|
|
429
|
+
const resolutionPaths = await resolutionPathProof?.finalize() ?? [];
|
|
284
430
|
const stable = {
|
|
285
431
|
schema: "mono-agent.source-execution-closure.v1",
|
|
286
432
|
roots: {
|
|
@@ -292,7 +438,7 @@ async function captureExecutionClosure(packageSource, additionalPackages) {
|
|
|
292
438
|
name: node.metadata.name,
|
|
293
439
|
version: node.metadata.version,
|
|
294
440
|
dependencies: [...node.dependencies]
|
|
295
|
-
.sort(([left], [right]) => left
|
|
441
|
+
.sort(([left], [right]) => compareCodeUnits(left, right))
|
|
296
442
|
.map(([name, nodeId]) => ({ name, nodeId })),
|
|
297
443
|
sourcePackage: node.sourcePackage,
|
|
298
444
|
})),
|
|
@@ -305,12 +451,26 @@ async function captureExecutionClosure(packageSource, additionalPackages) {
|
|
|
305
451
|
sourceRoot: node.sourceRoot,
|
|
306
452
|
entries: requiredMapValue(proofs, node.id, "source package proof"),
|
|
307
453
|
})),
|
|
454
|
+
dependencyLinks: dependencyProofs,
|
|
455
|
+
additionalLinks: additionalProofs,
|
|
456
|
+
resolutionPaths,
|
|
457
|
+
};
|
|
458
|
+
const filesystemProof = {
|
|
459
|
+
schema: "mono-agent.source-filesystem-proof.v1",
|
|
460
|
+
packages: closure.map((node) => ({
|
|
461
|
+
id: node.id,
|
|
462
|
+
entries: requiredMapValue(proofs, node.id, "source package proof"),
|
|
463
|
+
})),
|
|
464
|
+
dependencyLinks: dependencyProofs,
|
|
465
|
+
additionalLinks: additionalProofs,
|
|
466
|
+
resolutionPaths,
|
|
308
467
|
};
|
|
309
468
|
return {
|
|
310
469
|
nodes: closure,
|
|
311
470
|
additionalRoots,
|
|
312
471
|
sourceClosureSha256: sha256(Buffer.from(JSON.stringify(stable), "utf8")),
|
|
313
472
|
sourceProofSha256: sha256(Buffer.from(JSON.stringify(proof), "utf8")),
|
|
473
|
+
filesystemProofSha256: sha256(Buffer.from(JSON.stringify(filesystemProof), "utf8")),
|
|
314
474
|
};
|
|
315
475
|
}
|
|
316
476
|
async function canonicalAdditionalPackages(packages) {
|
|
@@ -334,7 +494,7 @@ async function canonicalAdditionalPackages(packages) {
|
|
|
334
494
|
byName.set(entry.packageName, canonical);
|
|
335
495
|
}
|
|
336
496
|
return [...byName]
|
|
337
|
-
.sort(([left], [right]) => left
|
|
497
|
+
.sort(([left], [right]) => compareCodeUnits(left, right))
|
|
338
498
|
.map(([packageName, packageSource]) => ({ packageName, packageSource }));
|
|
339
499
|
}
|
|
340
500
|
async function assertSourceClosureUnchanged(packageSource, additionalPackages, expected, phase) {
|
|
@@ -366,7 +526,7 @@ async function captureSourcePackage(packageRoot) {
|
|
|
366
526
|
}
|
|
367
527
|
entries.push({ path: pathRelative, type: "directory", mode: fileMode(before.mode) });
|
|
368
528
|
proof.push(sourceProofEntry(pathRelative, "directory", before));
|
|
369
|
-
const childNames = (await readdir(directory)).sort(
|
|
529
|
+
const childNames = (await readdir(directory)).sort(compareCodeUnits);
|
|
370
530
|
for (const name of childNames) {
|
|
371
531
|
const path = join(directory, name);
|
|
372
532
|
const childRelative = sourcePackageRelativePath(root, path);
|
|
@@ -395,7 +555,7 @@ async function captureSourcePackage(packageRoot) {
|
|
|
395
555
|
}
|
|
396
556
|
const [after, childNamesAfter] = await Promise.all([
|
|
397
557
|
lstat(directory, { bigint: true }),
|
|
398
|
-
readdir(directory).then((names) => names.sort(
|
|
558
|
+
readdir(directory).then((names) => names.sort(compareCodeUnits)),
|
|
399
559
|
]);
|
|
400
560
|
if (!sameSourceStats(before, after) || JSON.stringify(childNames) !== JSON.stringify(childNamesAfter)) {
|
|
401
561
|
throw new Error(`Managed runtime source package directory ${pathRelative} changed while it was inspected.`);
|
|
@@ -405,8 +565,8 @@ async function captureSourcePackage(packageRoot) {
|
|
|
405
565
|
if (packageJson === undefined) {
|
|
406
566
|
throw new Error(`Managed runtime source package ${root} has no regular package.json.`);
|
|
407
567
|
}
|
|
408
|
-
entries.sort((left, right) => left.path
|
|
409
|
-
proof.sort((left, right) => left.path
|
|
568
|
+
entries.sort((left, right) => compareCodeUnits(left.path, right.path));
|
|
569
|
+
proof.sort((left, right) => compareCodeUnits(left.path, right.path));
|
|
410
570
|
return {
|
|
411
571
|
manifest: { schema: "mono-agent.source-package.v1", entries },
|
|
412
572
|
proof,
|
|
@@ -517,6 +677,97 @@ function sameSourceStats(left, right) {
|
|
|
517
677
|
&& left.mtimeNs === right.mtimeNs
|
|
518
678
|
&& left.ctimeNs === right.ctimeNs;
|
|
519
679
|
}
|
|
680
|
+
function sameSourceProofEntry(left, right) {
|
|
681
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
682
|
+
}
|
|
683
|
+
async function captureFilesystemPathProof(path, logicalPath) {
|
|
684
|
+
const before = await lstat(path, { bigint: true });
|
|
685
|
+
const type = before.isSymbolicLink()
|
|
686
|
+
? "symlink"
|
|
687
|
+
: before.isDirectory()
|
|
688
|
+
? "directory"
|
|
689
|
+
: before.isFile()
|
|
690
|
+
? "file"
|
|
691
|
+
: undefined;
|
|
692
|
+
if (type === undefined) {
|
|
693
|
+
throw new Error(`Managed runtime package path ${logicalPath} has an unsupported filesystem type.`);
|
|
694
|
+
}
|
|
695
|
+
const target = type === "symlink" ? await readlink(path) : undefined;
|
|
696
|
+
const [after, targetAfter] = await Promise.all([
|
|
697
|
+
lstat(path, { bigint: true }),
|
|
698
|
+
type === "symlink" ? readlink(path) : Promise.resolve(undefined),
|
|
699
|
+
]);
|
|
700
|
+
if (!sameSourceStats(before, after)
|
|
701
|
+
|| (type === "symlink" && (!after.isSymbolicLink() || targetAfter !== target))
|
|
702
|
+
|| (type === "directory" && (!after.isDirectory() || after.isSymbolicLink()))
|
|
703
|
+
|| (type === "file" && (!after.isFile() || after.isSymbolicLink()))) {
|
|
704
|
+
throw new Error(`Managed runtime package path ${logicalPath} changed while it was inspected.`);
|
|
705
|
+
}
|
|
706
|
+
const portableTarget = target?.split(sep).join("/");
|
|
707
|
+
return sourceProofEntry(logicalPath, type, after, portableTarget);
|
|
708
|
+
}
|
|
709
|
+
async function createResolutionPathProof(filesystemRoot) {
|
|
710
|
+
const lexicalRoot = resolve(filesystemRoot);
|
|
711
|
+
const canonicalRoot = await realpath(lexicalRoot);
|
|
712
|
+
const captured = new Map();
|
|
713
|
+
const captureDirectory = async (path, logicalPath) => {
|
|
714
|
+
const current = await captureFilesystemPathProof(path, logicalPath);
|
|
715
|
+
if (current.type !== "directory") {
|
|
716
|
+
throw new Error(`Managed runtime resolution path ${logicalPath} must be a real directory.`);
|
|
717
|
+
}
|
|
718
|
+
const existing = captured.get(logicalPath);
|
|
719
|
+
if (existing !== undefined && !sameSourceProofEntry(existing.proof, current)) {
|
|
720
|
+
throw new Error(`Managed runtime resolution path ${logicalPath} changed while it was inspected.`);
|
|
721
|
+
}
|
|
722
|
+
if (existing === undefined)
|
|
723
|
+
captured.set(logicalPath, { path, proof: current });
|
|
724
|
+
};
|
|
725
|
+
const captureAncestors = async (path) => {
|
|
726
|
+
const target = resolve(path);
|
|
727
|
+
const root = pathInsideRoot(lexicalRoot, target)
|
|
728
|
+
? lexicalRoot
|
|
729
|
+
: pathInsideRoot(canonicalRoot, target)
|
|
730
|
+
? canonicalRoot
|
|
731
|
+
: undefined;
|
|
732
|
+
if (root === undefined) {
|
|
733
|
+
throw new Error("Managed runtime execution closure resolves outside its private install root.");
|
|
734
|
+
}
|
|
735
|
+
await captureDirectory(root, ".");
|
|
736
|
+
const parent = dirname(target);
|
|
737
|
+
const parentRelative = relative(root, parent);
|
|
738
|
+
if (parentRelative === "" || parentRelative === ".")
|
|
739
|
+
return;
|
|
740
|
+
let cursor = root;
|
|
741
|
+
for (const segment of parentRelative.split(sep).filter(Boolean)) {
|
|
742
|
+
cursor = join(cursor, segment);
|
|
743
|
+
await captureDirectory(cursor, portableResolutionPath(root, cursor));
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
await captureDirectory(lexicalRoot, ".");
|
|
747
|
+
return {
|
|
748
|
+
captureAncestors,
|
|
749
|
+
finalize: async () => {
|
|
750
|
+
const final = [];
|
|
751
|
+
for (const [logicalPath, initial] of [...captured].sort(([left], [right]) => compareCodeUnits(left, right))) {
|
|
752
|
+
const current = await captureFilesystemPathProof(initial.path, logicalPath);
|
|
753
|
+
if (current.type !== "directory" || !sameSourceProofEntry(initial.proof, current)) {
|
|
754
|
+
throw new Error(`Managed runtime resolution path ${logicalPath} changed while it was inspected.`);
|
|
755
|
+
}
|
|
756
|
+
final.push(current);
|
|
757
|
+
}
|
|
758
|
+
return final;
|
|
759
|
+
},
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function pathInsideRoot(root, path) {
|
|
763
|
+
const pathRelative = relative(root, path);
|
|
764
|
+
return pathRelative === ""
|
|
765
|
+
|| (pathRelative !== ".." && !pathRelative.startsWith(`..${sep}`) && !isAbsolute(pathRelative));
|
|
766
|
+
}
|
|
767
|
+
function portableResolutionPath(root, path) {
|
|
768
|
+
const pathRelative = relative(root, path);
|
|
769
|
+
return pathRelative === "" ? "." : pathRelative.split(sep).join("/");
|
|
770
|
+
}
|
|
520
771
|
function sourcePackageRelativePath(root, path) {
|
|
521
772
|
const pathRelative = relative(root, path);
|
|
522
773
|
if (pathRelative === "" || pathRelative === ".")
|
|
@@ -571,7 +822,7 @@ function dependencyRequirements(metadata) {
|
|
|
571
822
|
if (!requirements.has(name))
|
|
572
823
|
requirements.set(name, metadata.optionalPeers.has(name));
|
|
573
824
|
}
|
|
574
|
-
return [...requirements].sort(([left], [right]) => left
|
|
825
|
+
return [...requirements].sort(([left], [right]) => compareCodeUnits(left, right));
|
|
575
826
|
}
|
|
576
827
|
async function resolveInstalledDependencyPackageRoot(packageRoot, dependencyName) {
|
|
577
828
|
const segments = packageNameSegments(dependencyName);
|
|
@@ -579,6 +830,7 @@ async function resolveInstalledDependencyPackageRoot(packageRoot, dependencyName
|
|
|
579
830
|
for (;;) {
|
|
580
831
|
const candidate = join(cursor, "node_modules", ...segments);
|
|
581
832
|
try {
|
|
833
|
+
const proof = await captureFilesystemPathProof(candidate, dependencyName);
|
|
582
834
|
const canonical = await realpath(candidate);
|
|
583
835
|
const details = await lstat(canonical);
|
|
584
836
|
if (!details.isDirectory() || details.isSymbolicLink()) {
|
|
@@ -588,7 +840,14 @@ async function resolveInstalledDependencyPackageRoot(packageRoot, dependencyName
|
|
|
588
840
|
if (metadata.name !== dependencyName) {
|
|
589
841
|
throw new Error(`Installed dependency path ${candidate} declares ${metadata.name}, expected ${dependencyName}.`);
|
|
590
842
|
}
|
|
591
|
-
|
|
843
|
+
const [proofAfter, canonicalAfter] = await Promise.all([
|
|
844
|
+
captureFilesystemPathProof(candidate, dependencyName),
|
|
845
|
+
realpath(candidate),
|
|
846
|
+
]);
|
|
847
|
+
if (canonicalAfter !== canonical || !sameSourceProofEntry(proof, proofAfter)) {
|
|
848
|
+
throw new Error(`Installed dependency ${dependencyName} at ${candidate} changed while it was resolved.`);
|
|
849
|
+
}
|
|
850
|
+
return { candidatePath: candidate, packageRoot: canonical, proof: proofAfter };
|
|
592
851
|
}
|
|
593
852
|
catch (error) {
|
|
594
853
|
if (!isErrno(error, "ENOENT") && !isErrno(error, "ENOTDIR"))
|
|
@@ -700,7 +959,7 @@ function runtimeLayoutForRoot(layout, root) {
|
|
|
700
959
|
markerPath: join(root, ".mono-agent-runtime.json"),
|
|
701
960
|
};
|
|
702
961
|
}
|
|
703
|
-
async function acquireRuntimeLock(layout, identity, deps) {
|
|
962
|
+
async function acquireRuntimeLock(layout, identity, additionalPackages, deps) {
|
|
704
963
|
const deadline = deps.now() + LOCK_WAIT_TIMEOUT_MS;
|
|
705
964
|
const incarnation = await (deps.currentProcessIncarnation ?? currentProcessIncarnation)();
|
|
706
965
|
const isSameProcess = deps.isSameProcessIncarnation ?? matchesProcessIncarnation;
|
|
@@ -727,7 +986,7 @@ async function acquireRuntimeLock(layout, identity, deps) {
|
|
|
727
986
|
if (!isErrno(error, "EEXIST"))
|
|
728
987
|
throw error;
|
|
729
988
|
}
|
|
730
|
-
if (await verifyRuntime(layout, identity))
|
|
989
|
+
if (await verifyRuntime(layout, identity, additionalPackages))
|
|
731
990
|
return undefined;
|
|
732
991
|
let stale = false;
|
|
733
992
|
try {
|
|
@@ -823,7 +1082,7 @@ async function removeSameRuntimeLockDirectory(path, identity) {
|
|
|
823
1082
|
}
|
|
824
1083
|
await rm(released, { recursive: true, force: true });
|
|
825
1084
|
}
|
|
826
|
-
async function quarantineInvalidRuntime(layout, identity, deps) {
|
|
1085
|
+
async function quarantineInvalidRuntime(layout, identity, additionalPackages, deps) {
|
|
827
1086
|
try {
|
|
828
1087
|
await lstat(layout.installRoot);
|
|
829
1088
|
}
|
|
@@ -832,7 +1091,7 @@ async function quarantineInvalidRuntime(layout, identity, deps) {
|
|
|
832
1091
|
return;
|
|
833
1092
|
throw error;
|
|
834
1093
|
}
|
|
835
|
-
if (await verifyRuntime(layout, identity))
|
|
1094
|
+
if (await verifyRuntime(layout, identity, additionalPackages))
|
|
836
1095
|
return;
|
|
837
1096
|
await mkdir(dirname(layout.quarantineDir), { recursive: true, mode: 0o700 });
|
|
838
1097
|
await rename(layout.installRoot, layout.quarantineDir);
|
|
@@ -841,28 +1100,64 @@ async function quarantineInvalidRuntime(layout, identity, deps) {
|
|
|
841
1100
|
// are never silently overwritten, while no plist can point at this location.
|
|
842
1101
|
void deps;
|
|
843
1102
|
}
|
|
844
|
-
async function promoteStaging(layout, staged, identity) {
|
|
1103
|
+
async function promoteStaging(layout, staged, identity, additionalPackages) {
|
|
845
1104
|
try {
|
|
846
1105
|
await rename(staged.installRoot, layout.installRoot);
|
|
1106
|
+
return true;
|
|
847
1107
|
}
|
|
848
1108
|
catch (error) {
|
|
849
1109
|
// A concurrent installer can win after a stale-lock recovery. Its result is
|
|
850
1110
|
// acceptable only if it independently verifies against the same identity.
|
|
851
|
-
if (!(await verifyRuntime(layout, identity)))
|
|
1111
|
+
if (!(await verifyRuntime(layout, identity, additionalPackages)))
|
|
852
1112
|
throw error;
|
|
853
1113
|
await rm(staged.installRoot, { recursive: true, force: true });
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
async function verifyRuntime(layout, identity, additionalPackages) {
|
|
1118
|
+
const marker = await verifiedRuntimeMarker(layout, identity);
|
|
1119
|
+
if (marker === undefined)
|
|
1120
|
+
return false;
|
|
1121
|
+
try {
|
|
1122
|
+
const packageSource = dirname(dirname(layout.cliPath));
|
|
1123
|
+
const runtimeAdditionalPackages = additionalPackages.map(({ packageName }) => ({
|
|
1124
|
+
packageName,
|
|
1125
|
+
packageSource: join(layout.installRoot, "node_modules", ...packageNameSegments(packageName)),
|
|
1126
|
+
}));
|
|
1127
|
+
const closure = await captureExecutionClosure(packageSource, runtimeAdditionalPackages, layout.installRoot);
|
|
1128
|
+
return closure.sourceClosureSha256 === identity.sourceClosureSha256
|
|
1129
|
+
&& closure.filesystemProofSha256 === marker.executionProofSha256;
|
|
1130
|
+
}
|
|
1131
|
+
catch {
|
|
1132
|
+
return false;
|
|
854
1133
|
}
|
|
855
1134
|
}
|
|
856
|
-
async function
|
|
1135
|
+
async function verifiedRuntimeMarker(layout, identity) {
|
|
857
1136
|
try {
|
|
858
1137
|
const root = await lstat(layout.installRoot);
|
|
859
1138
|
if (!root.isDirectory() || root.isSymbolicLink() || !ownedPrivately(root))
|
|
860
|
-
return
|
|
1139
|
+
return undefined;
|
|
861
1140
|
if (!(await verifyInstalledPackage(layout, identity)))
|
|
862
|
-
return
|
|
1141
|
+
return undefined;
|
|
863
1142
|
await assertRegularFile(layout.markerPath, "managed runtime marker");
|
|
864
1143
|
const marker = JSON.parse(await readFile(layout.markerPath, "utf8"));
|
|
865
|
-
|
|
1144
|
+
const exactKeys = [
|
|
1145
|
+
"schema",
|
|
1146
|
+
"packageName",
|
|
1147
|
+
"closureManifestSha256",
|
|
1148
|
+
"executionProofSha256",
|
|
1149
|
+
"packageVersion",
|
|
1150
|
+
"cliSha256",
|
|
1151
|
+
"sourceClosureSha256",
|
|
1152
|
+
"nodeAbi",
|
|
1153
|
+
"platform",
|
|
1154
|
+
"arch",
|
|
1155
|
+
"installedAt",
|
|
1156
|
+
];
|
|
1157
|
+
const installedAtMs = typeof marker.installedAt === "string" ? Date.parse(marker.installedAt) : Number.NaN;
|
|
1158
|
+
const valid = Object.keys(marker).length === exactKeys.length
|
|
1159
|
+
&& exactKeys.every((key) => Object.hasOwn(marker, key))
|
|
1160
|
+
&& marker.schema === "mono-agent.managed-runtime.v4"
|
|
866
1161
|
&& marker.packageName === PACKAGE_NAME
|
|
867
1162
|
&& marker.packageVersion === identity.packageVersion
|
|
868
1163
|
&& marker.cliSha256 === identity.cliSha256
|
|
@@ -871,10 +1166,21 @@ async function verifyRuntime(layout, identity) {
|
|
|
871
1166
|
&& marker.platform === identity.platform
|
|
872
1167
|
&& marker.arch === identity.arch
|
|
873
1168
|
&& typeof marker.closureManifestSha256 === "string"
|
|
1169
|
+
&& /^[0-9a-f]{64}$/u.test(marker.closureManifestSha256)
|
|
1170
|
+
&& typeof marker.executionProofSha256 === "string"
|
|
1171
|
+
&& /^[0-9a-f]{64}$/u.test(marker.executionProofSha256)
|
|
1172
|
+
&& typeof marker.installedAt === "string"
|
|
1173
|
+
&& Number.isFinite(installedAtMs)
|
|
1174
|
+
&& new Date(installedAtMs).toISOString() === marker.installedAt
|
|
1175
|
+
// Promotion writes this sentinel before the final filesystem proof is
|
|
1176
|
+
// rebound. It must remain unverifiable even when the filesystem records
|
|
1177
|
+
// both writes with indistinguishable timestamp precision.
|
|
1178
|
+
&& marker.installedAt !== PROVISIONAL_RUNTIME_INSTALLED_AT
|
|
874
1179
|
&& await verifyClosureManifest(layout, marker.closureManifestSha256);
|
|
1180
|
+
return valid ? marker : undefined;
|
|
875
1181
|
}
|
|
876
1182
|
catch {
|
|
877
|
-
return
|
|
1183
|
+
return undefined;
|
|
878
1184
|
}
|
|
879
1185
|
}
|
|
880
1186
|
async function verifyInstalledPackage(layout, identity) {
|
|
@@ -941,7 +1247,7 @@ async function captureRuntimeClosureManifest(layout) {
|
|
|
941
1247
|
const excluded = new Set([resolve(layout.closureManifestPath), resolve(layout.markerPath)]);
|
|
942
1248
|
const visit = async (directory) => {
|
|
943
1249
|
const children = await readdir(directory, { withFileTypes: true });
|
|
944
|
-
children.sort((left, right) => left.name
|
|
1250
|
+
children.sort((left, right) => compareCodeUnits(left.name, right.name));
|
|
945
1251
|
for (const child of children) {
|
|
946
1252
|
const path = join(directory, child.name);
|
|
947
1253
|
if (excluded.has(resolve(path)))
|
|
@@ -981,7 +1287,7 @@ async function captureRuntimeClosureManifest(layout) {
|
|
|
981
1287
|
}
|
|
982
1288
|
};
|
|
983
1289
|
await visit(layout.installRoot);
|
|
984
|
-
entries.sort((left, right) => left.path
|
|
1290
|
+
entries.sort((left, right) => compareCodeUnits(left.path, right.path));
|
|
985
1291
|
return { schema: "mono-agent.execution-closure.v1", entries };
|
|
986
1292
|
}
|
|
987
1293
|
async function fingerprintClosureFile(path, pathRelative) {
|
|
@@ -1044,6 +1350,35 @@ async function writePrivateJson(path, value) {
|
|
|
1044
1350
|
await writeFile(path, privateJsonContents(value), { encoding: "utf8", mode: 0o600 });
|
|
1045
1351
|
await chmod(path, 0o600);
|
|
1046
1352
|
}
|
|
1353
|
+
async function writeFinalizedRuntimeMarker(path, marker, deps) {
|
|
1354
|
+
// Persist the post-proof boundary. If a write crosses into another second,
|
|
1355
|
+
// advance and rewrite so every process from the finalization window remains
|
|
1356
|
+
// in the marker's rejected whole-second interval.
|
|
1357
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
1358
|
+
const boundaryMs = deps.now();
|
|
1359
|
+
await writePrivateJson(path, {
|
|
1360
|
+
...marker,
|
|
1361
|
+
installedAt: new Date(boundaryMs).toISOString(),
|
|
1362
|
+
});
|
|
1363
|
+
if (Math.floor(deps.now() / 1_000) === Math.floor(boundaryMs / 1_000))
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
throw new Error("Could not publish a stable managed runtime launch boundary.");
|
|
1367
|
+
}
|
|
1368
|
+
async function waitForManagedRuntimeLaunchBoundary(layout, identity, deps) {
|
|
1369
|
+
const marker = await verifiedRuntimeMarker(layout, identity);
|
|
1370
|
+
if (marker === undefined) {
|
|
1371
|
+
throw new Error("The managed runtime marker became invalid before launch.");
|
|
1372
|
+
}
|
|
1373
|
+
const boundaryMs = Date.parse(marker.installedAt);
|
|
1374
|
+
const safeStartMs = (Math.floor(boundaryMs / 1_000) + 1) * 1_000;
|
|
1375
|
+
const nowMs = deps.now();
|
|
1376
|
+
if (safeStartMs - nowMs > 5_000) {
|
|
1377
|
+
throw new Error("The managed runtime launch boundary is implausibly far in the future.");
|
|
1378
|
+
}
|
|
1379
|
+
if (nowMs < safeStartMs)
|
|
1380
|
+
await deps.sleep(safeStartMs - nowMs);
|
|
1381
|
+
}
|
|
1047
1382
|
function privateJsonContents(value) {
|
|
1048
1383
|
return `${JSON.stringify(value, undefined, 2)}\n`;
|
|
1049
1384
|
}
|
|
@@ -1072,6 +1407,28 @@ async function ensurePrivateRuntimeAncestors(home, versionAbiDir) {
|
|
|
1072
1407
|
}
|
|
1073
1408
|
}
|
|
1074
1409
|
}
|
|
1410
|
+
async function verifyPrivateRuntimeAncestors(home, versionAbiDir) {
|
|
1411
|
+
const base = join(home, ".mono-agent");
|
|
1412
|
+
const pathRelative = relative(base, versionAbiDir);
|
|
1413
|
+
if (pathRelative === "" || pathRelative === ".." || pathRelative.startsWith(`..${sep}`) || isAbsolute(pathRelative)) {
|
|
1414
|
+
return false;
|
|
1415
|
+
}
|
|
1416
|
+
const paths = [base];
|
|
1417
|
+
for (const segment of pathRelative.split(/[\\/]/u).filter(Boolean)) {
|
|
1418
|
+
paths.push(join(paths.at(-1), segment));
|
|
1419
|
+
}
|
|
1420
|
+
try {
|
|
1421
|
+
for (const path of paths) {
|
|
1422
|
+
const details = await lstat(path);
|
|
1423
|
+
if (!details.isDirectory() || details.isSymbolicLink() || !ownedPrivately(details))
|
|
1424
|
+
return false;
|
|
1425
|
+
}
|
|
1426
|
+
return true;
|
|
1427
|
+
}
|
|
1428
|
+
catch {
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1075
1432
|
function ownedPrivately(details) {
|
|
1076
1433
|
const owned = typeof process.getuid !== "function" || Number(details.uid) === process.getuid();
|
|
1077
1434
|
return owned && (Number(details.mode) & 0o077) === 0;
|
|
@@ -1106,6 +1463,9 @@ function safeSegment(value) {
|
|
|
1106
1463
|
function sha256(value) {
|
|
1107
1464
|
return createHash("sha256").update(value).digest("hex");
|
|
1108
1465
|
}
|
|
1466
|
+
function compareCodeUnits(left, right) {
|
|
1467
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
1468
|
+
}
|
|
1109
1469
|
function isRecord(value) {
|
|
1110
1470
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1111
1471
|
}
|