@ai-ecoverse/slicc-kernel 1.70.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
  }
@@ -7261,7 +7463,7 @@ function checkIds(options) {
7261
7463
  if (!ID.test(value) || Number(value) > MAX_ID2) throw errnoError("EINVAL", `${key}=${value}`);
7262
7464
  }
7263
7465
  }
7264
- var MAX_LINKS6 = 40;
7466
+ var MAX_LINKS7 = 40;
7265
7467
  var STALE_RETRIES = 3;
7266
7468
  var SETTLE_ROUNDS = 4;
7267
7469
  var DEFAULT_IO = 8 * 1024 * 1024;
@@ -7271,7 +7473,7 @@ function parseSize(text) {
7271
7473
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
7272
7474
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
7273
7475
  }
7274
- var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
7476
+ var within5 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
7275
7477
  var HeldSet = class extends Set {
7276
7478
  idle;
7277
7479
  constructor(idle) {
@@ -7290,7 +7492,7 @@ var HeldSet = class extends Set {
7290
7492
  }
7291
7493
  };
7292
7494
  function heldUnder(held, target) {
7293
- 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;
7294
7496
  return false;
7295
7497
  }
7296
7498
  var HeldPaths = class extends Map {
@@ -7401,24 +7603,24 @@ var HeldPaths = class extends Map {
7401
7603
  return this.owning === path;
7402
7604
  }
7403
7605
  revoke(prefix) {
7404
- 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);
7405
7607
  }
7406
7608
  renamed(from, to) {
7407
7609
  for (const [path, count] of [...this]) {
7408
- if (!within4(path, from)) continue;
7610
+ if (!within5(path, from)) continue;
7409
7611
  const moved2 = to + path.slice(from.length);
7410
7612
  this.delete(path);
7411
7613
  this.set(moved2, (this.get(moved2) ?? 0) + count);
7412
7614
  if (this.revoked.delete(path)) this.revoked.add(moved2);
7413
7615
  }
7414
7616
  for (const [path, count] of [...this.opens]) {
7415
- if (!within4(path, from)) continue;
7617
+ if (!within5(path, from)) continue;
7416
7618
  const moved2 = to + path.slice(from.length);
7417
7619
  this.opens.delete(path);
7418
7620
  this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
7419
7621
  }
7420
7622
  for (const [id2, key] of this.handles) {
7421
- 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));
7422
7624
  }
7423
7625
  }
7424
7626
  isRevoked(path) {
@@ -7525,7 +7727,7 @@ var MountTable = class {
7525
7727
  locate(path) {
7526
7728
  let best;
7527
7729
  for (const mount of this.mounts.values()) {
7528
- 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))
7529
7731
  best = mount;
7530
7732
  }
7531
7733
  if (!best) return void 0;
@@ -7668,7 +7870,7 @@ var MountTable = class {
7668
7870
  async commitUnder(prefix) {
7669
7871
  const at = normalizePath(prefix);
7670
7872
  await Promise.all(
7671
- [...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))
7672
7874
  );
7673
7875
  }
7674
7876
  async commitOverlapping(real, offset, length2) {
@@ -7703,10 +7905,10 @@ var MountTable = class {
7703
7905
  }
7704
7906
  forget(mount, path) {
7705
7907
  mount.epoch++;
7706
- 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);
7707
7909
  mount.lists.delete(path);
7708
7910
  mount.lists.delete(parentOf7(path));
7709
- 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);
7710
7912
  }
