@ai-ecoverse/slicc-kernel 1.65.2 → 1.66.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
@@ -532,6 +532,64 @@ async function openable(g, op, path, want, create) {
532
532
  await need(g, op, path, want, { directory: st?.isDirectory ?? false });
533
533
  }
534
534
 
535
+ // src/kernel/cred.ts
536
+ var ROOT = Object.freeze({
537
+ ruid: 0,
538
+ euid: 0,
539
+ suid: 0,
540
+ rgid: 0,
541
+ egid: 0,
542
+ sgid: 0,
543
+ groups: [0]
544
+ });
545
+ var MAX_ID = 4294967294;
546
+ var MAX_GROUPS = 65536;
547
+ var UIDS = ["ruid", "euid", "suid"];
548
+ var GIDS = ["rgid", "egid", "sgid"];
549
+ function userCred(uid, gid, groups = [gid]) {
550
+ return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
551
+ }
552
+ function copyCred(cred) {
553
+ return { ...cred, groups: [...cred.groups] };
554
+ }
555
+ function id(value) {
556
+ if (value === void 0 || value === -1 || value === 4294967295) return void 0;
557
+ if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
558
+ throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
559
+ }
560
+ return value;
561
+ }
562
+ function changeCred(cur, change) {
563
+ const privileged = cur.euid === 0;
564
+ const next2 = copyCred(cur);
565
+ for (const keys of [UIDS, GIDS]) {
566
+ const own = keys.map((key) => cur[key]);
567
+ for (const key of keys) {
568
+ const value = id(change[key]);
569
+ if (value === void 0) continue;
570
+ if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
571
+ next2[key] = value;
572
+ }
573
+ }
574
+ if (change.groups !== void 0) {
575
+ if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
576
+ throw fsError("EINVAL", "not a group list");
577
+ }
578
+ const groups = change.groups.map((g) => id(g) ?? Number.NaN);
579
+ if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
580
+ if (!privileged) throw fsError("EPERM", "setgroups");
581
+ next2.groups = groups;
582
+ }
583
+ return next2;
584
+ }
585
+ function maySignal(sender, target) {
586
+ if (sender.euid === 0) return true;
587
+ return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
588
+ }
589
+ function maySee(viewer, target) {
590
+ return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
591
+ }
592
+
535
593
  // src/kernel/pipe.ts
536
594
  var PIPE_CAPACITY = 64 * 1024;
