@ai-ecoverse/slicc-kernel 1.69.0 → 1.71.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.
@@ -162,12 +162,12 @@ var IndexedDbMeta = class {
162
162
  return [transaction, transaction.objectStore(META_STORE)];
163
163
  }
164
164
  async get(paths) {
165
- const missing3 = paths.filter((path) => !this.cache.has(path));
166
- if (missing3.length > 0) {
165
+ const missing4 = paths.filter((path) => !this.cache.has(path));
166
+ if (missing4.length > 0) {
167
167
  const generation = this.generation;
168
168
  const [transaction, store] = this.transaction("readonly");
169
169
  const found = /* @__PURE__ */ new Map();
170
- for (const path of missing3) {
170
+ for (const path of missing4) {
171
171
  const request2 = store.get(path);
172
172
  request2.onsuccess = () => void found.set(path, request2.result ?? null);
173
173
  }
@@ -353,6 +353,183 @@ async function chownAs(fs, cred, path, change) {
353
353
  });
354
354
  }
355
355
 
356
+ // src/fs/protect.ts
357
+ var PROTECT_WRITER = "slicc-kernel: protect.writer needs the process tag (#405), which this kernel does not have yet";
358
+ function protectedPrefixes(paths) {
359
+ return [...new Set(paths.map((p) => normalizePath(p)))];
360
+ }
361
+ function within(path, prefix) {
362
+ return prefix === "/" || path === prefix || path.startsWith(`${prefix}/`);
363
+ }
364
+ function isProtected(prefixes2, path) {
365
+ return prefixes2.some((prefix) => within(path, prefix));
366
+ }
367
+ function holdsProtected(prefixes2, path) {
368
+ return prefixes2.some((prefix) => within(prefix, path));
369
+ }
370
+ var MAX_LINKS = 40;
371
+ function partsOf(path) {
372
+ return path.split("/").filter((p) => p !== "" && p !== ".");
373
+ }
374
+ function missing(err) {
375
+ const code = err?.code;
376
+ return code === "ENOENT" || code === "ENOTDIR";
377
+ }
378
+ async function resolved(fs, path, follow2) {
379
+ let done4 = [];
380
+ let todo = partsOf(path);
381
+ for (let hops = 0; todo.length > 0; ) {
382
+ const part = todo.shift();
383
+ if (part === "..") {
384
+ done4.pop();
385
+ continue;
386
+ }
387
+ if (todo.length === 0 && !follow2) {
388
+ done4.push(part);
389
+ break;
390
+ }
391
+ const at = `/${[...done4, part].join("/")}`;
392
+ const st = await fs.lstat(at).catch((err) => {
393
+ if (missing(err)) return void 0;
394
+ throw err;
395
+ });
396
+ if (!st) {
397
+ return normalizePath([...done4, part, ...todo].join("/"));
398
+ }
399
+ if (!st.isSymbolicLink) {
400
+ done4.push(part);
401
+ continue;
402
+ }
403
+ if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
404
+ const link2 = await fs.readlink(at);
405
+ if (link2.startsWith("/")) done4 = [];
406
+ todo = [...partsOf(link2), ...todo];
407
+ }
408
+ return `/${done4.join("/")}`;
409
+ }
410
+ var ProtectedFs = class {
411
+ prefixes;
412
+ constructor(prefixes2) {
413
+ this.prefixes = prefixes2;
414
+ }
415
+ async check(fs, path, follow2, whole = false) {
416
+ const lexical2 = normalizePath(path);
417
+ const real = await resolved(fs, lexical2, follow2);
418
+ for (const p of /* @__PURE__ */ new Set([lexical2, real])) {
419
+ if (isProtected(this.prefixes, p) || whole && holdsProtected(this.prefixes, p)) {
420
+ throw fsError("EROFS", path);
421
+ }
422
+ }
423
+ }
424
+ view(fs) {
425
+ const check = (path, follow2, whole = false) => this.check(fs, path, follow2, whole);
426
+ const marked = (path, st) => !st.readonly && isProtected(this.prefixes, normalizePath(path)) ? { ...st, readonly: true } : st;
427
+ const own = {
428
+ async writeFile(...args) {
429
+ await check(args[0], true);
430
+ return fs.writeFile(...args);
431
+ },
432
+ async mkdir(...args) {
433
+ await check(args[0], false);
434
+ return fs.mkdir(...args);
435
+ },
436
+ async rm(...args) {
437
+ await check(args[0], false, true);
438
+ return fs.rm(...args);
439
+ },
440
+ async rename(from, to) {
441
+ await check(from, false, true);
442
+ await check(to, false, true);
443
+ return fs.rename(from, to);
444
+ },
445
+ async link(from, to, follow2) {
446
+ await check(from, follow2 === true);
447
+ await check(to, false);
448
+ return fs.link(from, to, follow2);
449
+ },
450
+ async symlink(...args) {
451
+ await check(args[1], false);
452
+ return fs.symlink(...args);
453
+ },
454
+ async chmod(path, mode) {
455
+ await check(path, true);
456
+ return fs.chmod(path, mode);
457
+ },
458
+ async utimes(path, atime, mtime) {
459
+ await check(path, true);
460
+ return fs.utimes(path, atime, mtime);
461
+ },
462
+ async stat(path) {
463
+ return marked(path, await fs.stat(path));
464
+ },
465
+ async lstat(path) {
466
+ return marked(path, await fs.lstat(path));
467
+ },
468
+ async readdirStat(path) {
469
+ const dir = normalizePath(path);
470
+ return (await fs.readdirStat(path)).map(([name2, st]) => [
471
+ name2,
472
+ st && marked(`${dir}/${name2}`, st)
473
+ ]);
474
+ },
475
+ ...fs.createExclusive ? {
476
+ async createExclusive(...args) {
477
+ await check(args[0], true);
478
+ return fs.createExclusive?.(...args);
479
+ }
480
+ } : {},
481
+ ...fs.chown ? {
482
+ async chown(...args) {
483
+ await check(args[0], args[1].follow !== false);
484
+ return fs.chown?.(...args);
485
+ }
486
+ } : {},
487
+ ...fs.lutimes ? {
488
+ async lutimes(path, atime, mtime) {
489
+ await check(path, false);
490
+ return fs.lutimes?.(path, atime, mtime);
491
+ }
492
+ } : {},
493
+ ...fs.pwrite ? {
494
+ async pwrite(...args) {
495
+ await check(args[0], true);
496
+ return fs.pwrite?.(...args);
497
+ }
498
+ } : {},
499
+ ...fs.truncate ? {
500
+ async truncate(path, size) {
501
+ await check(path, true);
502
+ return fs.truncate?.(path, size);
503
+ }
504
+ } : {},
505
+ ...fs.exclusive ? {
506
+ async exclusive(path, run2, follow2) {
507
+ return fs.exclusive(
508
+ path,
509
+ (held) => run2({
510
+ ...held,
511
+ writeFile: async (...args) => {
512
+ await check(args[0], true);
513
+ return held.writeFile(...args);
514
+ }
515
+ }),
516
+ follow2
517
+ );
518
+ }
519
+ } : {}
520
+ };
521
+ const view = {};
522
+ for (let at = fs; at && at !== Object.prototype; at = Object.getPrototypeOf(at)) {
523
+ for (const name2 of Object.getOwnPropertyNames(at)) {
524
+ if (name2 === "constructor" || Object.hasOwn(view, name2)) continue;
525
+ const value = fs[name2];
526
+ view[name2] = typeof value === "function" ? value.bind(fs) : value;
527
+ }
528
+ }
529
+ return Object.assign(view, own);
530
+ }
531
+ };
532
+
356
533
  // src/fs/opfs.ts
357
534
  var S_IFDIR = 16384;
358
535
  var S_IFREG = 32768;
@@ -360,7 +537,7 @@ var S_IFLNK = 40960;
360
537
  var PERM_MASK = 4095;
361
538
  var S_ISGID2 = 1024;
362
539
  var READ_ATTEMPTS = 3;
363
- var MAX_LINKS = 40;
540
+ var MAX_LINKS2 = 40;
364
541
  var CHECK_MS = 1e3;
365
542
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
366
543
  var ERRNO_BY_DOM_ERROR = {
@@ -397,7 +574,7 @@ function sidecar(meta) {
397
574
  });
398
575
  return Object.fromEntries(META_OPS.map((op) => [op, guarded2(op)]));
399
576
  }
400
- function missing(err) {
577
+ function missing2(err) {
401
578
  const code = err?.code;
402
579
  return code === "ENOENT" || code === "ENOTDIR";
403
580
  }
@@ -414,7 +591,7 @@ function prefixes(path) {
414
591
  const parts2 = split(path);
415
592
  return parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`);
416
593
  }
417
- function within(path, root) {
594
+ function within2(path, root) {
418
595
  return path === root || path.startsWith(`${root}/`);
419
596
  }
420
597
  var LOCKED = /* @__PURE__ */ new Set(["NotReadableError", "NoModificationAllowedError", "InvalidStateError"]);
@@ -553,14 +730,25 @@ var OpfsFs = class {
553
730
  const fields = await this.newFields(path, owner, directory);
554
731
  if (fields) await this.meta.update(path, (entry) => ({ ...entry, path, ...fields }));
555
732
  }
733
+ guarded = [];
734
+ protect(prefixes2) {
735
+ this.guarded = prefixes2;
736
+ }
737
+ refuse(path, holding = false) {
738
+ if (isProtected(this.guarded, path) || holding && holdsProtected(this.guarded, path)) {
739
+ throw fsError("EROFS", path);
740
+ }
741
+ }
556
742
  async chown(path, change) {
557
743
  if (!this.owners) return;
558
744
  if (change.kind === "new") {
559
745
  const at = resolvePath((await this.locate(parentOf3(path), true)).path, baseOf(path));
746
+ this.refuse(at);
560
747
  const { uid = 0, gid = 0, mode } = change;
561
748
  return this.stampNew(at, { uid, gid, ...mode !== void 0 ? { mode } : {} }, false);
562
749
  }
563
750
  const found = await this.locate(path, change.follow !== false);
751
+ this.refuse(found.path);
564
752
  const [stored] = found.link ? [found.link] : await this.meta.get([found.path]);
565
753
  const current = await this.ownerAt(found.path, stored);
566
754
  const next2 = { uid: change.uid ?? current.uid, gid: change.gid ?? current.gid };
@@ -673,11 +861,11 @@ var OpfsFs = class {
673
861
  }
674
862
  async locate(path, follow2) {
675
863
  let current = path;
676
- for (let hops = 0; hops <= MAX_LINKS; hops++) {
864
+ for (let hops = 0; hops <= MAX_LINKS2; hops++) {
677
865
  try {
678
866
  return { path: current, handle: await this.handle(current) };
679
867
  } catch (err) {
680
- if (!missing(err)) throw err;
868
+ if (!missing2(err)) throw err;
681
869
  const link2 = await this.linkAlong(current);
682
870
  if (!link2) throw err;
683
871
  if (link2.path === current && !follow2) return { path: current, link: link2 };
@@ -699,7 +887,7 @@ var OpfsFs = class {
699
887
  const st = await this.lstat(at);
700
888
  if (!st.isSymbolicLink && !st.isDirectory) throw fsError("ENOTDIR", at);
701
889
  if (!st.isSymbolicLink) done4.push(part);
702
- else if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
890
+ else if (++hops > MAX_LINKS2) throw fsError("ELOOP", path);
703
891
  else {
704
892
  const link2 = await this.readlink(at);
705
893
  if (link2.startsWith("/")) done4 = [];
@@ -796,7 +984,7 @@ var OpfsFs = class {
796
984
  return this.locate(path, true).then(
797
985
  () => true,
798
986
  (err) => {
799
- if (missing(err)) return false;
987
+ if (missing2(err)) return false;
800
988
  throw err;
801
989
  }
802
990
  );
@@ -814,8 +1002,10 @@ var OpfsFs = class {
814
1002
  }
815
1003
  }
816
1004
  }
817
- async existing(path) {
818
- const handle = (await this.locate(path, true)).handle;
1005
+ async existing(path, writing = false) {
1006
+ const found = await this.locate(path, true);
1007
+ if (writing) this.refuse(found.path);
1008
+ const handle = found.handle;
819
1009
  if (handle.kind === "directory") throw fsError("EISDIR", path);
820
1010
  return handle;
821
1011
  }
@@ -840,7 +1030,7 @@ var OpfsFs = class {
840
1030
  await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
841
1031
  }
842
1032
  async truncate(path, size) {
843
- const handle = await this.existing(path);
1033
+ const handle = await this.existing(path, true);
844
1034
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
845
1035
  }
846
1036
  async writeSession(path, truncate) {
@@ -866,10 +1056,15 @@ var OpfsFs = class {
866
1056
  return new TextDecoder().decode(await this.readFileBuffer(path));
867
1057
  }
868
1058
  async writeTarget(path) {
1059
+ const [target, existed] = await this.locateWrite(path);
1060
+ this.refuse(target);
1061
+ return [target, existed];
1062
+ }
1063
+ async locateWrite(path) {
869
1064
  try {
870
1065
  return [(await this.locate(path, true)).path, true];
871
1066
  } catch (err) {
872
- if (!missing(err)) throw err;
1067
+ if (!missing2(err)) throw err;
873
1068
  }
874
1069
  const [entry] = await this.meta.get([path]);
875
1070
  const wanted = entry?.link === void 0 ? path : resolvePath(parentOf3(path), entry.link);
@@ -938,7 +1133,7 @@ var OpfsFs = class {
938
1133
  try {
939
1134
  found = await this.locate(path, true);
940
1135
  } catch (err) {
941
- if (!missing(err)) throw err;
1136
+ if (!missing2(err)) throw err;
942
1137
  }
943
1138
  if (found) {
944
1139
  const directory = found.handle?.kind === "directory";
@@ -959,6 +1154,7 @@ var OpfsFs = class {
959
1154
  }
960
1155
  const parent = await this.locate(parentOf3(path), true);
961
1156
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
1157
+ this.refuse(resolvePath(parent.path, baseOf(path)));
962
1158
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
963
1159
  const made2 = `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`;
964
1160
  await this.stampNew(made2, owner, true);
@@ -973,6 +1169,7 @@ var OpfsFs = class {
973
1169
  if (options.force && error.code === "ENOENT") return;
974
1170
  throw error;
975
1171
  }
1172
+ this.refuse(found.path, true);
976
1173
  if (found.handle) {
977
1174
  const [dir, name2] = await this.parent(found.path);
978
1175
  try {
@@ -997,13 +1194,15 @@ var OpfsFs = class {
997
1194
  async rename(from, to) {
998
1195
  if (from === to) return;
999
1196
  const source = await this.locate(from, false);
1197
+ this.refuse(source.path, true);
1000
1198
  this.forget(source.path);
1001
1199
  const parent = await this.locate(parentOf3(to), true);
1002
1200
  const destination = resolvePath(parent.path, baseOf(to));
1201
+ this.refuse(destination, true);
1003
1202
  const sourceStat = await this.describe(source);
1004
- if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
1203
+ if (sourceStat.isDirectory && within2(destination, source.path)) throw fsError("EINVAL", to);
1005
1204
  const target = await this.locate(destination, false).catch((err) => {
1006
- if (missing(err)) return null;
1205
+ if (missing2(err)) return null;
1007
1206
  throw err;
1008
1207
  });
1009
1208
  const targetStat = target && await this.describe(target);
@@ -1097,9 +1296,10 @@ var OpfsFs = class {
1097
1296
  const parent = await this.locate(parentOf3(path), true);
1098
1297
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
1099
1298
  const at = resolvePath(parent.path, baseOf(path));
1299
+ this.refuse(at);
1100
1300
  if (await this.locate(at, false).then(
1101
1301
  () => true,
1102
- (err) => !missing(err)
1302
+ (err) => !missing2(err)
1103
1303
  )) {
1104
1304
  throw fsError("EEXIST", path);
1105
1305
  }
@@ -1126,6 +1326,7 @@ var OpfsFs = class {
1126
1326
  }
1127
1327
  async chmod(path, mode) {
1128
1328
  const found = await this.locate(path, true);
1329
+ this.refuse(found.path);
1129
1330
  const ctimeMs = Date.now();
1130
1331
  await this.meta.update(found.path, (entry) => ({
1131
1332
  ...entry,
@@ -1141,6 +1342,7 @@ var OpfsFs = class {
1141
1342
  await this.setTimes(await this.locate(path, false), atime, mtime);
1142
1343
  }
1143
1344
  async setTimes(found, atime, mtime) {
1345
+ this.refuse(found.path);
1144
1346
  const { link: link2 } = found;
1145
1347
  if (link2) {
1146
1348
  const ctimeMs2 = Date.now();
@@ -1345,7 +1547,7 @@ function fresh(bytes, start2 = 0, end = bytes.length) {
1345
1547
  }
1346
1548
 
1347
1549
  // src/fs/walk.ts
1348
- var MAX_LINKS2 = 40;
1550
+ var MAX_LINKS3 = 40;
1349
1551
  function parentOf4(path) {
1350
1552
  return path.slice(0, path.lastIndexOf("/")) || "/";
1351
1553
  }
@@ -1374,7 +1576,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
1374
1576
  at = next2;
1375
1577
  continue;
1376
1578
  }
1377
- if (budget.hops >= MAX_LINKS2) throw fsError("ELOOP", dir);
1579
+ if (budget.hops >= MAX_LINKS3) throw fsError("ELOOP", dir);
1378
1580
  budget.hops++;
1379
1581
  queue.unshift(...link2.split("/").filter(Boolean));
1380
1582
  if (link2.startsWith("/")) at = "/";
@@ -1391,7 +1593,7 @@ async function realTarget(fs, path) {
1391
1593
  for (; ; ) {
1392
1594
  const link2 = await linkOf(fs, joinName(dir, name2));
1393
1595
  if (link2 === void 0) break;
1394
- if (budget.hops >= MAX_LINKS2) throw fsError("ELOOP", path);
1596
+ if (budget.hops >= MAX_LINKS3) throw fsError("ELOOP", path);
1395
1597
  budget.hops++;
1396
1598
  const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
1397
1599
  dir = await realDir(fs, parentOf4(target), budget);
@@ -1404,7 +1606,7 @@ async function realTarget(fs, path) {
1404
1606
  var R_OK = 4;
1405
1607
  var W_OK = 2;
1406
1608
  var X_OK = 1;
1407
- var MAX_LINKS3 = 40;
1609
+ var MAX_LINKS4 = 40;
1408
1610
  var S_ISVTX = 512;
1409
1611
  function permOf(st, link2) {
1410
1612
  return {
@@ -1475,7 +1677,7 @@ async function walk(fs, cred, path, follow2) {
1475
1677
  const perm = await perms.at(next2);
1476
1678
  const last = queue.length === 0;
1477
1679
  if (perm?.link !== void 0 && (!last || follow2)) {
1478
- if (++hops > MAX_LINKS3) return { path: next2, perm: void 0 };
1680
+ if (++hops > MAX_LINKS4) return { path: next2, perm: void 0 };
1479
1681
  queue.unshift(...parts(perm.link));
1480
1682
  if (perm.link.startsWith("/")) at = "/";
1481
1683
  continue;
@@ -3422,7 +3624,7 @@ var UserDb = class {
3422
3624
  };
3423
3625
 
3424
3626
  // src/client/serve-client.ts
3425
- var MAX_LINKS4 = 40;
3627
+ var MAX_LINKS5 = 40;
3426
3628
  function act(session, req, signal) {
3427
3629
  if (req.action === "write") session.write(req.bytes);
3428
3630
  else if (req.action === "resize") session.resize(req.cols, req.rows);
@@ -3431,22 +3633,22 @@ function act(session, req, signal) {
3431
3633
  }
3432
3634
  async function realpath(fs, path) {
3433
3635
  let parts2 = fs.resolvePath("/", path).split("/").filter(Boolean);
3434
- let resolved = "";
3636
+ let resolved2 = "";
3435
3637
  for (let hops = 0; parts2.length > 0; ) {
3436
3638
  const [name2, ...rest2] = parts2;
3437
- const next2 = `${resolved}/${name2}`;
3639
+ const next2 = `${resolved2}/${name2}`;
3438
3640
  if (!(await fs.lstat(next2)).isSymbolicLink) {
3439
- resolved = next2;
3641
+ resolved2 = next2;
3440
3642
  parts2 = rest2;
3441
3643
  continue;
3442
3644
  }
3443
- if (++hops > MAX_LINKS4) throw fsError("ELOOP", path);
3645
+ if (++hops > MAX_LINKS5) throw fsError("ELOOP", path);
3444
3646
  const target = await fs.readlink(next2);
3445
- const base = target.startsWith("/") ? "/" : resolved || "/";
3647
+ const base = target.startsWith("/") ? "/" : resolved2 || "/";
3446
3648
  parts2 = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest2];
3447
- resolved = "";
3649
+ resolved2 = "";
3448
3650
  }
3449
- return resolved || "/";
3651
+ return resolved2 || "/";
3450
3652
  }
3451
3653
  var CLIENT_CHECKS = {
3452
3654
  readFile: (g, at) => readable(g, "read", at(0)),
@@ -3751,7 +3953,7 @@ function serveClient(port, host) {
3751
3953
  if (!FS_METHODS.includes(req.method)) {
3752
3954
  throw new Error(`unknown file system call ${req.method}`);
3753
3955
  }
3754
- return fsCall(l.fs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
3956
+ return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
3755
3957
  case "watch":
3756
3958
  case "unwatch":
3757
3959
  return watchOp(l, req, watches, send, guardFor(l, self));
@@ -4970,11 +5172,11 @@ function ipv6(host) {
4970
5172
  const back = halves.length === 2 ? groups(halves[1]) : [];
4971
5173
  if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
4972
5174
  const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
4973
- const missing3 = 8 - words2.length - tail.length;
4974
- if (halves.length === 2 ? missing3 < 1 : missing3 !== 0) return void 0;
5175
+ const missing4 = 8 - words2.length - tail.length;
5176
+ if (halves.length === 2 ? missing4 < 1 : missing4 !== 0) return void 0;
4975
5177
  const all = [
4976
5178
  ...head.map((g) => Number.parseInt(g, 16)),
4977
- ...new Array(halves.length === 2 ? missing3 : 0).fill(0),
5179
+ ...new Array(halves.length === 2 ? missing4 : 0).fill(0),
4978
5180
  ...back.map((g) => Number.parseInt(g, 16)),
4979
5181
  ...tail
4980
5182
  ];
@@ -5763,7 +5965,7 @@ function parsePrefix(text) {
5763
5965
  if (length2 < 0 || length2 > max) return void 0;
5764
5966
  return { ...parsed2, length: length2 };
5765
5967
  }
5766
- function within2(prefix, addr) {
5968
+ function within3(prefix, addr) {
5767
5969
  if (prefix.v6 !== addr.v6) return false;
5768
5970
  const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
5769
5971
  return addr.bits >> shift === prefix.bits >> shift;
@@ -5803,9 +6005,9 @@ var Routes = class {
5803
6005
  if (!addr) return "unreachable";
5804
6006
  if (addr.v6 && addr.bits === 1n) return "loopback";
5805
6007
  if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
5806
- if (RESERVED.some((p) => within2(p, addr))) return "reserved";
6008
+ if (RESERVED.some((p) => within3(p, addr))) return "reserved";
5807
6009
  if (addr.v6 && !this.ipv6) return "unreachable";
5808
- if (this.exit || this.table.some((p) => within2(p, addr))) return "uplink";
6010
+ if (this.exit || this.table.some((p) => within3(p, addr))) return "uplink";
5809
6011
  return "unreachable";
5810
6012
  }
5811
6013
  };
@@ -6419,8 +6621,8 @@ function binfmtOf(binfmts, path) {
6419
6621
  var S_IFDIR2 = 16384;
6420
6622
  var S_IFREG2 = 32768;
6421
6623
  var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
6422
- var MAX_LINKS5 = 40;
6423
- function missing2(err) {
6624
+ var MAX_LINKS6 = 40;
6625
+ function missing3(err) {
6424
6626
  return err?.code === "ENOENT";
6425
6627
  }
6426
6628
  function synthetic(directory, path) {
@@ -6439,7 +6641,7 @@ function synthetic(directory, path) {
6439
6641
  }
6440
6642
  async function followLinks(fs, path) {
6441
6643
  let current = path;
6442
- for (let hops = 0; hops < MAX_LINKS5; hops++) {
6644
+ for (let hops = 0; hops < MAX_LINKS6; hops++) {
6443
6645
  const st = await fs.lstat(current).catch(() => void 0);
6444
6646
  if (!st?.isSymbolicLink) return current;
6445
6647
  const parent = current.slice(0, current.lastIndexOf("/")) || "/";
@@ -6486,7 +6688,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6486
6688
  try {
6487
6689
  return await real(path);
6488
6690
  } catch (err) {
6489
- const found = missing2(err) ? await linked(path) : void 0;
6691
+ const found = missing3(err) ? await linked(path) : void 0;
6490
6692
  if (found) return found;
6491
6693
  throw err;
6492
6694
  }
@@ -6498,7 +6700,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6498
6700
  try {
6499
6701
  return await fs.readFileBuffer(path);
6500
6702
  } catch (err) {
6501
- if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
6703
+ if (missing3(err) && (await linked(path))?.isFile) return new Uint8Array(0);
6502
6704
  throw err;
6503
6705
  }
6504
6706
  },
@@ -6514,7 +6716,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6514
6716
  const extra = await children2(path);
6515
6717
  if (!extra) return fs.readdir(path);
6516
6718
  const real = await fs.readdir(path).catch((err) => {
6517
- if (missing2(err)) return [];
6719
+ if (missing3(err)) return [];
6518
6720
  throw err;
6519
6721
  });
6520
6722
  return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
@@ -6523,7 +6725,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6523
6725
  const extra = await children2(path);
6524
6726
  if (!extra) return fs.readdirStat(path);
6525
6727
  const real = await fs.readdirStat(path).catch((err) => {
6526
- if (missing2(err)) return [];
6728
+ if (missing3(err)) return [];
6527
6729
  throw err;
6528
6730
  });
6529
6731
  const seen = new Set(real.map(([name2]) => name2));
@@ -6607,7 +6809,7 @@ var UNLINKED_DIR = ".slicc-unlinked";
6607
6809
  function webLocks() {
6608
6810
  return globalThis.navigator?.locks;
6609
6811
  }
6610
- var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
6812
+ var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
6611
6813
  var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6612
6814
  var lockName = (instance) => `slicc-unlinked:${instance}`;
6613
6815
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
@@ -6707,7 +6909,7 @@ var UnlinkedKeeper = class {
6707
6909
  for (const holder of this.holders) {
6708
6910
  if (holder.owns(target)) continue;
6709
6911
  for (const path of holder.held()) {
6710
- if (!within3(path, target) || holder.isKept(path)) continue;
6912
+ if (!within4(path, target) || holder.isKept(path)) continue;
6711
6913
  held.set(path, [...held.get(path) ?? [], holder]);
6712
6914
  }
6713
6915
  }
@@ -6960,6 +7162,18 @@ function capturing(chunks, max = Number.POSITIVE_INFINITY, overflow) {
6960
7162
  size += bytes.length;
6961
7163
  };
6962
7164
  }
7165
+ function captureFile(onData, control) {
7166
+ if (!control) return sinkFile(onData);
7167
+ control.opened = true;
7168
+ return new OpenFile({
7169
+ write: async (bytes) => {
7170
+ if (control.shut) throw new KernelError("EPIPE");
7171
+ onData(bytes.slice());
7172
+ return bytes.length;
7173
+ },
7174
+ close: () => control.ended()
7175
+ });
7176
+ }
6963
7177
  function concat3(chunks) {
6964
7178
  const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
6965
7179
  let offset = 0;
@@ -6994,7 +7208,12 @@ var ChildTable = class {
6994
7208
  const fds = new FdTable();
6995
7209
  const captured = /* @__PURE__ */ new Map();
6996
7210
  try {
6997
- for (const [n, slot2] of stdio.entries()) fds.installAt(n, this.openSlot(slot2, n, captured));
7211
+ for (const [n, slot2] of stdio.entries()) {
7212
+ fds.installAt(
7213
+ n,
7214
+ "sameAs" in slot2 ? fds.get(slot2.sameAs).retain() : this.openSlot(slot2, n, captured)
7215
+ );
7216
+ }
6998
7217
  for (const slot2 of inherit2) {
6999
7218
  if (slot2.fd <= 2) continue;
7000
7219
  if ("device" in slot2) {
@@ -7051,7 +7270,7 @@ var ChildTable = class {
7051
7270
  if ("capture" in slot2) {
7052
7271
  const chunks = [];
7053
7272
  captured.set(n, chunks);
7054
- return sinkFile(capturing(chunks, slot2.max, slot2.overflow));
7273
+ return captureFile(capturing(chunks, slot2.max, slot2.overflow), slot2.control);
7055
7274
  }
7056
7275
  return deviceFile("null");
7057
7276
  }
@@ -7244,7 +7463,7 @@ function checkIds(options) {
7244
7463
  if (!ID.test(value) || Number(value) > MAX_ID2) throw errnoError("EINVAL", `${key}=${value}`);
7245
7464
  }
7246
7465
  }
7247
- var MAX_LINKS6 = 40;
7466
+ var MAX_LINKS7 = 40;
7248
7467
  var STALE_RETRIES = 3;
7249
7468
  var SETTLE_ROUNDS = 4;
7250
7469
  var DEFAULT_IO = 8 * 1024 * 1024;
@@ -7254,7 +7473,7 @@ function parseSize(text) {
7254
7473
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
7255
7474
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
7256
7475
  }
7257
- var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
7476
+ var within5 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
7258
7477
  var HeldSet = class extends Set {
7259
7478
  idle;
7260
7479
  constructor(idle) {
@@ -7273,7 +7492,7 @@ var HeldSet = class extends Set {
7273
7492
  }
7274
7493
  };
7275
7494
  function heldUnder(held, target) {
7276
- for (const paths of held) for (const path of paths.keys()) if (within4(path, target)) return true;
7495
+ for (const paths of held) for (const path of paths.keys()) if (within5(path, target)) return true;
7277
7496
  return false;
7278
7497
  }
7279
7498
  var HeldPaths = class extends Map {
@@ -7384,24 +7603,24 @@ var HeldPaths = class extends Map {
7384
7603
  return this.owning === path;
7385
7604
  }
7386
7605
  revoke(prefix) {
7387
- for (const path of this.keys()) if (within4(path, prefix)) this.revoked.add(path);
7606
+ for (const path of this.keys()) if (within5(path, prefix)) this.revoked.add(path);
7388
7607
  }
7389
7608
  renamed(from, to) {
7390
7609
  for (const [path, count] of [...this]) {
7391
- if (!within4(path, from)) continue;
7610
+ if (!within5(path, from)) continue;
7392
7611
  const moved2 = to + path.slice(from.length);
7393
7612
  this.delete(path);
7394
7613
  this.set(moved2, (this.get(moved2) ?? 0) + count);
7395
7614
  if (this.revoked.delete(path)) this.revoked.add(moved2);
7396
7615
  }
7397
7616
  for (const [path, count] of [...this.opens]) {
7398
- if (!within4(path, from)) continue;
7617
+ if (!within5(path, from)) continue;
7399
7618
  const moved2 = to + path.slice(from.length);
7400
7619
  this.opens.delete(path);
7401
7620
  this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
7402
7621
  }
7403
7622
  for (const [id2, key] of this.handles) {
7404
- if (within4(key, from)) this.handles.set(id2, to + key.slice(from.length));
7623
+ if (within5(key, from)) this.handles.set(id2, to + key.slice(from.length));
7405
7624
  }
7406
7625
  }
7407
7626
  isRevoked(path) {
@@ -7508,7 +7727,7 @@ var MountTable = class {
7508
7727
  locate(path) {
7509
7728
  let best;
7510
7729
  for (const mount of this.mounts.values()) {
7511
- if (within4(path, mount.target) && (!best || mount.target.length > best.target.length))
7730
+ if (within5(path, mount.target) && (!best || mount.target.length > best.target.length))
7512
7731
  best = mount;
7513
7732
  }
7514
7733
  if (!best) return void 0;
@@ -7651,7 +7870,7 @@ var MountTable = class {
7651
7870
  async commitUnder(prefix) {
7652
7871
  const at = normalizePath(prefix);
7653
7872
  await Promise.all(
7654
- [...this.sessions.keys()].filter((p) => within4(p, at)).map((p) => this.commit(p))
7873
+ [...this.sessions.keys()].filter((p) => within5(p, at)).map((p) => this.commit(p))
7655
7874
  );
7656
7875
  }
7657
7876
  async commitOverlapping(real, offset, length2) {
@@ -7686,10 +7905,10 @@ var MountTable = class {
7686
7905
  }
7687
7906
  forget(mount, path) {
7688
7907
  mount.epoch++;
7689
- for (const key of [...mount.attrs.keys()]) if (within4(key, path)) mount.attrs.delete(key);
7908
+ for (const key of [...mount.attrs.keys()]) if (within5(key, path)) mount.attrs.delete(key);
7690
7909
  mount.lists.delete(path);
7691
7910
  mount.lists.delete(parentOf7(path));
7692
- for (const key of [...mount.lists.keys()]) if (within4(key, path)) mount.lists.delete(key);
7911
+ for (const key of [...mount.lists.keys()]) if (within5(key, path)) mount.lists.delete(key);
7693
7912
  }
7694
7913
  async getattr(mount, path, rel2, settled = false) {
7695
7914
  for (let round = 1; ; round++) {
@@ -7762,7 +7981,7 @@ var MountFs = class {
7762
7981
  throw err;
7763
7982
  }
7764
7983
  if (attr2.kind !== "symlink") return real;
7765
- if (hops >= MAX_LINKS6) throw errnoError("ELOOP", path);
7984
+ if (hops >= MAX_LINKS7) throw errnoError("ELOOP", path);
7766
7985
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
7767
7986
  return this.follow(this.base.resolvePath(parentOf7(real), target), hops + 1, create);
7768
7987
  }
@@ -10558,9 +10777,11 @@ function capLimit(maxBuffer) {
10558
10777
  return Math.min(asked, RUN_CAPTURE_CAP);
10559
10778
  }
10560
10779
  function slot(how, n, extra) {
10780
+ if (how === "stdout") return { sameAs: 1 };
10781
+ if (typeof how === "number") return { fd: how };
10561
10782
  if (how === "inherit") return { fd: n };
10562
10783
  if (how === "ignore") return { none: true };
10563
- return extra;
10784
+ return extra();
10564
10785
  }
10565
10786
  function frame2(outcome, out, err) {
10566
10787
  const head = new TextEncoder().encode(JSON.stringify(outcome));
@@ -10590,12 +10811,20 @@ async function runChild(req, deps) {
10590
10811
  if (failure2 !== void 0) return;
10591
10812
  failure2 = code;
10592
10813
  if (pid !== 0) kill();
10814
+ decided.resolve();
10593
10815
  };
10594
- const capture = () => ({ capture: true, max, overflow: () => stop("ENOBUFS") });
10816
+ const controls = [];
10817
+ const capture = () => {
10818
+ const { promise, resolve } = Promise.withResolvers();
10819
+ const control = { shut: false, ended: resolve, eof: promise };
10820
+ controls.push(control);
10821
+ return { capture: true, max, overflow: () => stop("ENOBUFS"), control };
10822
+ };
10823
+ const decided = Promise.withResolvers();
10595
10824
  const stdio = [
10596
- slot(req.stdin, 0, { input: req.input ?? new Uint8Array(0) }),
10597
- slot(req.stdout, 1, capture()),
10598
- slot(req.stderr, 2, capture())
10825
+ slot(req.stdin, 0, () => ({ input: req.input ?? new Uint8Array(0) })),
10826
+ slot(req.stdout, 1, capture),
10827
+ slot(req.stderr, 2, capture)
10599
10828
  ];
10600
10829
  try {
10601
10830
  pid = await deps.children.spawn(
@@ -10612,8 +10841,11 @@ async function runChild(req, deps) {
10612
10841
  let status;
10613
10842
  try {
10614
10843
  status = await reaped(deps, pid);
10844
+ const open = controls.filter((c) => c.opened);
10845
+ await Promise.race([Promise.all(open.map((c) => c.eof)), decided.promise]);
10615
10846
  } finally {
10616
10847
  clearTimeout(timer);
10848
+ for (const control of controls) control.shut = true;
10617
10849
  }
10618
10850
  const out = deps.children.captured(pid, 1);
10619
10851
  const err = deps.children.captured(pid, 2);
@@ -10783,7 +11015,7 @@ function isMissing(err) {
10783
11015
  if (err?.code === "ENOENT") return true;
10784
11016
  return err instanceof Error && err.message.startsWith("ENOENT");
10785
11017
  }
10786
- function within5(path, root) {
11018
+ function within6(path, root) {
10787
11019
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
10788
11020
  }
10789
11021
  var VfsNode = class {
@@ -11104,12 +11336,12 @@ var VfsNodes = class {
11104
11336
  return node;
11105
11337
  }
11106
11338
  holds(prefix) {
11107
- for (const path of this.byPath.keys()) if (within5(path, prefix)) return true;
11339
+ for (const path of this.byPath.keys()) if (within6(path, prefix)) return true;
11108
11340
  return false;
11109
11341
  }
11110
11342
  writes(prefix) {
11111
11343
  for (const [path, node] of this.byPath)
11112
- if (node.writers > 0 && within5(path, prefix)) return true;
11344
+ if (node.writers > 0 && within6(path, prefix)) return true;
11113
11345
  return false;
11114
11346
  }
11115
11347
  closed(node, writable2 = false) {
@@ -11153,11 +11385,11 @@ var VfsNodes = class {
11153
11385
  this.kept.delete(node);
11154
11386
  }
11155
11387
  async flush(path) {
11156
- for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
11388
+ for (const [p, node] of this.byPath) if (within6(p, path)) await node.serial(() => node.flush());
11157
11389
  }
11158
11390
  async unlinking(path) {
11159
11391
  for (const [held, node] of [...this.byPath]) {
11160
- if (!within5(held, path)) continue;
11392
+ if (!within6(held, path)) continue;
11161
11393
  await node.serial(async () => {
11162
11394
  await Promise.all([this.keepsOnDisk || node.keep(), node.limit()]);
11163
11395
  });
@@ -11165,14 +11397,14 @@ var VfsNodes = class {
11165
11397
  }
11166
11398
  revoke(prefix) {
11167
11399
  for (const [path, node] of [...this.byPath]) {
11168
- if (!within5(path, prefix)) continue;
11400
+ if (!within6(path, prefix)) continue;
11169
11401
  node.revoke();
11170
11402
  this.forget(node);
11171
11403
  }
11172
11404
  }
11173
11405
  unlinked(path) {
11174
11406
  for (const [held, node] of [...this.byPath]) {
11175
- if (!within5(held, path) || this.byPath.get(held) !== node) continue;
11407
+ if (!within6(held, path) || this.byPath.get(held) !== node) continue;
11176
11408
  if (!this.dropName(held, node)) this.orphan(node);
11177
11409
  }
11178
11410
  }
@@ -11180,8 +11412,8 @@ var VfsNodes = class {
11180
11412
  if (from === to) return;
11181
11413
  const moved2 = [];
11182
11414
  for (const [p, node] of [...this.byPath]) {
11183
- if (within5(p, from)) moved2.push([p, node]);
11184
- else if (within5(p, to) && this.byPath.get(p) === node && !this.dropName(p, node)) {
11415
+ if (within6(p, from)) moved2.push([p, node]);
11416
+ else if (within6(p, to) && this.byPath.get(p) === node && !this.dropName(p, node)) {
11185
11417
  this.orphan(node);
11186
11418
  }
11187
11419
  }
@@ -11727,6 +11959,7 @@ var WasmProcess = class {
11727
11959
  this.fds.installAt(fd, await this.procFile(req, path));
11728
11960
  return fd;
11729
11961
  }
11962
+ if (this.writesAt(req) && this.options.readonlyAt?.(path)) throw new KernelError("EROFS");
11730
11963
  const open = { ...req, path };
11731
11964
  if (req.exclusive) {
11732
11965
  await nodes.createExclusive(path, this.newOwner());
@@ -11772,6 +12005,9 @@ var WasmProcess = class {
11772
12005
  };
11773
12006
  return fs.exclusive ? fs.exclusive(first.path, look) : look();
11774
12007
  }
12008
+ writesAt(req) {
12009
+ return (req.flags & WRITE_ACCESS) !== 0 || req.truncate === true || req.create === true || req.exclusive === true;
12010
+ }
11775
12011
  async mayOpen(req) {
11776
12012
  const { guard: guard2 } = this.options;
11777
12013
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
@@ -12553,6 +12789,7 @@ function processOptions(opts, header3, hooks, created3) {
12553
12789
  ...inherited(opts),
12554
12790
  ...opts.owners ? { owners: true } : {},
12555
12791
  ...opts.sealed ? { sealed: opts.sealed } : {},
12792
+ ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
12556
12793
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
12557
12794
  ...opts.identity ? { identity: opts.identity } : {},
12558
12795
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
@@ -15279,13 +15516,13 @@ function interpreter(head) {
15279
15516
  function expandDefaults(defaults, env) {
15280
15517
  const out = {};
15281
15518
  for (const [key, value] of Object.entries(defaults ?? {})) {
15282
- let missing3 = false;
15519
+ let missing4 = false;
15283
15520
  const expanded = value.replace(ENV_REFERENCE, (_, name2) => {
15284
15521
  const set2 = Object.hasOwn(env, name2) ? env[name2] : void 0;
15285
- if (set2 === void 0) missing3 = true;
15522
+ if (set2 === void 0) missing4 = true;
15286
15523
  return set2 ?? "";
15287
15524
  });
15288
- if (!missing3) out[key] = expanded;
15525
+ if (!missing4) out[key] = expanded;
15289
15526
  }
15290
15527
  return out;
15291
15528
  }
@@ -15332,6 +15569,8 @@ function concat5(chunks) {
15332
15569
  }
15333
15570
  var Launcher = class {
15334
15571
  fs;
15572
+ processFs;
15573
+ prefixes;
15335
15574
  base;
15336
15575
  createWorker;
15337
15576
  modulesDir;
@@ -15458,6 +15697,10 @@ var Launcher = class {
15458
15697
  }
15459
15698
  );
15460
15699
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
15700
+ if (options.protect?.writer) throw new Error(PROTECT_WRITER);
15701
+ this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
15702
+ this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
15703
+ options.fs.protect?.(this.prefixes);
15461
15704
  this.users = new UserDb(this.fs, this.owners);
15462
15705
  this.sudoer = new Sudo({
15463
15706
  fs: this.fs,
@@ -15808,7 +16051,8 @@ var Launcher = class {
15808
16051
  env: req.env,
15809
16052
  cwd: req.cwd,
15810
16053
  fds: req.fds,
15811
- fs: this.fs,
16054
+ fs: this.processFs,
16055
+ ...this.prefixes.length > 0 ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
15812
16056
  createWorker: this.createWorker,
15813
16057
  onError: req.report,
15814
16058
  spawner: this.spawner(pid, req.report),
@@ -16093,6 +16337,18 @@ var Launcher = class {
16093
16337
  const keyed = () => this.jobs.fromKeyboard(send);
16094
16338
  this.settling.deliver(sig, pgid, keyed, tty.reading ? void 0 : foreground);
16095
16339
  }
16340
+ async resolvePrefixes() {
16341
+ for (const prefix of [...this.prefixes]) {
16342
+ const real = await this.base.realpath?.(prefix, true).catch(() => void 0);
16343
+ if (real !== void 0 && !this.prefixes.includes(real)) this.prefixes.push(real);
16344
+ }
16345
+ }
16346
+ guardMount(target) {
16347
+ const at = normalizePath(target);
16348
+ if (isProtected(this.prefixes, at) || holdsProtected(this.prefixes, at)) {
16349
+ throw fsError("EROFS", at);
16350
+ }
16351
+ }
16096
16352
  umaskOf(pid) {
16097
16353
  return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
16098
16354
  }
@@ -16227,6 +16483,7 @@ var Launcher = class {
16227
16483
  };
16228
16484
  }
16229
16485
  async mount(spec) {
16486
+ this.guardMount(spec.target);
16230
16487
  const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
16231
16488
  const entry = await this.mounts.mount(
16232
16489
  fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
@@ -16245,6 +16502,7 @@ var Launcher = class {
16245
16502
  [`/${UNLINKED_DIR}`, 448]
16246
16503
  ];
16247
16504
  for (const [path, mode] of layout) {
16505
+ if (isProtected(this.prefixes, normalizePath(path))) continue;
16248
16506
  const real = await this.realLayoutPath(path);
16249
16507
  const fresh2 = real !== void 0 && !await this.base.hasOwner?.(real);
16250
16508
  if (fresh2) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
@@ -16307,6 +16565,7 @@ var Launcher = class {
16307
16565
  }
16308
16566
  async processMount(pid, call, signal) {
16309
16567
  const spec = mountCall(call);
16568
+ this.guardMount(spec.target);
16310
16569
  await this.permit({ op: "mount", pid, ...spec, options: { ...spec.options } });
16311
16570
  await this.mountsOwn(
16312
16571
  this.credOf(pid),
@@ -16323,6 +16582,7 @@ var Launcher = class {
16323
16582
  }
16324
16583
  async umountAs(cred, target) {
16325
16584
  const at = normalizePath(target);
16585
+ this.guardMount(at);
16326
16586
  await this.mountsOwn(cred, at, async () => Number(this.mounts.mounted(at)?.options.uid));
16327
16587
  this.umount(at);
16328
16588
  }
@@ -16336,6 +16596,7 @@ var Launcher = class {
16336
16596
  async processUmount(pid, target, flags2) {
16337
16597
  const call = umountCall(target, flags2);
16338
16598
  let mounted;
16599
+ this.guardMount(call.target);
16339
16600
  for (; ; ) {
16340
16601
  mounted = this.mounts.mounted(call.target);
16341
16602
  await this.permit({
@@ -16350,6 +16611,7 @@ var Launcher = class {
16350
16611
  this.umount(call.target, call.detach);
16351
16612
  }
16352
16613
  async prepare() {
16614
+ await this.resolvePrefixes();
16353
16615
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
16354
16616
  if (this.owners) await this.base.chmod("/tmp", 1023);
16355
16617
  await this.provisionPnpmHome();
@@ -16406,7 +16668,7 @@ var Launcher = class {
16406
16668
  const start2 = dir ?? "/";
16407
16669
  const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
16408
16670
  const [file = ""] = argv;
16409
- await this.base.mkdir(cwd, { recursive: true });
16671
+ if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
16410
16672
  const planned = await this.plan(file, argv, cwd, scope);
16411
16673
  await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
16412
16674
  return { cwd, file, planned };
@@ -16647,6 +16909,7 @@ function hostOptions(req, port, sealed) {
16647
16909
  ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
16648
16910
  ...req.users !== void 0 ? { users: req.users } : {},
16649
16911
  ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
16912
+ ...req.protect ? { protect: { paths: [...req.protect.paths] } } : {},
16650
16913
  ...req.sealed ? { sealed } : {}
16651
16914
  };
16652
16915
  }