@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.
package/dist/node.js CHANGED
@@ -2490,22 +2490,22 @@ function act(session, req, signal) {
2490
2490
  }
2491
2491
  async function realpath(fs, path) {
2492
2492
  let parts2 = fs.resolvePath("/", path).split("/").filter(Boolean);
2493
- let resolved = "";
2493
+ let resolved2 = "";
2494
2494
  for (let hops = 0; parts2.length > 0; ) {
2495
2495
  const [name2, ...rest2] = parts2;
2496
- const next2 = `${resolved}/${name2}`;
2496
+ const next2 = `${resolved2}/${name2}`;
2497
2497
  if (!(await fs.lstat(next2)).isSymbolicLink) {
2498
- resolved = next2;
2498
+ resolved2 = next2;
2499
2499
  parts2 = rest2;
2500
2500
  continue;
2501
2501
  }
2502
2502
  if (++hops > MAX_LINKS3) throw fsError("ELOOP", path);
2503
2503
  const target = await fs.readlink(next2);
2504
- const base = target.startsWith("/") ? "/" : resolved || "/";
2504
+ const base = target.startsWith("/") ? "/" : resolved2 || "/";
2505
2505
  parts2 = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest2];
2506
- resolved = "";
2506
+ resolved2 = "";
2507
2507
  }
2508
- return resolved || "/";
2508
+ return resolved2 || "/";
2509
2509
  }