537
595
  var PipeError = class extends Error {
@@ -839,9 +897,9 @@ var FdTable = class _FdTable {
839
897
  closesOnExec(fd) {
840
898
  return this.cloexec.has(fd);
841
899
  }
842
- setStatusFlags(fd, flags) {
900
+ setStatusFlags(fd, flags2) {
843
901
  this.get(fd);
844
- this.status.set(fd, flags);
902
+ this.status.set(fd, flags2);
845
903
  }
846
904
  statusFlags(fd) {
847
905
  return this.status.get(fd);
@@ -886,7 +944,7 @@ var FdTable = class _FdTable {
886
944
  const child = new _FdTable();
887
945
  for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
888
946
  for (const fd of this.cloexec) child.cloexec.add(fd);
889
- for (const [fd, flags] of this.status) child.status.set(fd, flags);
947
+ for (const [fd, flags2] of this.status) child.status.set(fd, flags2);
890
948
  return child;
891
949
  }
892
950
  async closeAll() {
@@ -1353,9 +1411,9 @@ var KernelSocket = class _KernelSocket {
1353
1411
  case SO_TYPE:
1354
1412
  return SOCK_STREAM;
1355
1413
  case SO_ERROR: {
1356
- const failed2 = this.failed;
1414
+ const failed3 = this.failed;
1357
1415
  this.failed = void 0;
1358
- return failed2 ? wasiErrno(failed2) : 0;
1416
+ return failed3 ? wasiErrno(failed3) : 0;
1359
1417
  }
1360
1418
  case SO_ACCEPTCONN:
1361
1419
  return this.state === "listening" ? 1 : 0;
@@ -2047,64 +2105,6 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
2047
2105
  };
2048
2106
  }
2049
2107
 
2050
- // src/kernel/cred.ts
2051
- var ROOT = Object.freeze({
2052
- ruid: 0,
2053
- euid: 0,
2054
- suid: 0,
2055
- rgid: 0,
2056
- egid: 0,
2057
- sgid: 0,
2058
- groups: [0]
2059
- });
2060
- var MAX_ID = 4294967294;
2061
- var MAX_GROUPS = 65536;
2062
- var UIDS = ["ruid", "euid", "suid"];
2063
- var GIDS = ["rgid", "egid", "sgid"];
2064
- function userCred(uid, gid, groups = [gid]) {
2065
- return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
2066
- }
2067
- function copyCred(cred) {
2068
- return { ...cred, groups: [...cred.groups] };
2069
- }
2070
- function id(value) {
2071
- if (value === void 0 || value === -1 || value === 4294967295) return void 0;
2072
- if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
2073
- throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
2074
- }
2075
- return value;
2076
- }
2077
- function changeCred(cur, change) {
2078
- const privileged = cur.euid === 0;
2079
- const next2 = copyCred(cur);
2080
- for (const keys of [UIDS, GIDS]) {
2081
- const own = keys.map((key) => cur[key]);
2082
- for (const key of keys) {
2083
- const value = id(change[key]);
2084
- if (value === void 0) continue;
2085
- if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
2086
- next2[key] = value;
2087
- }
2088
- }
2089
- if (change.groups !== void 0) {
2090
- if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
2091
- throw fsError("EINVAL", "not a group list");
2092
- }
2093
- const groups = change.groups.map((g) => id(g) ?? Number.NaN);
2094
- if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
2095
- if (!privileged) throw fsError("EPERM", "setgroups");
2096
- next2.groups = groups;
2097
- }
2098
- return next2;
2099
- }
2100
- function maySignal(sender, target) {
2101
- if (sender.euid === 0) return true;
2102
- return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
2103
- }
2104
- function maySee(viewer, target) {
2105
- return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
2106
- }
2107
-
2108
2108
  // src/users.ts
2109
2109
  var PASSWD = "/etc/passwd";
2110
2110
  var GROUP = "/etc/group";
@@ -2438,7 +2438,7 @@ var UserDb = class {
2438
2438
  };
2439
2439
 
2440
2440
  // src/client/protocol.ts
2441
- var PROTOCOL = [1, 8];
2441
+ var PROTOCOL = [1, 9];
2442
2442
  var FS_METHODS = [
2443
2443
  "readFile",
2444
2444
  "writeFile",
@@ -2596,6 +2596,12 @@ async function runAs(l, self, requested) {
2596
2596
  }
2597
2597
  return target;
2598
2598
  }
2599
+ function clientCred(self) {
2600
+ return self ? credOf(self) : copyCred(ROOT);
2601
+ }
2602
+ async function runTagged(l, self, requested, tag) {
2603
+ return { user: await runAs(l, self, requested), tag };
2604
+ }
2599
2605
  function processes(l, self) {
2600
2606
  const viewer = credOf(self);
2601
2607
  return l.list(viewer).map(({ tid: _, cred = viewer, umask: _umask, ...entry }) => ({
@@ -2730,7 +2736,7 @@ function serveClient(port, host) {
2730
2736
  async function terminal(req, l) {
2731
2737
  const session = await l.openTerminal(req.argv, {
2732
2738
  ...req.options,
2733
- user: await runAs(l, self, req.options.user),
2739
+ ...await runTagged(l, self, req.options.user, host.tag),
2734
2740
  onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
2735
2741
  });
2736
2742
  if (state === "closed") {
@@ -2752,7 +2758,7 @@ function serveClient(port, host) {
2752
2758
  let leader;
2753
2759
  const result = await l.run(req.argv, {
2754
2760
  ...req.options,
2755
- user: await runAs(l, self, req.options.user),
2761
+ ...await runTagged(l, self, req.options.user, host.tag),
2756
2762
  collect: false,
2757
2763
  onStdout: out(1),
2758
2764
  onStderr: out(2),
@@ -2793,9 +2799,9 @@ function serveClient(port, host) {
2793
2799
  case "unwatch":
2794
2800
  return watchOp(l, req, watches, send, guardFor(l, self));
2795
2801
  case "mount":
2796
- return { result: await l.mount(req.spec) };
2802
+ return { result: await l.mountAs(clientCred(self), req.spec) };
2797
2803
  case "umount":
2798
- l.umount(req.target);
2804
+ await l.umountAs(clientCred(self), req.target);
2799
2805
  return { result: true };
2800
2806
  case "mounts":
2801
2807
  return { result: l.mounts.list() };
@@ -5001,9 +5007,8 @@ async function scanFilesystems(fs, modules) {
5001
5007
  }
5002
5008
  var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
5003
5009
  async function scanBinfmts(fs, modules, warn = () => {
5004
- }) {
5010
+ }, owners = /* @__PURE__ */ new Map()) {
5005
5011
  const found = /* @__PURE__ */ new Map();
5006
- const owners = /* @__PURE__ */ new Map();
5007
5012
  for (const pkg of await packagesIn(fs, modules)) {
5008
5013
  let manifest;
5009
5014
  try {
@@ -5028,7 +5033,7 @@ async function scanBinfmts(fs, modules, warn = () => {
5028
5033
  }
5029
5034
  return found;
5030
5035
  }
5031
- function binfmtOf(binfmts, path) {
5036
+ function binfmtSuffix(binfmts, path) {
5032
5037
  const name2 = path.slice(path.lastIndexOf("/") + 1);
5033
5038
  let best;
5034
5039
  for (const suffix of binfmts.keys()) {
@@ -5036,6 +5041,10 @@ function binfmtOf(binfmts, path) {
5036
5041
  if (best === void 0 || suffix.length > best.length) best = suffix;
5037
5042
  }
5038
5043
  }
5044
+ return best;
5045
+ }
5046
+ function binfmtOf(binfmts, path) {
5047
+ const best = binfmtSuffix(binfmts, path);
5039
5048
  return best === void 0 ? void 0 : binfmts.get(best);
5040
5049
  }
5041
5050
 
@@ -5390,8 +5399,9 @@ var ChildTable = class {
5390
5399
  if (!this.forker) throw new SpawnError("ENOSYS");
5391
5400
  return this.track(state, this.parentFds.fork(), /* @__PURE__ */ new Map(), this.forker);
5392
5401
  }
5393
- async spawn(req, stdio, inherit2 = []) {
5394
- if (!this.spawner) throw new SpawnError("ENOSYS");
5402
+ async spawn(req, stdio, inherit2 = [], grant) {
5403
+ const { spawner } = this;
5404
+ if (!spawner) throw new SpawnError("ENOSYS");
5395
5405
  const fds = new FdTable();
5396
5406
  const captured = /* @__PURE__ */ new Map();
5397
5407
  try {
@@ -5409,7 +5419,7 @@ var ChildTable = class {
5409
5419
  await fds.closeAll();
5410
5420
  throw e;
5411
5421
  }
5412
- return this.track(req, fds, captured, this.spawner);
5422
+ return this.track(req, fds, captured, (r, f) => spawner(r, f, grant));
5413
5423
  }
5414
5424
  async track(req, fds, captured, start2) {
5415
5425
  let handle;
@@ -5456,34 +5466,34 @@ var ChildTable = class {
5456
5466
  }
5457
5467
  return deviceFile("null");
5458
5468
  }
5459
- async wait(pid, nohang, signal, flags = {}) {
5469
+ async wait(pid, nohang, signal, flags2 = {}) {
5460
5470
  let interrupt;
5461
5471
  try {
5462
5472
  for (; ; ) {
5463
- const candidates = this.candidates(pid, flags);
5473
+ const candidates = this.candidates(pid, flags2);
5464
5474
  if (candidates.length === 0) throw new KernelError("ECHILD");
5465
5475
  const done4 = candidates.find(([, child]) => child.code !== void 0);
5466
5476
  if (done4) return this.reap(done4[0], done4[1].code);
5467
- const changed = this.stateReport(candidates, flags);
5477
+ const changed = this.stateReport(candidates, flags2);
5468
5478
  if (changed) return changed;
5469
5479
  if (nohang) return [0, 0];
5470
5480
  interrupt ??= interrupted(signal);
5471
- await this.nextChange(candidates, flags, interrupt.promise);
5481
+ await this.nextChange(candidates, flags2, interrupt.promise);
5472
5482
  }
5473
5483
  } finally {
5474
5484
  interrupt?.done();
5475
5485
  }
5476
5486
  }
5477
- candidates(pid, flags) {
5487
+ candidates(pid, flags2) {
5478
5488
  const all = [...this.children];
5479
5489
  if (pid > 0) return all.filter(([p]) => p === pid);
5480
- const inGroup2 = flags.inGroup;
5490
+ const inGroup2 = flags2.inGroup;
5481
5491
  return pid === -1 || !inGroup2 ? all : all.filter(([p]) => inGroup2(p));
5482
5492
  }
5483
- async nextChange(candidates, flags, interrupt) {
5493
+ async nextChange(candidates, flags2, interrupt) {
5484
5494
  let wake;
5485
5495
  const stateChange = new Promise((resolve) => wake = resolve);
5486
- const watching = flags.untraced || flags.continued;
5496
+ const watching = flags2.untraced || flags2.continued;
5487
5497
  if (watching && wake) this.stateChanged.push(wake);
5488
5498
  try {
5489
5499
  await Promise.race([
@@ -5495,14 +5505,14 @@ var ChildTable = class {
5495
5505
  this.stateChanged = this.stateChanged.filter((w) => w !== wake);
5496
5506
  }
5497
5507
  }
5498
- stateReport(candidates, flags) {
5508
+ stateReport(candidates, flags2) {
5499
5509
  for (const [p, child] of candidates) {
5500
- if (flags.untraced && child.stopReport !== void 0) {
5510
+ if (flags2.untraced && child.stopReport !== void 0) {
5501
5511
  const sig = child.stopReport;
5502
5512
  child.stopReport = void 0;
5503
5513
  return [p, stoppedStatus(sig)];
5504
5514
  }
5505
- if (flags.continued && child.continueReport) {
5515
+ if (flags2.continued && child.continueReport) {
5506
5516
  child.continueReport = false;
5507
5517
  return [p, CONTINUED_STATUS];
5508
5518
  }
@@ -8661,8 +8671,8 @@ function ipv6(host) {
8661
8671
  const head = groups(halves[0]);
8662
8672
  const back = halves.length === 2 ? groups(halves[1]) : [];
8663
8673
  if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
8664
- const words = [...head, ...back].map((g) => Number.parseInt(g, 16));
8665
- const missing3 = 8 - words.length - tail.length;
8674
+ const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
8675
+ const missing3 = 8 - words2.length - tail.length;
8666
8676
  if (halves.length === 2 ? missing3 < 1 : missing3 !== 0) return void 0;
8667
8677
  const all = [
8668
8678
  ...head.map((g) => Number.parseInt(g, 16)),
@@ -10811,8 +10821,8 @@ function errCode(err) {
10811
10821
  async function targetDir(fs, path) {
10812
10822
  return parentOf(await realTarget(fs, path).catch(() => path));
10813
10823
  }
10814
- async function refuseReadonly(fs, path, flags, opts = {}) {
10815
- const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
10824
+ async function refuseReadonly(fs, path, flags2, opts = {}) {
10825
+ const writes = (flags2 & O_ACCMODE) !== 0 || opts.truncate === true;
10816
10826
  if (!writes && !opts.create || !fs.stat) return;
10817
10827
  const stat = (p) => fs.stat?.(p).catch((err) => {
10818
10828
  const code = err?.code;
@@ -11027,6 +11037,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11027
11037
  "proc-spawn",
11028
11038
  "proc-wait",
11029
11039
  "proc-captured",
11040
+ "proc-sudo",
11030
11041
  "proc-run",
11031
11042
  "proc-exit",
11032
11043
  "proc-fork",
@@ -11059,6 +11070,12 @@ var MAX_READ = 1024 * 1024;
11059
11070
  var WRITE_ACCESS = 3;
11060
11071
  var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
11061
11072
  var DEFAULT_UMASK = 18;
11073
+ function words(value) {
11074
+ if (!Array.isArray(value) || !value.every((w) => typeof w === "string")) {
11075
+ throw new KernelError("EINVAL");
11076
+ }
11077
+ return [...value];
11078
+ }
11062
11079
  var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
11063
11080
  var HOSTS_ONLY = new Resolver({ routes: new Routes() });
11064
11081
  var WasmProcess = class {
@@ -11251,17 +11268,17 @@ var WasmProcess = class {
11251
11268
  if (restart && this.options.hasPending?.()) throw new KernelError("EINTR");
11252
11269
  }
11253
11270
  async procWait(req, cancelled) {
11254
- const flags = {
11271
+ const flags2 = {
11255
11272
  untraced: req.untraced,
11256
11273
  continued: req.continued,
11257
11274
  inGroup: this.waitGroup(req.pid)
11258
11275
  };
11259
11276
  if (this.owed && !req.nohang) {
11260
- const now = await this.children.wait(req.pid, true, this.interrupt.signal, flags);
11277
+ const now = await this.children.wait(req.pid, true, this.interrupt.signal, flags2);
11261
11278
  if (now[0] !== 0) return now;
11262
11279
  }
11263
11280
  const signal = req.nohang ? this.interrupt.signal : this.blockingSignal(cancelled);
11264
- const waited = await this.children.wait(req.pid, req.nohang, signal, flags);
11281
+ const waited = await this.children.wait(req.pid, req.nohang, signal, flags2);
11265
11282
  return waited;
11266
11283
  }
11267
11284
  blockingSignal(cancelled, owing = true) {
@@ -11593,13 +11610,13 @@ var WasmProcess = class {
11593
11610
  }
11594
11611
  fdInfo(fd) {
11595
11612
  const file = this.fds.get(fd).file;
11596
- const flags = this.fds.statusFlags(fd);
11613
+ const flags2 = this.fds.statusFlags(fd);
11597
11614
  const terminal = (file.tty ?? file.pty?.slave)?.name;
11598
11615
  return {
11599
11616
  tty: file.tty !== void 0,
11600
11617
  kind: file instanceof KernelSocket ? "socket" : kernelFdKind(file),
11601
11618
  ...file.heldMeta ? { meta: file.heldMeta } : {},
11602
- ...flags !== void 0 ? { flags } : {},
11619
+ ...flags2 !== void 0 ? { flags: flags2 } : {},
11603
11620
  ...this.fds.closesOnExec(fd) ? { cloexec: true } : {},
11604
11621
  ...file.read && !file.write ? { access: "read" } : {},
11605
11622
  ...file.write && !file.read ? { access: "write" } : {},
@@ -11787,7 +11804,27 @@ var WasmProcess = class {
11787
11804
  return { ok: true, kind: "void" };
11788
11805
  case "proc-captured":
11789
11806
  return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
11790
- }
11807
+ case "proc-sudo":
11808
+ return { ok: true, kind: "json", json: await this.sudo(req, cancelled) };
11809
+ }
11810
+ }
11811
+ async sudo(req, cancelled) {
11812
+ const { sudo } = this.options;
11813
+ if (!sudo) throw new KernelError("ENOSYS");
11814
+ const call = {
11815
+ op: "proc-sudo",
11816
+ args: words(req.args),
11817
+ cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
11818
+ ...typeof req.term === "string" ? { term: req.term } : {}
11819
+ };
11820
+ const answer = await sudo(call, this.blockingSignal(cancelled));
11821
+ if (!("grant" in answer)) return answer;
11822
+ if (this.dying) throw new KernelError("EINTR");
11823
+ const { file, argv, env, cwd, cred, caller } = answer.grant;
11824
+ const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
11825
+ return {
11826
+ pid: await this.children.spawn({ file, argv, env, cwd }, stdio, [], { cred, caller })
11827
+ };
11791
11828
  }
11792
11829
  async mountSyscall(req, cancelled) {
11793
11830
  const { mount, umount } = this.options;
@@ -11795,9 +11832,9 @@ var WasmProcess = class {
11795
11832
  try {
11796
11833
  if (req.op === "umount") await umount(req.target, req.flags);
11797
11834
  else {
11798
- const { source, target, type, flags, data } = req;
11835
+ const { source, target, type, flags: flags2, data } = req;
11799
11836
  const signal = this.blockingSignal(cancelled);
11800
- await Promise.race([mount({ source, target, type, flags, data }, signal), aborted(signal)]);
11837
+ await Promise.race([mount({ source, target, type, flags: flags2, data }, signal), aborted(signal)]);
11801
11838
  }
11802
11839
  } catch (err) {
11803
11840
  const code = err?.code;
@@ -11940,14 +11977,14 @@ function descId(file) {
11940
11977
  function inheritedFds(fds) {
11941
11978
  return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
11942
11979
  const open = fds.get(fd);
11943
- const flags = fds.statusFlags(fd);
11980
+ const flags2 = fds.statusFlags(fd);
11944
11981
  const kind = open.file instanceof KernelSocket ? "socket" : kernelFdKind(open.file);
11945
11982
  const meta = open.file.heldMeta;
11946
11983
  return {
11947
11984
  fd,
11948
11985
  kind,
11949
11986
  ...kind === "device" && meta && "device" in meta ? { device: meta } : {},
11950
- ...flags !== void 0 ? { flags } : {},
11987
+ ...flags2 !== void 0 ? { flags: flags2 } : {},
11951
11988
  ...fds.closesOnExec(fd) ? { cloexec: true } : {},
11952
11989
  ...kind === "stream" ? { desc: descId(open) } : {}
11953
11990
  };
@@ -12100,6 +12137,7 @@ function processOptions(opts, header4, hooks, created3) {
12100
12137
  kill: opts.kill,
12101
12138
  ...opts.writesBack ? { writesBack: opts.writesBack } : {},
12102
12139
  processes: opts.processes,
12140
+ ...opts.sudo ? { sudo: opts.sudo } : {},
12103
12141
  openFiles: opts.openFiles,
12104
12142
  ...opts.nodes ? { nodes: opts.nodes } : {},
12105
12143
  mounts: opts.mounts,
@@ -12858,12 +12896,12 @@ function kernelFsHandlers(fs) {
12858
12896
  const attr2 = attrOf(st);
12859
12897
  return [{ name: name2, kind: attr2.kind, attr: attr2 }];
12860
12898
  }),
12861
- async open(path, flags) {
12899
+ async open(path, flags2) {
12862
12900
  const st = await existing(path);
12863
- if (st && flags.exclusive) throw fsError2("EEXIST", path);
12864
- if (st?.isDirectory && flags.write) throw fsError2("EISDIR", path);
12865
- if (!st && !flags.create) throw fsError2("ENOENT", path);
12866
- if (!st || flags.truncate) await fs.writeFile(path, new Uint8Array(0));
12901
+ if (st && flags2.exclusive) throw fsError2("EEXIST", path);
12902
+ if (st?.isDirectory && flags2.write) throw fsError2("EISDIR", path);
12903
+ if (!st && !flags2.create) throw fsError2("ENOENT", path);
12904
+ if (!st || flags2.truncate) await fs.writeFile(path, new Uint8Array(0));
12867
12905
  handles.set(++nextFh, { path });
12868
12906
  return nextFh;
12869
12907
  },
@@ -12993,24 +13031,24 @@ function absolute(target) {
12993
13031
  return normalizePath(target);
12994
13032
  }
12995
13033
  function mountCall(call) {
12996
- let flags = call.flags >>> 0;
12997
- if ((flags & MS_MGC_MSK) >>> 0 === MS_MGC_VAL) flags &= ~MS_MGC_MSK;
12998
- if (flags & ~(MS_RDONLY | IGNORED_FLAGS)) {
12999
- throw errnoError("EINVAL", `unsupported mount flags ${flags.toString(16)}`);
13034
+ let flags2 = call.flags >>> 0;
13035
+ if ((flags2 & MS_MGC_MSK) >>> 0 === MS_MGC_VAL) flags2 &= ~MS_MGC_MSK;
13036
+ if (flags2 & ~(MS_RDONLY | IGNORED_FLAGS)) {
13037
+ throw errnoError("EINVAL", `unsupported mount flags ${flags2.toString(16)}`);
13000
13038
  }
13001
13039
  if (!call.type) throw errnoError("EINVAL", "no file system type");
13002
13040
  const target = absolute(call.target);
13003
13041
  refusedTarget(target);
13004
13042
  const options = parseMountOptions(call.data);
13005
- if (flags & MS_RDONLY) options.ro = "";
13043
+ if (flags2 & MS_RDONLY) options.ro = "";
13006
13044
  return { type: call.type, source: call.source || "none", target, options };
13007
13045
  }
13008
- function umountCall(target, flags) {
13046
+ function umountCall(target, flags2) {
13009
13047
  const known = MNT_FORCE | MNT_DETACH | UMOUNT_NOFOLLOW;
13010
- if (flags >>> 0 & ~known) throw errnoError("EINVAL", `unsupported umount flags ${flags}`);
13048
+ if (flags2 >>> 0 & ~known) throw errnoError("EINVAL", `unsupported umount flags ${flags2}`);
13011
13049
  const at = absolute(target);
13012
13050
  refusedTarget(at);
13013
- return { target: at, detach: (flags & (MNT_FORCE | MNT_DETACH)) !== 0 };
13051
+ return { target: at, detach: (flags2 & (MNT_FORCE | MNT_DETACH)) !== 0 };
13014
13052
  }
13015
13053
 
13016
13054
  // src/mount/fstab.ts
@@ -13068,22 +13106,22 @@ async function mountFstab(lines2, mount, signal, retries = FSTAB_RETRIES, discar
13068
13106
  );
13069
13107
  }
13070
13108
  async function mountLine({ line, spec, invalid }, mount, signal, retries, discard) {
13071
- let failed2 = invalid;
13109
+ let failed3 = invalid;
13072
13110
  for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
13073
13111
  try {
13074
13112
  const entry = await mount(spec);
13075
13113
  if (!signal.aborted) return { line, target: spec.target, entry };
13076
13114
  discard(entry);
13077
- failed2 = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
13115
+ failed3 = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
13078
13116
  break;
13079
13117
  } catch (err) {
13080
- failed2 = err;
13118
+ failed3 = err;
13081
13119
  if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
13082
13120
  await wait(retries[attempt], signal);
13083
13121
  }
13084
13122
  }
13085
- const code = codeOf3(failed2);
13086
- return { line, target: spec.target, code, error: String(failed2?.message ?? code) };
13123
+ const code = codeOf3(failed3);
13124
+ return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
13087
13125
  }
13088
13126
 
13089
13127
  // src/mount/hostfs.ts
@@ -13260,13 +13298,13 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
13260
13298
  }, againMs);
13261
13299
  timer.unref?.();
13262
13300
  });
13263
- async function writable2(client, path, flags) {
13264
- const { ifMatch: _, ...asked } = flags;
13301
+ async function writable2(client, path, flags2) {
13302
+ const { ifMatch: _, ...asked } = flags2;
13265
13303
  const opening2 = () => client.call("open", { path: rel(path), ...asked });
13266
13304
  try {
13267
13305
  return { opened: await opening2(), restore: void 0 };
13268
13306
  } catch (err) {
13269
- if (!flags.write || codeOf4(err) !== "EACCES") throw err;
13307
+ if (!flags2.write || codeOf4(err) !== "EACCES") throw err;
13270
13308
  const was = await client.call("stat", { path: rel(path) });
13271
13309
  const mode = was.mode ?? 0;
13272
13310
  if (mode & 128) throw err;
@@ -13288,11 +13326,11 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
13288
13326
  );
13289
13327
  return entries.map(({ name: name2, attr: attr2 }) => ({ name: name2, kind: attr2.kind, attr: attr2 }));
13290
13328
  },
13291
- async open(path, flags) {
13329
+ async open(path, flags2) {
13292
13330
  const client = current();
13293
13331
  const token = client.token;
13294
- const { opened: opened2, restore } = await writable2(client, path, flags);
13295
- if (flags.ifMatch !== void 0 && opened2.attr?.etag !== flags.ifMatch) {
13332
+ const { opened: opened2, restore } = await writable2(client, path, flags2);
13333
+ if (flags2.ifMatch !== void 0 && opened2.attr?.etag !== flags2.ifMatch) {
13296
13334
  await client.call("release", { fh: opened2.fh }, true);
13297
13335
  throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
13298
13336
  }
@@ -13300,7 +13338,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
13300
13338
  client,
13301
13339
  token,
13302
13340
  fh: opened2.fh,
13303
- etag: flags.write ? void 0 : opened2.attr?.etag,
13341
+ etag: flags2.write ? void 0 : opened2.attr?.etag,
13304
13342
  restore
13305
13343
  });
13306
13344
  return nextFh;
@@ -13980,14 +14018,14 @@ function tmpfs() {
13980
14018
  if (!found) throw fsError2("EBADF", `handle ${fh}`);
13981
14019
  return found;
13982
14020
  };
13983
- function opened2(path, flags) {
14021
+ function opened2(path, flags2) {
13984
14022
  const { dir, name: name2 } = parent(path);
13985
14023
  const existing = dir.children.get(name2);
13986
- if (existing && flags.exclusive) throw fsError2("EEXIST", path);
13987
- if (existing?.kind === "directory" && flags.write) throw fsError2("EISDIR", path);
14024
+ if (existing && flags2.exclusive) throw fsError2("EEXIST", path);
14025
+ if (existing?.kind === "directory" && flags2.write) throw fsError2("EISDIR", path);
13988
14026
  if (existing) return existing;
13989
- if (!flags.create) throw fsError2("ENOENT", path);
13990
- const node = make("file", 33188, dir, flags.owner);
14027
+ if (!flags2.create) throw fsError2("ENOENT", path);
14028
+ const node = make("file", 33188, dir, flags2.owner);
13991
14029
  place(dir, name2, node);
13992
14030
  return node;
13993
14031
  }
@@ -14007,13 +14045,13 @@ function tmpfs() {
14007
14045
  ([name2, node]) => ({ name: name2, kind: node.kind, attr: attr(node) })
14008
14046
  );
14009
14047
  },
14010
- async open(path, flags) {
14011
- const node = opened2(path, flags);
14012
- if (flags.truncate && node.kind === "file") {
14048
+ async open(path, flags2) {
14049
+ const node = opened2(path, flags2);
14050
+ if (flags2.truncate && node.kind === "file") {
14013
14051
  node.data.truncate(0);
14014
14052
  node.mtime = Date.now();
14015
14053
  }
14016
- handles.set(++nextFh, { node, write: flags.write });
14054
+ handles.set(++nextFh, { node, write: flags2.write });
14017
14055
  return nextFh;
14018
14056
  },
14019
14057
  async read(fh, offset, size) {
@@ -14129,10 +14167,10 @@ function importedMemory(bytes) {
14129
14167
  return s;
14130
14168
  };
14131
14169
  const limits = () => {
14132
- const flags = bytes[at++];
14170
+ const flags2 = bytes[at++];
14133
14171
  const initial = u32();
14134
- const maximum = flags & 1 ? u32() : void 0;
14135
- return { initial, maximum, shared: (flags & 2) !== 0 };
14172
+ const maximum = flags2 & 1 ? u32() : void 0;
14173
+ return { initial, maximum, shared: (flags2 & 2) !== 0 };
14136
14174
  };
14137
14175
  while (at < bytes.length) {
14138
14176
  const id2 = bytes[at++];
@@ -14211,9 +14249,9 @@ var Reader = class {
14211
14249
  return code;
14212
14250
  }
14213
14251
  limits() {
14214
- const flags = this.byte();
14252
+ const flags2 = this.byte();
14215
14253
  this.u32();
14216
- if (flags & 1) this.u32();
14254
+ if (flags2 & 1) this.u32();
14217
14255
  }
14218
14256
  };
14219
14257
  function readTypes(r) {
@@ -14268,6 +14306,412 @@ function foreignImports(bytes) {
14268
14306
  return {};
14269
14307
  }
14270
14308
 
14309
+ // src/sudo.ts
14310
+ var SUDOERS = "/etc/sudoers";
14311
+ var SUDOERS_D = "/etc/sudoers.d";
14312
+ var SECURE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
14313
+ var DEFAULT_SUDOERS = "%sudo Cmnd *\n";
14314
+ var SUDO_TIMEOUT_MS = 3e5;
14315
+ var NAME3 = /^[a-z_][a-z0-9_.-]*$/;
14316
+ var RULE = /^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()\s]+)\)\s+)?Cmnd(?:\s+(.*))?$/;
14317
+ var TERM = /^[A-Za-z0-9._+-]+$/;
14318
+ var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
14319
+ var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
14320
+ function failed2(errno, error) {
14321
+ return { error, errno };
14322
+ }
14323
+ function unescaped(double) {
14324
+ return double.replace(/\\(["\\$`])/g, "$1");
14325
+ }
14326
+ function splitWords(text) {
14327
+ const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
14328
+ const words2 = [];
14329
+ let word;
14330
+ while (piece.lastIndex < text.length) {
14331
+ const m = piece.exec(text);
14332
+ if (!m) return void 0;
14333
+ const [, single, double, escaped, plain, space] = m;
14334
+ if (space !== void 0) {
14335
+ if (word !== void 0) words2.push(word);
14336
+ word = void 0;
14337
+ continue;
14338
+ }
14339
+ word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain));
14340
+ }
14341
+ if (word !== void 0) words2.push(word);
14342
+ return words2;
14343
+ }
14344
+ function quoteWord(word) {
14345
+ return PLAIN.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
14346
+ }
14347
+ function validWho(who) {
14348
+ if (who === "ALL") return true;
14349
+ return NAME3.test(who.startsWith("%") ? who.slice(1) : who);
14350
+ }
14351
+ function parseRule(line) {
14352
+ const text = line.trim();
14353
+ if (text === "" || text.startsWith("#")) return null;
14354
+ const m = RULE.exec(text);
14355
+ if (!m) return void 0;
14356
+ const [, nopasswd, who = "", target = "root", rest2 = ""] = m;
14357
+ if (!validWho(who) || target !== "ALL" && !NAME3.test(target)) return void 0;
14358
+ if (rest2.trim() === "*") return { nopasswd: !!nopasswd, who, target, cmnd: "*" };
14359
+ const cmnd = splitWords(rest2);
14360
+ if (!cmnd?.[0]?.startsWith("/")) return void 0;
14361
+ return { nopasswd: !!nopasswd, who, target, cmnd };
14362
+ }
14363
+ function flags(parsed2, arg, next2) {
14364
+ for (let j = 1; j < arg.length; j++) {
14365
+ const flag = arg[j];
14366
+ if (flag === "n") parsed2.nonInteractive = true;
14367
+ else if (flag === "l") parsed2.list = true;
14368
+ else if (flag !== "u") return `sudo: invalid option -- '${flag}'
14369
+ ${USAGE}`;
14370
+ else {
14371
+ const user = arg.slice(j + 1) || next2;
14372
+ if (!user) return `sudo: option requires an argument -- 'u'
14373
+ ${USAGE}`;
14374
+ parsed2.user = user;
14375
+ return arg.slice(j + 1) ? 0 : 1;
14376
+ }
14377
+ }
14378
+ return 0;
14379
+ }
14380
+ function parseArgs(args) {
14381
+ const parsed2 = { list: false, nonInteractive: false, user: "root", command: [] };
14382
+ let i = 0;
14383
+ for (; i < args.length; i++) {
14384
+ const arg = args[i];
14385
+ if (arg === "--") {
14386
+ i++;
14387
+ break;
14388
+ }
14389
+ if (!arg.startsWith("-") || arg === "-") break;
14390
+ const used = flags(parsed2, arg, args[i + 1]);
14391
+ if (typeof used === "string") return used;
14392
+ i += used;
14393
+ }
14394
+ parsed2.command = args.slice(i);
14395
+ if (parsed2.list ? parsed2.command.length > 0 : parsed2.command.length === 0) return USAGE;
14396
+ return parsed2;
14397
+ }
14398
+ function described(rule) {
14399
+ const cmnd = rule.cmnd === "*" ? "ALL" : rule.cmnd.map(quoteWord).join(" ");
14400
+ return ` (${rule.target}) ${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}`;
14401
+ }
14402
+ var Sudo = class {
14403
+ o;
14404
+ warned = /* @__PURE__ */ new Set();
14405
+ pending = /* @__PURE__ */ new Set();
14406
+ next = 0;
14407
+ constructor(options) {
14408
+ this.o = options;
14409
+ }
14410
+ warn(message3) {
14411
+ if (this.warned.has(message3)) return;
14412
+ this.warned.add(message3);
14413
+ (this.o.warn ?? console.warn)(message3);
14414
+ }
14415
+ async provision() {
14416
+ const { fs } = this.o;
14417
+ if (!await fs.exists(SUDOERS)) {
14418
+ await fs.writeFile(SUDOERS, DEFAULT_SUDOERS);
14419
+ await fs.chown?.(SUDOERS, { uid: 0, gid: 0, mode: 288, follow: false });
14420
+ }
14421
+ if (!await fs.exists(SUDOERS_D)) {
14422
+ await fs.mkdir(SUDOERS_D, { recursive: true });
14423
+ await fs.chown?.(SUDOERS_D, { uid: 0, gid: 0, mode: 488, follow: false });
14424
+ }
14425
+ }
14426
+ async trusted(path) {
14427
+ const parts2 = normalizePath(path).split("/").filter(Boolean);
14428
+ for (let n = 0; n <= parts2.length; n++) {
14429
+ const at = `/${parts2.slice(0, n).join("/")}`;
14430
+ const st = await this.o.fs.lstat(at).catch(() => void 0);
14431
+ if (!st || st.isSymbolicLink || st.uid !== 0 || (st.mode & 18) !== 0) return false;
14432
+ }
14433
+ return true;
14434
+ }
14435
+ async policyFile(path) {
14436
+ if (!await this.trusted(path)) {
14437
+ this.warn(`sudo: ${path} skipped: it must be root's, no symlink, and writable by root only`);
14438
+ return [];
14439
+ }
14440
+ const text = await this.o.fs.readFile(path).catch(() => "");
14441
+ const rules = [];
14442
+ for (const [n, line] of text.split("\n").entries()) {
14443
+ const rule = parseRule(line);
14444
+ if (rule) rules.push(rule);
14445
+ else if (rule === void 0) this.warn(`sudo: ${path}:${n + 1}: bad line skipped: ${line}`);
14446
+ }
14447
+ return rules;
14448
+ }
14449
+ async policy() {
14450
+ const rules = await this.policyFile(SUDOERS);
14451
+ const names = await this.o.fs.readdir(SUDOERS_D).catch(() => []);
14452
+ if (names.length > 0 && !await this.trusted(SUDOERS_D)) {
14453
+ this.warn(`sudo: ${SUDOERS_D} skipped: it must be root's and writable by root only`);
14454
+ return rules;
14455
+ }
14456
+ for (const name2 of [...names].sort()) {
14457
+ if (name2.startsWith(".") || name2.endsWith("~")) continue;
14458
+ rules.push(...await this.policyFile(`${SUDOERS_D}/${name2}`));
14459
+ }
14460
+ return rules;
14461
+ }
14462
+ async groupIds() {
14463
+ return new Map((await this.o.users.groups()).map((g) => [g.name, g.gid]));
14464
+ }
14465
+ async applies(caller, cred) {
14466
+ const gids = await this.groupIds();
14467
+ const mine = /* @__PURE__ */ new Set([cred.rgid, cred.egid, ...cred.groups]);
14468
+ return (rule) => {
14469
+ if (rule.who === "ALL" || rule.who === caller.name) return true;
14470
+ const gid = rule.who.startsWith("%") ? gids.get(rule.who.slice(1)) : void 0;
14471
+ return gid !== void 0 && mine.has(gid);
14472
+ };
14473
+ }
14474
+ async lookup(user) {
14475
+ const key = /^#\d+$/.test(user) ? Number(user.slice(1)) : user;
14476
+ return this.o.users.lookup(key);
14477
+ }
14478
+ async canonical(cwd) {
14479
+ const { fs } = this.o;
14480
+ const path = normalizePath(cwd);
14481
+ return fs.realpath ? fs.realpath(path, true).catch(() => void 0) : path;
14482
+ }
14483
+ async reachable(cred, cwd) {
14484
+ const walked = await access(this.o.fs, cred, cwd, X_OK, { directory: true });
14485
+ if (walked.denied !== void 0 || walked.missing) return false;
14486
+ return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
14487
+ }
14488
+ async untrusted(path, cwd) {
14489
+ const files = await this.o.backing?.(path, cwd) ?? [];
14490
+ for (const file of [path, ...files]) if (!await this.trusted(file)) return file;
14491
+ return void 0;
14492
+ }
14493
+ async locate(word, cwd) {
14494
+ const { fs } = this.o;
14495
+ const candidates = word.includes("/") ? [fs.resolvePath(cwd, word)] : SECURE_PATH.split(":").map((dir) => `${dir}/${word}`);
14496
+ for (const path of candidates) {
14497
+ const st = await fs.stat(path).catch(() => void 0);
14498
+ if (!st?.isFile || (st.mode & 73) === 0) continue;
14499
+ return fs.realpath ? fs.realpath(path, true).catch(() => path) : path;
14500
+ }
14501
+ return void 0;
14502
+ }
14503
+ async request(caller, call, signal) {
14504
+ const parsed2 = parseArgs(call.args);
14505
+ if (typeof parsed2 === "string") return failed2("EINVAL", parsed2);
14506
+ if (this.o.mode !== "enforce") {
14507
+ const mode = this.o.mode;
14508
+ if (parsed2.list) return { list: `sudo: users are '${mode}': commands run as the caller
14509
+ ` };
14510
+ return { run: parsed2.command };
14511
+ }
14512
+ const { cred } = caller;
14513
+ const self = await this.o.users.lookup(cred.ruid);
14514
+ if (!self) return failed2("EPERM", `sudo: uid ${cred.ruid} is not in the passwd database`);
14515
+ const target = await this.lookup(parsed2.user);
14516
+ if (!target) return failed2("ENOENT", `sudo: unknown user ${parsed2.user}`);
14517
+ const root = cred.euid === 0;
14518
+ const rules = root ? [] : await this.policy();
14519
+ const applies = await this.applies(self, cred);
14520
+ if (parsed2.list) return this.listed(self, root, rules.filter(applies));
14521
+ const [word = "", ...args] = parsed2.command;
14522
+ const cwd = await this.canonical(call.cwd);
14523
+ if (cwd === void 0 || !await this.reachable(cred, cwd)) {
14524
+ return failed2(
14525
+ "EACCES",
14526
+ `sudo: cannot run in ${call.cwd}: it isn't a directory you can enter`
14527
+ );
14528
+ }
14529
+ const path = await this.locate(word, cwd);
14530
+ if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
14531
+ const untrusted = await this.untrusted(path, cwd);
14532
+ if (untrusted) {
14533
+ return failed2("EPERM", `sudo: ${untrusted} must be root's, and writable by root only`);
14534
+ }
14535
+ const argv = [path, ...args];
14536
+ const matching = rules.filter(
14537
+ (rule) => applies(rule) && (rule.target === "ALL" || rule.target === target.name) && (rule.cmnd === "*" || rule.cmnd.length === argv.length && rule.cmnd.every((w, i) => w === argv[i]))
14538
+ );
14539
+ const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
14540
+ if (root || matching.some((rule) => rule.nopasswd)) return grant();
14541
+ const quoted = argv.map(quoteWord).join(" ");
14542
+ if (matching.length === 0) {
14543
+ return failed2(
14544
+ "EPERM",
14545
+ `sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
14546
+ );
14547
+ }
14548
+ if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
14549
+ if (this.pending.has(cred.ruid)) return failed2("EBUSY", "sudo: another request is pending");
14550
+ this.pending.add(cred.ruid);
14551
+ try {
14552
+ const answer = await this.ask(caller, self, target, parsed2.command, path, cwd, signal);
14553
+ if (typeof answer !== "string") return answer;
14554
+ if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
14555
+ return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
14556
+ }
14557
+ const changed = await this.untrusted(path, cwd);
14558
+ if (changed) {
14559
+ return failed2("EPERM", `sudo: ${changed} must be root's, and writable by root only`);
14560
+ }
14561
+ if (answer === "always" && !await this.remember(self, target, argv)) {
14562
+ return failed2("EIO", "sudo: the rule could not be recorded");
14563
+ }
14564
+ return grant();
14565
+ } finally {
14566
+ this.pending.delete(cred.ruid);
14567
+ }
14568
+ }
14569
+ listed(self, root, rules) {
14570
+ const host = this.o.hostname;
14571
+ if (root) {
14572
+ return {
14573
+ list: `User ${self.name} may run the following commands on ${host}:
14574
+ (ALL) NOPASSWD: ALL
14575
+ `
14576
+ };
14577
+ }
14578
+ if (rules.length === 0) {
14579
+ return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
14580
+ }
14581
+ const lines2 = rules.map(described).join("\n");
14582
+ return { list: `User ${self.name} may run the following commands on ${host}:
14583
+ ${lines2}
14584
+ ` };
14585
+ }
14586
+ async ask(caller, self, target, argv, path, cwd, requester) {
14587
+ const { approve } = this.o;
14588
+ if (!approve || requester.aborted) return failed2("EACCES", "sudo: approval denied");
14589
+ const createdAt = Date.now();
14590
+ const timeout = this.o.timeoutMs ?? SUDO_TIMEOUT_MS;
14591
+ const req = {
14592
+ id: `sudo-${++this.next}-${crypto.randomUUID()}`,
14593
+ kind: "sudo",
14594
+ user: self.name,
14595
+ uid: self.uid,
14596
+ target: target.name,
14597
+ argv: [...argv],
14598
+ path,
14599
+ cwd,
14600
+ pid: caller.pid,
14601
+ ...caller.tag !== void 0 ? { tag: caller.tag } : {},
14602
+ ...caller.tty ? { tty: caller.tty } : {},
14603
+ createdAt,
14604
+ expiresAt: createdAt + timeout,
14605
+ always: true
14606
+ };
14607
+ const control = new AbortController();
14608
+ const timer = setTimeout(() => control.abort("expired"), timeout);
14609
+ const withdraw = () => control.abort("withdrawn");
14610
+ requester.addEventListener("abort", withdraw, { once: true });
14611
+ const aborted2 = new Promise(
14612
+ (resolve) => control.signal.addEventListener("abort", () => resolve("aborted"), { once: true })
14613
+ );
14614
+ try {
14615
+ const answered = Promise.resolve().then(() => approve(req, { signal: control.signal })).catch(() => "deny");
14616
+ const answer = await Promise.race([answered, aborted2]);
14617
+ if (answer === "aborted" || control.signal.aborted) {
14618
+ return control.signal.reason === "expired" ? failed2("ETIMEDOUT", "sudo: the request expired") : failed2("EINTR", "sudo: the request was withdrawn");
14619
+ }
14620
+ return answer === "once" || answer === "always" ? answer : failed2("EACCES", "sudo: approval denied");
14621
+ } finally {
14622
+ clearTimeout(timer);
14623
+ requester.removeEventListener("abort", withdraw);
14624
+ }
14625
+ }
14626
+ async remember(self, target, argv) {
14627
+ const { fs } = this.o;
14628
+ const path = `${SUDOERS_D}/${self.name}`;
14629
+ if (!NAME3.test(self.name)) return false;
14630
+ try {
14631
+ await this.provision();
14632
+ if (!await this.trusted(SUDOERS_D)) return false;
14633
+ const st = await fs.lstat(path).catch(() => void 0);
14634
+ if (st && !await this.trusted(path)) return false;
14635
+ const old = st ? await fs.readFile(path) : "";
14636
+ const line = `NOPASSWD ${self.name} (${target.name}) Cmnd ${argv.map(quoteWord).join(" ")}
14637
+ `;
14638
+ const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
14639
+ ${line}`;
14640
+ await fs.writeFile(path, text);
14641
+ await fs.chown?.(path, { uid: 0, gid: 0, mode: 288, follow: false });
14642
+ return true;
14643
+ } catch {
14644
+ return false;
14645
+ }
14646
+ }
14647
+ grant(self, target, cred, argv, file, cwd, term) {
14648
+ const env = {
14649
+ ...this.o.env(),
14650
+ ...userEnv(target, this.o.pnpmHome),
14651
+ SHELL: target.shell,
14652
+ PATH: SECURE_PATH,
14653
+ PWD: cwd,
14654
+ ...term !== void 0 && TERM.test(term) ? { TERM: term } : {},
14655
+ SUDO_USER: self.name,
14656
+ SUDO_UID: String(cred.ruid),
14657
+ SUDO_GID: String(cred.rgid),
14658
+ SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
14659
+ };
14660
+ return { grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred } };
14661
+ }
14662
+ };
14663
+
14664
+ // src/sudo-command.ts
14665
+ var SUDO_PATH = "/usr/bin/sudo";
14666
+ var SUDO_COMMAND = {
14667
+ name: "sudo",
14668
+ abi: "js",
14669
+ glue: SUDO_PATH,
14670
+ wasm: SUDO_PATH,
14671
+ argv0: "sudo"
14672
+ };
14673
+ var SUDO_SOURCE = [
14674
+ "export default async function sudo(ctx) {",
14675
+ " const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);",
14676
+ " let pid;",
14677
+ " let held;",
14678
+ " const relay = (sig) => {",
14679
+ " if (pid === undefined) held = sig;",
14680
+ " else return ctx.kill(pid, sig).catch(() => undefined);",
14681
+ " };",
14682
+ ' for (const sig of ["SIGTERM", "SIGHUP"]) await ctx.signals.on(sig, relay);',
14683
+ " const failed = () => (held === undefined ? 1 : 128 + held);",
14684
+ " let answer;",
14685
+ " try {",
14686
+ " answer = await ctx.sudo(ctx.argv.slice(1));",
14687
+ " } catch (err) {",
14688
+ ' await say(2, "sudo: " + (err && err.code ? err.code : String(err)) + "\\n");',
14689
+ " return failed();",
14690
+ " }",
14691
+ " if (answer.error !== undefined) {",
14692
+ ' await say(2, answer.error + "\\n");',
14693
+ " return failed();",
14694
+ " }",
14695
+ " if (answer.list !== undefined) {",
14696
+ " await say(1, answer.list);",
14697
+ " return 0;",
14698
+ " }",
14699
+ ' for (const sig of ["SIGINT", "SIGQUIT"]) await ctx.signals.on(sig, () => undefined);',
14700
+ " if (answer.run !== undefined) {",
14701
+ " try {",
14702
+ " pid = (await ctx.spawn({ argv: answer.run })).pid;",
14703
+ " } catch (err) {",
14704
+ ' const why = err && err.code === "ENOENT" ? "command not found" : String(err && err.code);',
14705
+ ' await say(2, "sudo: " + answer.run[0] + ": " + why + "\\n");',
14706
+ " return failed();",
14707
+ " }",
14708
+ " } else pid = answer.pid;",
14709
+ " if (held !== undefined) await relay(held);",
14710
+ " return (await ctx.wait(pid)).status;",
14711
+ "}",
14712
+ ""
14713
+ ].join("\n");
14714
+
14271
14715
  // src/launcher.ts
14272
14716
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
14273
14717
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
@@ -14295,9 +14739,21 @@ function usersMode(mode = "off") {
14295
14739
  if (USERS_MODES.includes(mode)) return mode;
14296
14740
  throw new Error(`users must be 'off', 'report' or 'enforce', not ${String(mode)}`);
14297
14741
  }
14742
+ function tagOption(tag) {
14743
+ if (tag !== void 0 && typeof tag !== "string") throw new TypeError("a tag must be a string");
14744
+ }
14298
14745
  function baseName(path) {
14299
14746
  return path.slice(path.lastIndexOf("/") + 1);
14300
14747
  }
14748
+ function packageRootOf(path, roots) {
14749
+ for (const root of roots) {
14750
+ const base = normalizePath(root);
14751
+ if (!path.startsWith(`${base}/`)) continue;
14752
+ const [first = "", second] = path.slice(base.length + 1).split("/");
14753
+ return first.startsWith("@") && second !== void 0 ? `${base}/${first}/${second}` : `${base}/${first}`;
14754
+ }
14755
+ return void 0;
14756
+ }
14301
14757
  function modulePath(glue) {
14302
14758
  return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
14303
14759
  }
@@ -14309,11 +14765,11 @@ function shebang(head) {
14309
14765
  return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
14310
14766
  }
14311
14767
  function interpreter(head) {
14312
- const words = shebang(head);
14313
- if (!words) return "node";
14314
- if (baseName(words[0]) === "env") words.shift();
14315
- if (words[0] === "-S") words.shift();
14316
- return baseName(words[0] ?? "");
14768
+ const words2 = shebang(head);
14769
+ if (!words2) return "node";
14770
+ if (baseName(words2[0]) === "env") words2.shift();
14771
+ if (words2[0] === "-S") words2.shift();
14772
+ return baseName(words2[0] ?? "");
14317
14773
  }
14318
14774
  function expandDefaults(defaults, env) {
14319
14775
  const out = {};
@@ -14387,7 +14843,10 @@ var Launcher = class {
14387
14843
  orphans = /* @__PURE__ */ new Set();
14388
14844
  described = /* @__PURE__ */ new Map();
14389
14845
  creds = /* @__PURE__ */ new Map();
14846
+ tags = /* @__PURE__ */ new Map();
14847
+ granted = /* @__PURE__ */ new Map();
14390
14848
  users;
14849
+ sudoer;
14391
14850
  jobs = new JobTable();
14392
14851
  locks = new LockTable();
14393
14852
  settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
@@ -14488,6 +14947,17 @@ var Launcher = class {
14488
14947
  );
14489
14948
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
14490
14949
  this.users = new UserDb(this.fs, this.owners);
14950
+ this.sudoer = new Sudo({
14951
+ fs: this.fs,
14952
+ users: this.users,
14953
+ mode: this.usersMode,
14954
+ hostname,
14955
+ env: () => this.env,
14956
+ pnpmHome: this.pnpmHome,
14957
+ backing: (path, cwd) => this.backing(path, cwd),
14958
+ approve: options.approve,
14959
+ timeoutMs: options.sudoTimeoutMs
14960
+ });
14491
14961
  void this.unlinked.sweep("/");
14492
14962
  this.watchers.watch(
14493
14963
  [this.modulesDir, this.pnpmHome],
@@ -14508,7 +14978,7 @@ var Launcher = class {
14508
14978
  commands(scope = "") {
14509
14979
  let found = this.catalogs.get(scope);
14510
14980
  if (!found) {
14511
- found = this.roots(scope).then((roots) => scanCommands(this.base, roots));
14981
+ found = this.roots(scope).then((roots) => scanCommands(this.base, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
14512
14982
  this.catalogs.set(scope, found);
14513
14983
  }
14514
14984
  return found;
@@ -14631,10 +15101,10 @@ var Launcher = class {
14631
15101
  const head = await this.head(script);
14632
15102
  if (!head) return void 0;
14633
15103
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
14634
- const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
14635
- if (!words) return void 0;
14636
- if (baseName(words[0]) === "env") words.shift();
14637
- const [interp, ...rest2] = words;
15104
+ const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
15105
+ if (!words2) return void 0;
15106
+ if (baseName(words2[0]) === "env") words2.shift();
15107
+ const [interp, ...rest2] = words2;
14638
15108
  const arg = rest2.join(" ");
14639
15109
  const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
14640
15110
  const found = interp ? await this.resolve(interp, interp, cwd, scope) : void 0;
@@ -14671,6 +15141,41 @@ var Launcher = class {
14671
15141
  await need(g, "exec", this.fs.resolvePath(req.cwd, file), want);
14672
15142
  }
14673
15143
  }
15144
+ async grantedCwd(caller, cwd) {
15145
+ const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
15146
+ return real === cwd && await this.sudoer.reachable(caller, cwd);
15147
+ }
15148
+ async binfmtManifests(scripts) {
15149
+ const sources = /* @__PURE__ */ new Map();
15150
+ const binfmts = await scanBinfmts(this.base, await this.roots(), void 0, sources);
15151
+ const manifests = [];
15152
+ for (const script of scripts) {
15153
+ const head = await this.head(script);
15154
+ if (!head || isWasm(head) || shebang(head)) continue;
15155
+ const pkg = sources.get(binfmtSuffix(binfmts, script) ?? "");
15156
+ if (pkg !== void 0) manifests.push(`${pkg}/package.json`);
15157
+ }
15158
+ return manifests;
15159
+ }
15160
+ async backing(file, cwd) {
15161
+ const planned = await this.plan(file, [file], cwd, "");
15162
+ if (!planned) return [];
15163
+ const { glue, wasm, imports } = planned.target;
15164
+ const files = [glue, wasm, ...imports ? [imports] : []];
15165
+ const scripts = [];
15166
+ for (const name2 of [file, ...planned.via ?? []]) {
15167
+ if (name2.includes("/") && !COMMAND2.test(name2)) scripts.push(this.fs.resolvePath(cwd, name2));
15168
+ const script = (await this.scriptCommand(name2, cwd, ""))?.script;
15169
+ if (script) scripts.push(script);
15170
+ }
15171
+ files.push(...scripts, ...await this.binfmtManifests(scripts));
15172
+ const roots = await this.roots();
15173
+ for (const at of [...files]) {
15174
+ const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
15175
+ if (root !== void 0) files.push(`${root}/package.json`);
15176
+ }
15177
+ return [...new Set(files)].filter((path) => path !== "" && path !== SUDO_PATH);
15178
+ }
14674
15179
  async unrunnable(file, cwd, scope) {
14675
15180
  const path = this.fs.resolvePath(cwd, file);
14676
15181
  const head = await this.base.readFileBuffer(path).catch(() => null);
@@ -14708,6 +15213,9 @@ var Launcher = class {
14708
15213
  return await this.base.exists(optional) ? optional : void 0;
14709
15214
  }
14710
15215
  async program(target, env) {
15216
+ if (target.abi === "js" && target.wasm === SUDO_PATH) {
15217
+ return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
15218
+ }
14711
15219
  if (target.abi === "js") {
14712
15220
  return { abi: "js", glue: await this.base.readFile(target.wasm), path: target.wasm };
14713
15221
  }
@@ -14794,6 +15302,7 @@ var Launcher = class {
14794
15302
  kill: (target, sig) => this.kill(target, sig, this.credOf(pid), pid),
14795
15303
  writesBack: (target) => this.processes.get(target)?.writesBack() === true,
14796
15304
  processes: () => ({ boot: this.boot, processes: this.list(this.credOf(pid)) }),
15305
+ sudo: (call, signal) => this.sudo(pid, call, signal),
14797
15306
  openFiles: this.openFiles,
14798
15307
  nodes: this.nodes,
14799
15308
  held: this.held,
@@ -14801,7 +15310,7 @@ var Launcher = class {
14801
15310
  statfs: (path) => this.mounts.statfs(path),
14802
15311
  mounts: () => this.mounts.list(),
14803
15312
  mount: (call, signal) => this.processMount(pid, call, signal),
14804
- umount: (target, flags) => this.processUmount(pid, target, flags),
15313
+ umount: (target, flags2) => this.processUmount(pid, target, flags2),
14805
15314
  jobs: this.jobs,
14806
15315
  ptys: this.ptys,
14807
15316
  net: this.net,
@@ -14815,6 +15324,7 @@ var Launcher = class {
14815
15324
  this.processes.set(pid, handle);
14816
15325
  const { cred } = req;
14817
15326
  this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
15327
+ if (req.tag !== void 0) this.tags.set(pid, req.tag);
14818
15328
  if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
14819
15329
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
14820
15330
  if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
@@ -14842,6 +15352,8 @@ var Launcher = class {
14842
15352
  this.jobs.remove(pid);
14843
15353
  this.described.delete(pid);
14844
15354
  this.creds.delete(pid);
15355
+ this.tags.delete(pid);
15356
+ this.granted.delete(pid);
14845
15357
  }
14846
15358
  reaped(pid) {
14847
15359
  if (this.zombies.delete(pid)) this.forget(pid);
@@ -14863,8 +15375,8 @@ var Launcher = class {
14863
15375
  };
14864
15376
  const listed = [];
14865
15377
  for (const member of members.values()) {
14866
- const described = this.described.get(member.pid);
14867
- if (member.execed || !described) continue;
15378
+ const described2 = this.described.get(member.pid);
15379
+ if (member.execed || !described2) continue;
14868
15380
  const cred = this.credOf(member.pid);
14869
15381
  if (!maySee(viewer, cred)) continue;
14870
15382
  const root = rootOf(member);
@@ -14874,8 +15386,8 @@ var Launcher = class {
14874
15386
  ppid: shown(root.ppid),
14875
15387
  pgid: member.pgid,
14876
15388
  sid: member.sid,
14877
- ...described,
14878
- started: (this.described.get(root.pid) ?? described).started,
15389
+ ...described2,
15390
+ started: (this.described.get(root.pid) ?? described2).started,
14879
15391
  state: this.processes.has(member.pid) ? "S" : "Z",
14880
15392
  memory: this.processes.get(member.pid)?.memory() ?? 0,
14881
15393
  umask: this.umaskOf(member.pid),
@@ -14885,12 +15397,26 @@ var Launcher = class {
14885
15397
  }
14886
15398
  return listed;
14887
15399
  }
15400
+ sudo(pid, call, signal) {
15401
+ const caller = {
15402
+ cred: this.credOf(pid),
15403
+ pid: this.jobs.shown(pid),
15404
+ tag: this.tagOf(pid),
15405
+ tty: this.described.get(pid)?.tty
15406
+ };
15407
+ return this.sudoer.request(caller, call, signal);
15408
+ }
14888
15409
  spawner(ppid, report) {
14889
- return async (req, fds) => {
14890
- const scope = await this.scopeOf(this.credOf(ppid).euid);
15410
+ return async (req, fds, grant) => {
15411
+ if (grant && !await this.grantedCwd(grant.caller, req.cwd)) {
15412
+ await fds.closeAll();
15413
+ throw new SpawnError("EACCES");
15414
+ }
15415
+ const cred = grant ? grant.cred : this.credOf(ppid);
15416
+ const scope = grant ? "" : await this.scopeOf(cred.euid);
14891
15417
  const planned = await this.plan(req.file, req.argv, req.cwd, scope);
14892
15418
  try {
14893
- await this.mayExec(this.credOf(ppid), { ...req, pid: ppid }, planned);
15419
+ await this.mayExec(cred, { ...req, pid: ppid }, planned);
14894
15420
  } catch (err) {
14895
15421
  await fds.closeAll();
14896
15422
  throw err.code === "EACCES" ? new SpawnError("EACCES") : err;
@@ -14910,10 +15436,12 @@ var Launcher = class {
14910
15436
  ppid,
14911
15437
  ignored: this.ignoredBy(ppid),
14912
15438
  umask: this.umaskOf(ppid),
14913
- cred: this.credOf(ppid),
15439
+ cred,
15440
+ tag: this.tagOf(ppid),
14914
15441
  ...exec
14915
15442
  });
14916
15443
  deciding?.attach();
15444
+ if (grant) this.granted.set(handle.pid, ppid);
14917
15445
  return childHandle(handle);
14918
15446
  };
14919
15447
  }
@@ -14957,7 +15485,8 @@ var Launcher = class {
14957
15485
  fork: state,
14958
15486
  ignored: this.ignoredBy(ppid, true),
14959
15487
  umask: this.umaskOf(ppid),
14960
- cred: this.credOf(ppid)
15488
+ cred: this.credOf(ppid),
15489
+ tag: this.tagOf(ppid)
14961
15490
  })
14962
15491
  );
14963
15492
  }
@@ -14982,6 +15511,9 @@ var Launcher = class {
14982
15511
  credOf(pid) {
14983
15512
  return this.creds.get(pid)?.() ?? copyCred(ROOT);
14984
15513
  }
15514
+ tagOf(pid) {
15515
+ return this.tags.get(pid);
15516
+ }
14985
15517
  released(path) {
14986
15518
  const at = normalizePath(path);
14987
15519
  this.unlinked.closed(at);
@@ -15000,7 +15532,7 @@ var Launcher = class {
15000
15532
  }
15001
15533
  kill(pid, sig, sender, from) {
15002
15534
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
15003
- const may = (target) => !sender || maySignal(sender, this.credOf(target)) || sig === SIG.CONT && session !== void 0 && this.jobs.sidOf(target) === session || from !== void 0 && this.imageOf(from) === this.imageOf(target);
15535
+ const may = (target) => !sender || maySignal(sender, this.credOf(target)) || sig === SIG.CONT && session !== void 0 && this.jobs.sidOf(target) === session || from !== void 0 && this.imageOf(from) === this.imageOf(target) || from !== void 0 && this.granted.get(this.jobs.shown(target)) === from;
15004
15536
  if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
15005
15537
  const handle = this.processes.get(pid);
15006
15538
  if (!handle) return false;
@@ -15121,7 +15653,8 @@ var Launcher = class {
15121
15653
  if (fresh) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
15122
15654
  }
15123
15655
  }
15124
- async realLayoutPath(path) {
15656
+ async realLayoutPath(given) {
15657
+ const path = normalizePath(given);
15125
15658
  if (!await this.base.exists(path)) return void 0;
15126
15659
  if (path === "/") return path;
15127
15660
  return joinName(await realDir(this.base, parentOf(path)), baseName(path));
@@ -15132,7 +15665,8 @@ var Launcher = class {
15132
15665
  mountedBy(spec, cred) {
15133
15666
  if (!this.owners || cred.euid === 0 && cred.egid === 0) return spec;
15134
15667
  const ids = { uid: String(cred.euid), gid: String(cred.egid) };
15135
- return { ...spec, options: { ...ids, ...spec.options } };
15668
+ const options = cred.euid === 0 ? { ...ids, ...spec.options } : { ...spec.options, ...ids };
15669
+ return { ...spec, options };
15136
15670
  }
15137
15671
  mountRoot(path) {
15138
15672
  let root = "/";
@@ -15177,13 +15711,36 @@ var Launcher = class {
15177
15711
  async processMount(pid, call, signal) {
15178
15712
  const spec = mountCall(call);
15179
15713
  await this.permit({ op: "mount", pid, ...spec, options: { ...spec.options } });
15714
+ await this.mountsOwn(
15715
+ this.credOf(pid),
15716
+ spec.target,
15717
+ async () => (await this.fs.stat(spec.target)).uid
15718
+ );
15180
15719
  const entry = await this.mount(this.mountedBy(spec, this.credOf(pid)));
15181
15720
  if (signal.aborted) this.umount(entry.target, true);
15182
15721
  }
15183
- async processUmount(pid, target, flags) {
15184
- const call = umountCall(target, flags);
15722
+ async mountAs(cred, spec) {
15723
+ const target = normalizePath(spec.target);
15724
+ await this.mountsOwn(cred, target, async () => (await this.fs.stat(target)).uid);
15725
+ return this.mount(this.mountedBy(spec, cred));
15726
+ }
15727
+ async umountAs(cred, target) {
15728
+ const at = normalizePath(target);
15729
+ await this.mountsOwn(cred, at, async () => Number(this.mounts.mounted(at)?.options.uid));
15730
+ this.umount(at);
15731
+ }
15732
+ async mountsOwn(cred, target, owner) {
15733
+ const { euid } = cred;
15734
+ if (this.usersMode !== "enforce" || euid === 0) return;
15735
+ if (await owner().catch(() => void 0) !== euid) {
15736
+ throw fsError("EPERM", `${target} is not the caller's to mount or unmount`);
15737
+ }
15738
+ }
15739
+ async processUmount(pid, target, flags2) {
15740
+ const call = umountCall(target, flags2);
15741
+ let mounted;
15185
15742
  for (; ; ) {
15186
- const mounted = this.mounts.mounted(call.target);
15743
+ mounted = this.mounts.mounted(call.target);
15187
15744
  await this.permit({
15188
15745
  op: "umount",
15189
15746
  pid,
@@ -15192,6 +15749,7 @@ var Launcher = class {
15192
15749
  });
15193
15750
  if (this.mounts.mounted(call.target) === mounted) break;
15194
15751
  }
15752
+ await this.mountsOwn(this.credOf(pid), call.target, async () => Number(mounted?.options.uid));
15195
15753
  this.umount(call.target, call.detach);
15196
15754
  }
15197
15755
  async prepare() {
@@ -15200,6 +15758,7 @@ var Launcher = class {
15200
15758
  await this.provisionPnpmHome();
15201
15759
  await this.users.ensure();
15202
15760
  if (this.owners) await this.stampLayout();
15761
+ if (this.owners) await this.sudoer.provision();
15203
15762
  await writeCaFile(this.base, this.ca).catch(() => void 0);
15204
15763
  this.fstab = this.mountFstab();
15205
15764
  }
@@ -15237,8 +15796,9 @@ var Launcher = class {
15237
15796
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
15238
15797
  return found;
15239
15798
  }
15240
- identity(account) {
15799
+ identity(account, tag) {
15241
15800
  return {
15801
+ tag,
15242
15802
  cred: credOf(account),
15243
15803
  ...account.umask !== void 0 ? { umask: account.umask } : {}
15244
15804
  };
@@ -15255,6 +15815,7 @@ var Launcher = class {
15255
15815
  return { cwd, file, planned };
15256
15816
  }
15257
15817
  async openTerminal(argv, options) {
15818
+ tagOption(options.tag);
15258
15819
  const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
15259
15820
  const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
15260
15821
  if (!planned) throw new Error(`${file}: command not found`);
@@ -15283,7 +15844,13 @@ var Launcher = class {
15283
15844
  );
15284
15845
  const report = (message3) => options.onData(encoder3.encode(`${message3}\r
15285
15846
  `));
15286
- const handle = await this.launch(planned, { env, cwd, fds, report, ...this.identity(account) });
15847
+ const handle = await this.launch(planned, {
15848
+ env,
15849
+ cwd,
15850
+ fds,
15851
+ report,
15852
+ ...this.identity(account, options.tag)
15853
+ });
15287
15854
  leader = handle.pid;
15288
15855
  void handle.exited.then(() => tty.hangup());
15289
15856
  return {
@@ -15299,6 +15866,7 @@ var Launcher = class {
15299
15866
  };
15300
15867
  }
15301
15868
  async run(argv, options = {}) {
15869
+ tagOption(options.tag);
15302
15870
  const account = await this.account(options.user);
15303
15871
  const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
15304
15872
  const env = this.environment(cwd, options.env, account);
@@ -15328,7 +15896,7 @@ var Launcher = class {
15328
15896
  fds,
15329
15897
  report,
15330
15898
  ...group,
15331
- ...this.identity(account)
15899
+ ...this.identity(account, options.tag)
15332
15900
  });
15333
15901
  options.onStarted?.(handle.pid);
15334
15902
  const status = await handle.exited;
@@ -16480,7 +17048,8 @@ async function createNodeKernel(options = {}) {
16480
17048
  ...options.cdp ? { cdp: options.cdp } : {},
16481
17049
  ...options.hostname !== void 0 ? { hostname: options.hostname } : {},
16482
17050
  ...options.users !== void 0 ? { users: options.users } : {},
16483
- ...options.onDenial ? { onDenial: options.onDenial } : {}
17051
+ ...options.onDenial ? { onDenial: options.onDenial } : {},
17052
+ ...options.approve ? { approve: options.approve } : {}
16484
17053
  });
16485
17054
  await launcher.prepare();
16486
17055
  const guard2 = (work) => new Promise((resolve, reject) => {
@@ -16556,8 +17125,9 @@ async function createNodeKernel(options = {}) {
16556
17125
  addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
16557
17126
  removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
16558
17127
  },
16559
- async connect({ user } = {}) {
17128
+ async connect({ user, tag } = {}) {
16560
17129
  if (terminated) throw new Error(TERMINATED);
17130
+ tagOption(tag);
16561
17131
  if (user !== void 0 && !await launcher.users.lookup(user)) {
16562
17132
  throw fsError("ENOENT", `no user ${String(user)}`);
16563
17133
  }
@@ -16565,7 +17135,8 @@ async function createNodeKernel(options = {}) {
16565
17135
  const served = serveClient(port1, {
16566
17136
  launcher: async () => launcher,
16567
17137
  signal: signalNumber,
16568
- ...user !== void 0 ? { user } : {}
17138
+ ...user !== void 0 ? { user } : {},
17139
+ ...tag !== void 0 ? { tag } : {}
16569
17140
  });
16570
17141
  clients.add(served);
16571
17142
  void served.closed.then(() => clients.delete(served));