7711
7913
  async getattr(mount, path, rel2, settled = false) {
7712
7914
  for (let round = 1; ; round++) {
@@ -7779,7 +7981,7 @@ var MountFs = class {
7779
7981
  throw err;
7780
7982
  }
7781
7983
  if (attr2.kind !== "symlink") return real;
7782
- if (hops >= MAX_LINKS6) throw errnoError("ELOOP", path);
7984
+ if (hops >= MAX_LINKS7) throw errnoError("ELOOP", path);
7783
7985
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
7784
7986
  return this.follow(this.base.resolvePath(parentOf7(real), target), hops + 1, create);
7785
7987
  }
@@ -10813,7 +11015,7 @@ function isMissing(err) {
10813
11015
  if (err?.code === "ENOENT") return true;
10814
11016
  return err instanceof Error && err.message.startsWith("ENOENT");
10815
11017
  }
10816
- function within5(path, root) {
11018
+ function within6(path, root) {
10817
11019
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
10818
11020
  }
10819
11021
  var VfsNode = class {
@@ -11134,12 +11336,12 @@ var VfsNodes = class {
11134
11336
  return node;
11135
11337
  }
11136
11338
  holds(prefix) {
11137
- 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;
11138
11340
  return false;
11139
11341
  }
11140
11342
  writes(prefix) {
11141
11343
  for (const [path, node] of this.byPath)
11142
- if (node.writers > 0 && within5(path, prefix)) return true;
11344
+ if (node.writers > 0 && within6(path, prefix)) return true;
11143
11345
  return false;
11144
11346
  }
11145
11347
  closed(node, writable2 = false) {
@@ -11183,11 +11385,11 @@ var VfsNodes = class {
11183
11385
  this.kept.delete(node);
11184
11386
  }
11185
11387
  async flush(path) {
11186
- 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());
11187
11389
  }
11188
11390
  async unlinking(path) {
11189
11391
  for (const [held, node] of [...this.byPath]) {
11190
- if (!within5(held, path)) continue;
11392
+ if (!within6(held, path)) continue;
11191
11393
  await node.serial(async () => {
11192
11394
  await Promise.all([this.keepsOnDisk || node.keep(), node.limit()]);
11193
11395
  });
@@ -11195,14 +11397,14 @@ var VfsNodes = class {
11195
11397
  }
11196
11398
  revoke(prefix) {
11197
11399
  for (const [path, node] of [...this.byPath]) {
11198
- if (!within5(path, prefix)) continue;
11400
+ if (!within6(path, prefix)) continue;
11199
11401
  node.revoke();
11200
11402
  this.forget(node);
11201
11403
  }
11202
11404
  }
11203
11405
  unlinked(path) {
11204
11406
  for (const [held, node] of [...this.byPath]) {
11205
- if (!within5(held, path) || this.byPath.get(held) !== node) continue;
11407
+ if (!within6(held, path) || this.byPath.get(held) !== node) continue;
11206
11408
  if (!this.dropName(held, node)) this.orphan(node);
11207
11409
  }
11208
11410
  }
@@ -11210,8 +11412,8 @@ var VfsNodes = class {
11210
11412
  if (from === to) return;
11211
11413
  const moved2 = [];
11212
11414
  for (const [p, node] of [...this.byPath]) {
11213
- if (within5(p, from)) moved2.push([p, node]);
11214
- 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)) {
11215
11417
  this.orphan(node);
11216
11418
  }
11217
11419
  }
@@ -11757,6 +11959,7 @@ var WasmProcess = class {
11757
11959
  this.fds.installAt(fd, await this.procFile(req, path));
11758
11960
  return fd;
11759
11961
  }
11962
+ if (this.writesAt(req) && this.options.readonlyAt?.(path)) throw new KernelError("EROFS");
11760
11963
  const open = { ...req, path };
11761
11964
  if (req.exclusive) {
11762
11965
  await nodes.createExclusive(path, this.newOwner());
@@ -11802,6 +12005,9 @@ var WasmProcess = class {
11802
12005
  };
11803
12006
  return fs.exclusive ? fs.exclusive(first.path, look) : look();
11804
12007
  }
12008
+ writesAt(req) {
12009
+ return (req.flags & WRITE_ACCESS) !== 0 || req.truncate === true || req.create === true || req.exclusive === true;
12010
+ }
11805
12011
  async mayOpen(req) {
11806
12012
  const { guard: guard2 } = this.options;
11807
12013
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
@@ -12583,6 +12789,7 @@ function processOptions(opts, header3, hooks, created3) {
12583
12789
  ...inherited(opts),
12584
12790
  ...opts.owners ? { owners: true } : {},
12585
12791
  ...opts.sealed ? { sealed: opts.sealed } : {},
12792
+ ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
12586
12793
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
12587
12794
  ...opts.identity ? { identity: opts.identity } : {},
12588
12795
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
@@ -15309,13 +15516,13 @@ function interpreter(head) {
15309
15516
  function expandDefaults(defaults, env) {
15310
15517
  const out = {};
15311
15518
  for (const [key, value] of Object.entries(defaults ?? {})) {
15312
- let missing3 = false;
15519
+ let missing4 = false;
15313
15520
  const expanded = value.replace(ENV_REFERENCE, (_, name2) => {
15314
15521
  const set2 = Object.hasOwn(env, name2) ? env[name2] : void 0;
15315
- if (set2 === void 0) missing3 = true;
15522
+ if (set2 === void 0) missing4 = true;
15316
15523
  return set2 ?? "";
15317
15524
  });
15318
- if (!missing3) out[key] = expanded;
15525
+ if (!missing4) out[key] = expanded;
15319
15526
  }
15320
15527
  return out;
15321
15528
  }
@@ -15362,6 +15569,8 @@ function concat5(chunks) {
15362
15569
  }
15363
15570
  var Launcher = class {
15364
15571
  fs;
15572
+ processFs;
15573
+ prefixes;
15365
15574
  base;
15366
15575
  createWorker;
15367
15576
  modulesDir;
@@ -15488,6 +15697,10 @@ var Launcher = class {
15488
15697
  }
15489
15698
  );
15490
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);
15491
15704
  this.users = new UserDb(this.fs, this.owners);
15492
15705
  this.sudoer = new Sudo({
15493
15706
  fs: this.fs,
@@ -15838,7 +16051,8 @@ var Launcher = class {
15838
16051
  env: req.env,
15839
16052
  cwd: req.cwd,
15840
16053
  fds: req.fds,
15841
- fs: this.fs,
16054
+ fs: this.processFs,
16055
+ ...this.prefixes.length > 0 ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
15842
16056
  createWorker: this.createWorker,
15843
16057
  onError: req.report,
15844
16058
  spawner: this.spawner(pid, req.report),
@@ -16123,6 +16337,18 @@ var Launcher = class {
16123
16337
  const keyed = () => this.jobs.fromKeyboard(send);
16124
16338
  this.settling.deliver(sig, pgid, keyed, tty.reading ? void 0 : foreground);
16125
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
+ }
16126
16352
  umaskOf(pid) {
16127
16353
  return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
16128
16354
  }
@@ -16257,6 +16483,7 @@ var Launcher = class {
16257
16483
  };
16258
16484
  }
16259
16485
  async mount(spec) {
16486
+ this.guardMount(spec.target);
16260
16487
  const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
16261
16488
  const entry = await this.mounts.mount(
16262
16489
  fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
@@ -16275,6 +16502,7 @@ var Launcher = class {
16275
16502
  [`/${UNLINKED_DIR}`, 448]
16276
16503
  ];
16277
16504
  for (const [path, mode] of layout) {
16505
+ if (isProtected(this.prefixes, normalizePath(path))) continue;
16278
16506
  const real = await this.realLayoutPath(path);
16279
16507
  const fresh2 = real !== void 0 && !await this.base.hasOwner?.(real);
16280
16508
  if (fresh2) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
@@ -16337,6 +16565,7 @@ var Launcher = class {
16337
16565
  }
16338
16566
  async processMount(pid, call, signal) {
16339
16567
  const spec = mountCall(call);
16568
+ this.guardMount(spec.target);
16340
16569
  await this.permit({ op: "mount", pid, ...spec, options: { ...spec.options } });
16341
16570
  await this.mountsOwn(
16342
16571
  this.credOf(pid),
@@ -16353,6 +16582,7 @@ var Launcher = class {
16353
16582
  }
16354
16583
  async umountAs(cred, target) {
16355
16584
  const at = normalizePath(target);
16585
+ this.guardMount(at);
16356
16586
  await this.mountsOwn(cred, at, async () => Number(this.mounts.mounted(at)?.options.uid));
16357
16587
  this.umount(at);
16358
16588
  }
@@ -16366,6 +16596,7 @@ var Launcher = class {
16366
16596
  async processUmount(pid, target, flags2) {
16367
16597
  const call = umountCall(target, flags2);
16368
16598
  let mounted;
16599
+ this.guardMount(call.target);
16369
16600
  for (; ; ) {
16370
16601
  mounted = this.mounts.mounted(call.target);
16371
16602
  await this.permit({
@@ -16380,6 +16611,7 @@ var Launcher = class {
16380
16611
  this.umount(call.target, call.detach);
16381
16612
  }
16382
16613
  async prepare() {
16614
+ await this.resolvePrefixes();
16383
16615
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
16384
16616
  if (this.owners) await this.base.chmod("/tmp", 1023);
16385
16617
  await this.provisionPnpmHome();
@@ -16436,7 +16668,7 @@ var Launcher = class {
16436
16668
  const start2 = dir ?? "/";
16437
16669
  const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
16438
16670
  const [file = ""] = argv;
16439
- await this.base.mkdir(cwd, { recursive: true });
16671
+ if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
16440
16672
  const planned = await this.plan(file, argv, cwd, scope);
16441
16673
  await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
16442
16674
  return { cwd, file, planned };
@@ -16677,6 +16909,7 @@ function hostOptions(req, port, sealed) {
16677
16909
  ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
16678
16910
  ...req.users !== void 0 ? { users: req.users } : {},
16679
16911
  ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
16912
+ ...req.protect ? { protect: { paths: [...req.protect.paths] } } : {},
16680
16913
  ...req.sealed ? { sealed } : {}
16681
16914
  };
16682
16915
  }