2510
2510
  var CLIENT_CHECKS = {
2511
2511
  readFile: (g, at) => readable(g, "read", at(0)),
@@ -2810,7 +2810,7 @@ function serveClient(port, host) {
2810
2810
  if (!FS_METHODS.includes(req.method)) {
2811
2811
  throw new Error(`unknown file system call ${req.method}`);
2812
2812
  }
2813
- return fsCall(l.fs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
2813
+ return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
2814
2814
  case "watch":
2815
2815
  case "unwatch":
2816
2816
  return watchOp(l, req, watches, send, guardFor(l, self));
@@ -2997,6 +2997,183 @@ var MemoryMeta = class {
2997
2997
  }
2998
2998
  };
2999
2999
 
3000
+ // src/fs/protect.ts
3001
+ var PROTECT_WRITER = "slicc-kernel: protect.writer needs the process tag (#405), which this kernel does not have yet";
3002
+ function protectedPrefixes(paths) {
3003
+ return [...new Set(paths.map((p) => normalizePath(p)))];
3004
+ }
3005
+ function within(path, prefix) {
3006
+ return prefix === "/" || path === prefix || path.startsWith(`${prefix}/`);
3007
+ }
3008
+ function isProtected(prefixes2, path) {
3009
+ return prefixes2.some((prefix) => within(path, prefix));
3010
+ }
3011
+ function holdsProtected(prefixes2, path) {
3012
+ return prefixes2.some((prefix) => within(prefix, path));
3013
+ }
3014
+ var MAX_LINKS4 = 40;
3015
+ function partsOf(path) {
3016
+ return path.split("/").filter((p) => p !== "" && p !== ".");
3017
+ }
3018
+ function missing(err) {
3019
+ const code = err?.code;
3020
+ return code === "ENOENT" || code === "ENOTDIR";
3021
+ }
3022
+ async function resolved(fs, path, follow2) {
3023
+ let done4 = [];
3024
+ let todo = partsOf(path);
3025
+ for (let hops = 0; todo.length > 0; ) {
3026
+ const part = todo.shift();
3027
+ if (part === "..") {
3028
+ done4.pop();
3029
+ continue;
3030
+ }
3031
+ if (todo.length === 0 && !follow2) {
3032
+ done4.push(part);
3033
+ break;
3034
+ }
3035
+ const at = `/${[...done4, part].join("/")}`;
3036
+ const st = await fs.lstat(at).catch((err) => {
3037
+ if (missing(err)) return void 0;
3038
+ throw err;
3039
+ });
3040
+ if (!st) {
3041
+ return normalizePath([...done4, part, ...todo].join("/"));
3042
+ }
3043
+ if (!st.isSymbolicLink) {
3044
+ done4.push(part);
3045
+ continue;
3046
+ }
3047
+ if (++hops > MAX_LINKS4) throw fsError("ELOOP", path);
3048
+ const link2 = await fs.readlink(at);
3049
+ if (link2.startsWith("/")) done4 = [];
3050
+ todo = [...partsOf(link2), ...todo];
3051
+ }
3052
+ return `/${done4.join("/")}`;
3053
+ }
3054
+ var ProtectedFs = class {
3055
+ prefixes;
3056
+ constructor(prefixes2) {
3057
+ this.prefixes = prefixes2;
3058
+ }
3059
+ async check(fs, path, follow2, whole = false) {
3060
+ const lexical2 = normalizePath(path);
3061
+ const real = await resolved(fs, lexical2, follow2);
3062
+ for (const p of /* @__PURE__ */ new Set([lexical2, real])) {
3063
+ if (isProtected(this.prefixes, p) || whole && holdsProtected(this.prefixes, p)) {
3064
+ throw fsError("EROFS", path);
3065
+ }
3066
+ }
3067
+ }
3068
+ view(fs) {
3069
+ const check = (path, follow2, whole = false) => this.check(fs, path, follow2, whole);
3070
+ const marked = (path, st) => !st.readonly && isProtected(this.prefixes, normalizePath(path)) ? { ...st, readonly: true } : st;
3071
+ const own = {
3072
+ async writeFile(...args) {
3073
+ await check(args[0], true);
3074
+ return fs.writeFile(...args);
3075
+ },
3076
+ async mkdir(...args) {
3077
+ await check(args[0], false);
3078
+ return fs.mkdir(...args);
3079
+ },
3080
+ async rm(...args) {
3081
+ await check(args[0], false, true);
3082
+ return fs.rm(...args);
3083
+ },
3084
+ async rename(from, to) {
3085
+ await check(from, false, true);
3086
+ await check(to, false, true);
3087
+ return fs.rename(from, to);
3088
+ },
3089
+ async link(from, to, follow2) {
3090
+ await check(from, follow2 === true);
3091
+ await check(to, false);
3092
+ return fs.link(from, to, follow2);
3093
+ },
3094
+ async symlink(...args) {
3095
+ await check(args[1], false);
3096
+ return fs.symlink(...args);
3097
+ },
3098
+ async chmod(path, mode) {
3099
+ await check(path, true);
3100
+ return fs.chmod(path, mode);
3101
+ },
3102
+ async utimes(path, atime, mtime) {
3103
+ await check(path, true);
3104
+ return fs.utimes(path, atime, mtime);
3105
+ },
3106
+ async stat(path) {
3107
+ return marked(path, await fs.stat(path));
3108
+ },
3109
+ async lstat(path) {
3110
+ return marked(path, await fs.lstat(path));
3111
+ },
3112
+ async readdirStat(path) {
3113
+ const dir = normalizePath(path);
3114
+ return (await fs.readdirStat(path)).map(([name2, st]) => [
3115
+ name2,
3116
+ st && marked(`${dir}/${name2}`, st)
3117
+ ]);
3118
+ },
3119
+ ...fs.createExclusive ? {
3120
+ async createExclusive(...args) {
3121
+ await check(args[0], true);
3122
+ return fs.createExclusive?.(...args);
3123
+ }
3124
+ } : {},
3125
+ ...fs.chown ? {
3126
+ async chown(...args) {
3127
+ await check(args[0], args[1].follow !== false);
3128
+ return fs.chown?.(...args);
3129
+ }
3130
+ } : {},
3131
+ ...fs.lutimes ? {
3132
+ async lutimes(path, atime, mtime) {
3133
+ await check(path, false);
3134
+ return fs.lutimes?.(path, atime, mtime);
3135
+ }
3136
+ } : {},
3137
+ ...fs.pwrite ? {
3138
+ async pwrite(...args) {
3139
+ await check(args[0], true);
3140
+ return fs.pwrite?.(...args);
3141
+ }
3142
+ } : {},
3143
+ ...fs.truncate ? {
3144
+ async truncate(path, size) {
3145
+ await check(path, true);
3146
+ return fs.truncate?.(path, size);
3147
+ }
3148
+ } : {},
3149
+ ...fs.exclusive ? {
3150
+ async exclusive(path, run2, follow2) {
3151
+ return fs.exclusive(
3152
+ path,
3153
+ (held) => run2({
3154
+ ...held,
3155
+ writeFile: async (...args) => {
3156
+ await check(args[0], true);
3157
+ return held.writeFile(...args);
3158
+ }
3159
+ }),
3160
+ follow2
3161
+ );
3162
+ }
3163
+ } : {}
3164
+ };
3165
+ const view = {};
3166
+ for (let at = fs; at && at !== Object.prototype; at = Object.getPrototypeOf(at)) {
3167
+ for (const name2 of Object.getOwnPropertyNames(at)) {
3168
+ if (name2 === "constructor" || Object.hasOwn(view, name2)) continue;
3169
+ const value = fs[name2];
3170
+ view[name2] = typeof value === "function" ? value.bind(fs) : value;
3171
+ }
3172
+ }
3173
+ return Object.assign(view, own);
3174
+ }
3175
+ };
3176
+
3000
3177
  // src/fs/opfs.ts
3001
3178
  var S_IFDIR = 16384;
3002
3179
  var S_IFREG = 32768;
@@ -3004,7 +3181,7 @@ var S_IFLNK = 40960;
3004
3181
  var PERM_MASK = 4095;
3005
3182
  var S_ISGID3 = 1024;
3006
3183
  var READ_ATTEMPTS = 3;
3007
- var MAX_LINKS4 = 40;
3184
+ var MAX_LINKS5 = 40;
3008
3185
  var CHECK_MS = 1e3;
3009
3186
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
3010
3187
  var ERRNO_BY_DOM_ERROR = {
@@ -3041,7 +3218,7 @@ function sidecar(meta) {
3041
3218
  });
3042
3219
  return Object.fromEntries(META_OPS.map((op) => [op, guarded2(op)]));
3043
3220
  }
3044
- function missing(err) {
3221
+ function missing2(err) {
3045
3222
  const code = err?.code;
3046
3223
  return code === "ENOENT" || code === "ENOTDIR";
3047
3224
  }
@@ -3058,7 +3235,7 @@ function prefixes(path) {
3058
3235
  const parts2 = split(path);
3059
3236
  return parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`);
3060
3237
  }
3061
- function within(path, root) {
3238
+ function within2(path, root) {
3062
3239
  return path === root || path.startsWith(`${root}/`);
3063
3240
  }
3064
3241
  var LOCKED = /* @__PURE__ */ new Set(["NotReadableError", "NoModificationAllowedError", "InvalidStateError"]);
@@ -3193,14 +3370,25 @@ var OpfsFs = class {
3193
3370
  const fields = await this.newFields(path, owner, directory);
3194
3371
  if (fields) await this.meta.update(path, (entry) => ({ ...entry, path, ...fields }));
3195
3372
  }
3373
+ guarded = [];
3374
+ protect(prefixes2) {
3375
+ this.guarded = prefixes2;
3376
+ }
3377
+ refuse(path, holding = false) {
3378
+ if (isProtected(this.guarded, path) || holding && holdsProtected(this.guarded, path)) {
3379
+ throw fsError("EROFS", path);
3380
+ }
3381
+ }
3196
3382
  async chown(path, change) {
3197
3383
  if (!this.owners) return;
3198
3384
  if (change.kind === "new") {
3199
3385
  const at = resolvePath((await this.locate(parentOf4(path), true)).path, baseOf(path));
3386
+ this.refuse(at);
3200
3387
  const { uid = 0, gid = 0, mode } = change;
3201
3388
  return this.stampNew(at, { uid, gid, ...mode !== void 0 ? { mode } : {} }, false);
3202
3389
  }
3203
3390
  const found = await this.locate(path, change.follow !== false);
3391
+ this.refuse(found.path);
3204
3392
  const [stored] = found.link ? [found.link] : await this.meta.get([found.path]);
3205
3393
  const current = await this.ownerAt(found.path, stored);
3206
3394
  const next2 = { uid: change.uid ?? current.uid, gid: change.gid ?? current.gid };
@@ -3313,11 +3501,11 @@ var OpfsFs = class {
3313
3501
  }
3314
3502
  async locate(path, follow2) {
3315
3503
  let current = path;
3316
- for (let hops = 0; hops <= MAX_LINKS4; hops++) {
3504
+ for (let hops = 0; hops <= MAX_LINKS5; hops++) {
3317
3505
  try {
3318
3506
  return { path: current, handle: await this.handle(current) };
3319
3507
  } catch (err) {
3320
- if (!missing(err)) throw err;
3508
+ if (!missing2(err)) throw err;
3321
3509
  const link2 = await this.linkAlong(current);
3322
3510
  if (!link2) throw err;
3323
3511
  if (link2.path === current && !follow2) return { path: current, link: link2 };
@@ -3339,7 +3527,7 @@ var OpfsFs = class {
3339
3527
  const st = await this.lstat(at);
3340
3528
  if (!st.isSymbolicLink && !st.isDirectory) throw fsError("ENOTDIR", at);
3341
3529
  if (!st.isSymbolicLink) done4.push(part);
3342
- else if (++hops > MAX_LINKS4) throw fsError("ELOOP", path);
3530
+ else if (++hops > MAX_LINKS5) throw fsError("ELOOP", path);
3343
3531
  else {
3344
3532
  const link2 = await this.readlink(at);
3345
3533
  if (link2.startsWith("/")) done4 = [];
@@ -3436,7 +3624,7 @@ var OpfsFs = class {
3436
3624
  return this.locate(path, true).then(
3437
3625
  () => true,
3438
3626
  (err) => {
3439
- if (missing(err)) return false;
3627
+ if (missing2(err)) return false;
3440
3628
  throw err;
3441
3629
  }
3442
3630
  );
@@ -3454,8 +3642,10 @@ var OpfsFs = class {
3454
3642
  }
3455
3643
  }
3456
3644
  }
3457
- async existing(path) {
3458
- const handle = (await this.locate(path, true)).handle;
3645
+ async existing(path, writing = false) {
3646
+ const found = await this.locate(path, true);
3647
+ if (writing) this.refuse(found.path);
3648
+ const handle = found.handle;
3459
3649
  if (handle.kind === "directory") throw fsError("EISDIR", path);
3460
3650
  return handle;
3461
3651
  }
@@ -3480,7 +3670,7 @@ var OpfsFs = class {
3480
3670
  await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
3481
3671
  }
3482
3672
  async truncate(path, size) {
3483
- const handle = await this.existing(path);
3673
+ const handle = await this.existing(path, true);
3484
3674
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
3485
3675
  }
3486
3676
  async writeSession(path, truncate) {
@@ -3506,10 +3696,15 @@ var OpfsFs = class {
3506
3696
  return new TextDecoder().decode(await this.readFileBuffer(path));
3507
3697
  }
3508
3698
  async writeTarget(path) {
3699
+ const [target, existed] = await this.locateWrite(path);
3700
+ this.refuse(target);
3701
+ return [target, existed];
3702
+ }
3703
+ async locateWrite(path) {
3509
3704
  try {
3510
3705
  return [(await this.locate(path, true)).path, true];
3511
3706
  } catch (err) {
3512
- if (!missing(err)) throw err;
3707
+ if (!missing2(err)) throw err;
3513
3708
  }
3514
3709
  const [entry] = await this.meta.get([path]);
3515
3710
  const wanted = entry?.link === void 0 ? path : resolvePath(parentOf4(path), entry.link);
@@ -3578,7 +3773,7 @@ var OpfsFs = class {
3578
3773
  try {
3579
3774
  found = await this.locate(path, true);
3580
3775
  } catch (err) {
3581
- if (!missing(err)) throw err;
3776
+ if (!missing2(err)) throw err;
3582
3777
  }
3583
3778
  if (found) {
3584
3779
  const directory = found.handle?.kind === "directory";
@@ -3599,6 +3794,7 @@ var OpfsFs = class {
3599
3794
  }
3600
3795
  const parent = await this.locate(parentOf4(path), true);
3601
3796
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
3797
+ this.refuse(resolvePath(parent.path, baseOf(path)));
3602
3798
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
3603
3799
  const made2 = `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`;
3604
3800
  await this.stampNew(made2, owner, true);
@@ -3613,6 +3809,7 @@ var OpfsFs = class {
3613
3809
  if (options.force && error.code === "ENOENT") return;
3614
3810
  throw error;
3615
3811
  }
3812
+ this.refuse(found.path, true);
3616
3813
  if (found.handle) {
3617
3814
  const [dir, name2] = await this.parent(found.path);
3618
3815
  try {
@@ -3637,13 +3834,15 @@ var OpfsFs = class {
3637
3834
  async rename(from, to) {
3638
3835
  if (from === to) return;
3639
3836
  const source = await this.locate(from, false);
3837
+ this.refuse(source.path, true);
3640
3838
  this.forget(source.path);
3641
3839
  const parent = await this.locate(parentOf4(to), true);
3642
3840
  const destination = resolvePath(parent.path, baseOf(to));
3841
+ this.refuse(destination, true);
3643
3842
  const sourceStat = await this.describe(source);
3644
- if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
3843
+ if (sourceStat.isDirectory && within2(destination, source.path)) throw fsError("EINVAL", to);
3645
3844
  const target = await this.locate(destination, false).catch((err) => {
3646
- if (missing(err)) return null;
3845
+ if (missing2(err)) return null;
3647
3846
  throw err;
3648
3847
  });
3649
3848
  const targetStat = target && await this.describe(target);
@@ -3737,9 +3936,10 @@ var OpfsFs = class {
3737
3936
  const parent = await this.locate(parentOf4(path), true);
3738
3937
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
3739
3938
  const at = resolvePath(parent.path, baseOf(path));
3939
+ this.refuse(at);
3740
3940
  if (await this.locate(at, false).then(
3741
3941
  () => true,
3742
- (err) => !missing(err)
3942
+ (err) => !missing2(err)
3743
3943
  )) {
3744
3944
  throw fsError("EEXIST", path);
3745
3945
  }
@@ -3766,6 +3966,7 @@ var OpfsFs = class {
3766
3966
  }
3767
3967
  async chmod(path, mode) {
3768
3968
  const found = await this.locate(path, true);
3969
+ this.refuse(found.path);
3769
3970
  const ctimeMs = Date.now();
3770
3971
  await this.meta.update(found.path, (entry) => ({
3771
3972
  ...entry,
@@ -3781,6 +3982,7 @@ var OpfsFs = class {
3781
3982
  await this.setTimes(await this.locate(path, false), atime, mtime);
3782
3983
  }
3783
3984
  async setTimes(found, atime, mtime) {
3985
+ this.refuse(found.path);
3784
3986
  const { link: link2 } = found;
3785
3987
  if (link2) {
3786
3988
  const ctimeMs2 = Date.now();
@@ -3880,7 +4082,7 @@ function pidOwner(proc) {
3880
4082
  function webLocks() {
3881
4083
  return globalThis.navigator?.locks;
3882
4084
  }
3883
- var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
4085
+ var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3884
4086
  var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3885
4087
  var lockName = (instance) => `slicc-unlinked:${instance}`;
3886
4088
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
@@ -3980,7 +4182,7 @@ var UnlinkedKeeper = class {
3980
4182
  for (const holder of this.holders) {
3981
4183
  if (holder.owns(target)) continue;
3982
4184
  for (const path of holder.held()) {
3983
- if (!within2(path, target) || holder.isKept(path)) continue;
4185
+ if (!within3(path, target) || holder.isKept(path)) continue;
3984
4186
  held.set(path, [...held.get(path) ?? [], holder]);
3985
4187
  }
3986
4188
  }
@@ -5068,8 +5270,8 @@ function binfmtOf(binfmts, path) {
5068
5270
  var S_IFDIR2 = 16384;
5069
5271
  var S_IFREG2 = 32768;
5070
5272
  var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
5071
- var MAX_LINKS5 = 40;
5072
- function missing2(err) {
5273
+ var MAX_LINKS6 = 40;
5274
+ function missing3(err) {
5073
5275
  return err?.code === "ENOENT";
5074
5276
  }
5075
5277
  function synthetic(directory, path) {
@@ -5088,7 +5290,7 @@ function synthetic(directory, path) {
5088
5290
  }
5089
5291
  async function followLinks(fs, path) {
5090
5292
  let current = path;
5091
- for (let hops = 0; hops < MAX_LINKS5; hops++) {
5293
+ for (let hops = 0; hops < MAX_LINKS6; hops++) {
5092
5294
  const st = await fs.lstat(current).catch(() => void 0);
5093
5295
  if (!st?.isSymbolicLink) return current;
5094
5296
  const parent = current.slice(0, current.lastIndexOf("/")) || "/";
@@ -5135,7 +5337,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5135
5337
  try {
5136
5338
  return await real(path);
5137
5339
  } catch (err) {
5138
- const found = missing2(err) ? await linked(path) : void 0;
5340
+ const found = missing3(err) ? await linked(path) : void 0;
5139
5341
  if (found) return found;
5140
5342
  throw err;
5141
5343
  }
@@ -5147,7 +5349,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5147
5349
  try {
5148
5350
  return await fs.readFileBuffer(path);
5149
5351
  } catch (err) {
5150
- if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
5352
+ if (missing3(err) && (await linked(path))?.isFile) return new Uint8Array(0);
5151
5353
  throw err;
5152
5354
  }
5153
5355
  },
@@ -5163,7 +5365,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5163
5365
  const extra = await children2(path);
5164
5366
  if (!extra) return fs.readdir(path);
5165
5367
  const real = await fs.readdir(path).catch((err) => {
5166
- if (missing2(err)) return [];
5368
+ if (missing3(err)) return [];
5167
5369
  throw err;
5168
5370
  });
5169
5371
  return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
@@ -5172,7 +5374,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5172
5374
  const extra = await children2(path);
5173
5375
  if (!extra) return fs.readdirStat(path);
5174
5376
  const real = await fs.readdirStat(path).catch((err) => {
5175
- if (missing2(err)) return [];
5377
+ if (missing3(err)) return [];
5176
5378
  throw err;
5177
5379
  });
5178
5380
  const seen = new Set(real.map(([name2]) => name2));
@@ -5688,7 +5890,7 @@ function checkIds(options) {
5688
5890
  if (!ID.test(value) || Number(value) > MAX_ID2) throw errnoError("EINVAL", `${key}=${value}`);
5689
5891
  }
5690
5892
  }
5691
- var MAX_LINKS6 = 40;
5893
+ var MAX_LINKS7 = 40;
5692
5894
  var STALE_RETRIES = 3;
5693
5895
  var SETTLE_ROUNDS = 4;
5694
5896
  var DEFAULT_IO = 8 * 1024 * 1024;
@@ -5698,7 +5900,7 @@ function parseSize(text) {
5698
5900
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
5699
5901
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
5700
5902
  }
5701
- var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
5903
+ var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
5702
5904
  var HeldSet = class extends Set {
5703
5905
  idle;
5704
5906
  constructor(idle) {
@@ -5717,7 +5919,7 @@ var HeldSet = class extends Set {
5717
5919
  }
5718
5920
  };
5719
5921
  function heldUnder(held, target) {
5720
- for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
5922
+ for (const paths of held) for (const path of paths.keys()) if (within4(path, target)) return true;
5721
5923
  return false;
5722
5924
  }
5723
5925
  var HeldPaths = class extends Map {
@@ -5828,24 +6030,24 @@ var HeldPaths = class extends Map {
5828
6030
  return this.owning === path;
5829
6031
  }
5830
6032
  revoke(prefix) {
5831
- for (const path of this.keys()) if (within3(path, prefix)) this.revoked.add(path);
6033
+ for (const path of this.keys()) if (within4(path, prefix)) this.revoked.add(path);
5832
6034
  }
5833
6035
  renamed(from, to) {
5834
6036
  for (const [path, count] of [...this]) {
5835
- if (!within3(path, from)) continue;
6037
+ if (!within4(path, from)) continue;
5836
6038
  const moved2 = to + path.slice(from.length);
5837
6039
  this.delete(path);
5838
6040
  this.set(moved2, (this.get(moved2) ?? 0) + count);
5839
6041
  if (this.revoked.delete(path)) this.revoked.add(moved2);
5840
6042
  }
5841
6043
  for (const [path, count] of [...this.opens]) {
5842
- if (!within3(path, from)) continue;
6044
+ if (!within4(path, from)) continue;
5843
6045
  const moved2 = to + path.slice(from.length);
5844
6046
  this.opens.delete(path);
5845
6047
  this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
5846
6048
  }
5847
6049
  for (const [id2, key] of this.handles) {
5848
- if (within3(key, from)) this.handles.set(id2, to + key.slice(from.length));
6050
+ if (within4(key, from)) this.handles.set(id2, to + key.slice(from.length));
5849
6051
  }
5850
6052
  }
5851
6053
  isRevoked(path) {
@@ -5952,7 +6154,7 @@ var MountTable = class {
5952
6154
  locate(path) {
5953
6155
  let best;
5954
6156
  for (const mount of this.mounts.values()) {
5955
- if (within3(path, mount.target) && (!best || mount.target.length > best.target.length))
6157
+ if (within4(path, mount.target) && (!best || mount.target.length > best.target.length))
5956
6158
  best = mount;
5957
6159
  }
5958
6160
  if (!best) return void 0;
@@ -6095,7 +6297,7 @@ var MountTable = class {
6095
6297
  async commitUnder(prefix) {
6096
6298
  const at = normalizePath(prefix);
6097
6299
  await Promise.all(
6098
- [...this.sessions.keys()].filter((p) => within3(p, at)).map((p) => this.commit(p))
6300
+ [...this.sessions.keys()].filter((p) => within4(p, at)).map((p) => this.commit(p))
6099
6301
  );
6100
6302
  }
6101
6303
  async commitOverlapping(real, offset, length2) {
@@ -6130,10 +6332,10 @@ var MountTable = class {
6130
6332
  }
6131
6333
  forget(mount, path) {
6132
6334
  mount.epoch++;
6133
- for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
6335
+ for (const key of [...mount.attrs.keys()]) if (within4(key, path)) mount.attrs.delete(key);
6134
6336
  mount.lists.delete(path);
6135
6337
  mount.lists.delete(parentOf7(path));
6136
- for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
6338
+ for (const key of [...mount.lists.keys()]) if (within4(key, path)) mount.lists.delete(key);
6137
6339
  }
6138
6340
  async getattr(mount, path, rel2, settled = false) {
6139
6341
  for (let round = 1; ; round++) {
@@ -6206,7 +6408,7 @@ var MountFs = class {
6206
6408
  throw err;
6207
6409
  }
6208
6410
  if (attr2.kind !== "symlink") return real;
6209
- if (hops >= MAX_LINKS6) throw errnoError("ELOOP", path);
6411
+ if (hops >= MAX_LINKS7) throw errnoError("ELOOP", path);
6210
6412
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
6211
6413
  return this.follow(this.base.resolvePath(parentOf7(real), target), hops + 1, create);
6212
6414
  }
@@ -8851,11 +9053,11 @@ function ipv6(host) {
8851
9053
  const back = halves.length === 2 ? groups(halves[1]) : [];
8852
9054
  if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
8853
9055
  const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
8854
- const missing3 = 8 - words2.length - tail.length;
8855
- if (halves.length === 2 ? missing3 < 1 : missing3 !== 0) return void 0;
9056
+ const missing4 = 8 - words2.length - tail.length;
9057
+ if (halves.length === 2 ? missing4 < 1 : missing4 !== 0) return void 0;
8856
9058
  const all = [
8857
9059
  ...head.map((g) => Number.parseInt(g, 16)),
8858
- ...new Array(halves.length === 2 ? missing3 : 0).fill(0),
9060
+ ...new Array(halves.length === 2 ? missing4 : 0).fill(0),
8859
9061
  ...back.map((g) => Number.parseInt(g, 16)),
8860
9062
  ...tail
8861
9063
  ];
@@ -9699,7 +9901,7 @@ function parsePrefix(text) {
9699
9901
  if (length2 < 0 || length2 > max) return void 0;
9700
9902
  return { ...parsed2, length: length2 };
9701
9903
  }
9702
- function within4(prefix, addr) {
9904
+ function within5(prefix, addr) {
9703
9905
  if (prefix.v6 !== addr.v6) return false;
9704
9906
  const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
9705
9907
  return addr.bits >> shift === prefix.bits >> shift;
@@ -9739,9 +9941,9 @@ var Routes = class {
9739
9941
  if (!addr) return "unreachable";
9740
9942
  if (addr.v6 && addr.bits === 1n) return "loopback";
9741
9943
  if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
9742
- if (RESERVED.some((p) => within4(p, addr))) return "reserved";
9944
+ if (RESERVED.some((p) => within5(p, addr))) return "reserved";
9743
9945
  if (addr.v6 && !this.ipv6) return "unreachable";
9744
- if (this.exit || this.table.some((p) => within4(p, addr))) return "uplink";
9946
+ if (this.exit || this.table.some((p) => within5(p, addr))) return "uplink";
9745
9947
  return "unreachable";
9746
9948
  }
9747
9949
  };
@@ -10588,7 +10790,7 @@ function isMissing(err) {
10588
10790
  if (err?.code === "ENOENT") return true;
10589
10791
  return err instanceof Error && err.message.startsWith("ENOENT");
10590
10792
  }
10591
- function within5(path, root) {
10793
+ function within6(path, root) {
10592
10794
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
10593
10795
  }
10594
10796
  var VfsNode = class {
@@ -10909,12 +11111,12 @@ var VfsNodes = class {
10909
11111
  return node;
10910
11112
  }
10911
11113
  holds(prefix) {
10912
- for (const path of this.byPath.keys()) if (within5(path, prefix)) return true;
11114
+ for (const path of this.byPath.keys()) if (within6(path, prefix)) return true;
10913
11115
  return false;
10914
11116
  }
10915
11117
  writes(prefix) {
10916
11118
  for (const [path, node] of this.byPath)
10917
- if (node.writers > 0 && within5(path, prefix)) return true;
11119
+ if (node.writers > 0 && within6(path, prefix)) return true;
10918
11120
  return false;
10919
11121
  }
10920
11122
  closed(node, writable2 = false) {
@@ -10958,11 +11160,11 @@ var VfsNodes = class {
10958
11160
  this.kept.delete(node);
10959
11161
  }
10960
11162
  async flush(path) {
10961
- for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
11163
+ for (const [p, node] of this.byPath) if (within6(p, path)) await node.serial(() => node.flush());
10962
11164
  }
10963
11165
  async unlinking(path) {
10964
11166
  for (const [held, node] of [...this.byPath]) {
10965
- if (!within5(held, path)) continue;
11167
+ if (!within6(held, path)) continue;
10966
11168
  await node.serial(async () => {
10967
11169
  await Promise.all([this.keepsOnDisk || node.keep(), node.limit()]);
10968
11170
  });
@@ -10970,14 +11172,14 @@ var VfsNodes = class {
10970
11172
  }
10971
11173
  revoke(prefix) {
10972
11174
  for (const [path, node] of [...this.byPath]) {
10973
- if (!within5(path, prefix)) continue;
11175
+ if (!within6(path, prefix)) continue;
10974
11176
  node.revoke();
10975
11177
  this.forget(node);
10976
11178
  }
10977
11179
  }
10978
11180
  unlinked(path) {
10979
11181
  for (const [held, node] of [...this.byPath]) {
10980
- if (!within5(held, path) || this.byPath.get(held) !== node) continue;
11182
+ if (!within6(held, path) || this.byPath.get(held) !== node) continue;
10981
11183
  if (!this.dropName(held, node)) this.orphan(node);
10982
11184
  }
10983
11185
  }
@@ -10985,8 +11187,8 @@ var VfsNodes = class {
10985
11187
  if (from === to) return;
10986
11188
  const moved2 = [];
10987
11189
  for (const [p, node] of [...this.byPath]) {
10988
- if (within5(p, from)) moved2.push([p, node]);
10989
- else if (within5(p, to) && this.byPath.get(p) === node && !this.dropName(p, node)) {
11190
+ if (within6(p, from)) moved2.push([p, node]);
11191
+ else if (within6(p, to) && this.byPath.get(p) === node && !this.dropName(p, node)) {
10990
11192
  this.orphan(node);
10991
11193
  }
10992
11194
  }
@@ -11532,6 +11734,7 @@ var WasmProcess = class {
11532
11734
  this.fds.installAt(fd, await this.procFile(req, path));
11533
11735
  return fd;
11534
11736
  }
11737
+ if (this.writesAt(req) && this.options.readonlyAt?.(path)) throw new KernelError("EROFS");
11535
11738
  const open = { ...req, path };
11536
11739
  if (req.exclusive) {
11537
11740
  await nodes.createExclusive(path, this.newOwner());
@@ -11577,6 +11780,9 @@ var WasmProcess = class {
11577
11780
  };
11578
11781
  return fs.exclusive ? fs.exclusive(first.path, look) : look();
11579
11782
  }
11783
+ writesAt(req) {
11784
+ return (req.flags & WRITE_ACCESS) !== 0 || req.truncate === true || req.create === true || req.exclusive === true;
11785
+ }
11580
11786
  async mayOpen(req) {
11581
11787
  const { guard: guard2 } = this.options;
11582
11788
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
@@ -12358,6 +12564,7 @@ function processOptions(opts, header4, hooks, created3) {
12358
12564
  ...inherited(opts),
12359
12565
  ...opts.owners ? { owners: true } : {},
12360
12566
  ...opts.sealed ? { sealed: opts.sealed } : {},
12567
+ ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
12361
12568
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
12362
12569
  ...opts.identity ? { identity: opts.identity } : {},
12363
12570
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
@@ -15187,13 +15394,13 @@ function interpreter(head) {
15187
15394
  function expandDefaults(defaults, env) {
15188
15395
  const out = {};
15189
15396
  for (const [key, value] of Object.entries(defaults ?? {})) {
15190
- let missing3 = false;
15397
+ let missing4 = false;
15191
15398
  const expanded = value.replace(ENV_REFERENCE, (_, name2) => {
15192
15399
  const set2 = Object.hasOwn(env, name2) ? env[name2] : void 0;
15193
- if (set2 === void 0) missing3 = true;
15400
+ if (set2 === void 0) missing4 = true;
15194
15401
  return set2 ?? "";
15195
15402
  });
15196
- if (!missing3) out[key] = expanded;
15403
+ if (!missing4) out[key] = expanded;
15197
15404
  }
15198
15405
  return out;
15199
15406
  }
@@ -15240,6 +15447,8 @@ function concat5(chunks2) {
15240
15447
  }
15241
15448
  var Launcher = class {
15242
15449
  fs;
15450
+ processFs;
15451
+ prefixes;
15243
15452
  base;
15244
15453
  createWorker;
15245
15454
  modulesDir;
@@ -15366,6 +15575,10 @@ var Launcher = class {
15366
15575
  }
15367
15576
  );
15368
15577
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
15578
+ if (options.protect?.writer) throw new Error(PROTECT_WRITER);
15579
+ this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
15580
+ this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
15581
+ options.fs.protect?.(this.prefixes);
15369
15582
  this.users = new UserDb(this.fs, this.owners);
15370
15583
  this.sudoer = new Sudo({
15371
15584
  fs: this.fs,
@@ -15716,7 +15929,8 @@ var Launcher = class {
15716
15929
  env: req.env,
15717
15930
  cwd: req.cwd,
15718
15931
  fds: req.fds,
15719
- fs: this.fs,
15932
+ fs: this.processFs,
15933
+ ...this.prefixes.length > 0 ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
15720
15934
  createWorker: this.createWorker,
15721
15935
  onError: req.report,
15722
15936
  spawner: this.spawner(pid, req.report),
@@ -16001,6 +16215,18 @@ var Launcher = class {
16001
16215
  const keyed = () => this.jobs.fromKeyboard(send);
16002
16216
  this.settling.deliver(sig, pgid, keyed, tty.reading ? void 0 : foreground);
16003
16217
  }
16218
+ async resolvePrefixes() {
16219
+ for (const prefix of [...this.prefixes]) {
16220
+ const real = await this.base.realpath?.(prefix, true).catch(() => void 0);
16221
+ if (real !== void 0 && !this.prefixes.includes(real)) this.prefixes.push(real);
16222
+ }
16223
+ }
16224
+ guardMount(target) {
16225
+ const at = normalizePath(target);
16226
+ if (isProtected(this.prefixes, at) || holdsProtected(this.prefixes, at)) {
16227
+ throw fsError("EROFS", at);
16228
+ }
16229
+ }
16004
16230
  umaskOf(pid) {
16005
16231
  return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
16006
16232
  }
@@ -16135,6 +16361,7 @@ var Launcher = class {
16135
16361
  };
16136
16362
  }
16137
16363
  async mount(spec) {
16364
+ this.guardMount(spec.target);
16138
16365
  const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
16139
16366
  const entry = await this.mounts.mount(
16140
16367
  fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
@@ -16153,6 +16380,7 @@ var Launcher = class {
16153
16380
  [`/${UNLINKED_DIR}`, 448]
16154
16381
  ];
16155
16382
  for (const [path, mode] of layout) {
16383
+ if (isProtected(this.prefixes, normalizePath(path))) continue;
16156
16384
  const real = await this.realLayoutPath(path);
16157
16385
  const fresh2 = real !== void 0 && !await this.base.hasOwner?.(real);
16158
16386
  if (fresh2) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
@@ -16215,6 +16443,7 @@ var Launcher = class {
16215
16443
  }
16216
16444
  async processMount(pid, call, signal) {
16217
16445
  const spec = mountCall(call);
16446
+ this.guardMount(spec.target);
16218
16447
  await this.permit({ op: "mount", pid, ...spec, options: { ...spec.options } });
16219
16448
  await this.mountsOwn(
16220
16449
  this.credOf(pid),
@@ -16231,6 +16460,7 @@ var Launcher = class {
16231
16460
  }
16232
16461
  async umountAs(cred, target) {
16233
16462
  const at = normalizePath(target);
16463
+ this.guardMount(at);
16234
16464
  await this.mountsOwn(cred, at, async () => Number(this.mounts.mounted(at)?.options.uid));
16235
16465
  this.umount(at);
16236
16466
  }
@@ -16244,6 +16474,7 @@ var Launcher = class {
16244
16474
  async processUmount(pid, target, flags2) {
16245
16475
  const call = umountCall(target, flags2);
16246
16476
  let mounted;
16477
+ this.guardMount(call.target);
16247
16478
  for (; ; ) {
16248
16479
  mounted = this.mounts.mounted(call.target);
16249
16480
  await this.permit({
@@ -16258,6 +16489,7 @@ var Launcher = class {
16258
16489
  this.umount(call.target, call.detach);
16259
16490
  }
16260
16491
  async prepare() {
16492
+ await this.resolvePrefixes();
16261
16493
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
16262
16494
  if (this.owners) await this.base.chmod("/tmp", 1023);
16263
16495
  await this.provisionPnpmHome();
@@ -16314,7 +16546,7 @@ var Launcher = class {
16314
16546
  const start2 = dir ?? "/";
16315
16547
  const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
16316
16548
  const [file = ""] = argv;
16317
- await this.base.mkdir(cwd, { recursive: true });
16549
+ if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
16318
16550
  const planned = await this.plan(file, argv, cwd, scope);
16319
16551
  await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
16320
16552
  return { cwd, file, planned };
@@ -17554,6 +17786,7 @@ async function createNodeKernel(options = {}) {
17554
17786
  ...options.hostname !== void 0 ? { hostname: options.hostname } : {},
17555
17787
  ...options.users !== void 0 ? { users: options.users } : {},
17556
17788
  ...options.onDenial ? { onDenial: options.onDenial } : {},
17789
+ ...options.protect ? { protect: options.protect } : {},
17557
17790
  ...options.sealed ? { sealed: options.sealed } : {},
17558
17791
  ...options.approve ? { approve: options.approve } : {}
17559
17792
  });