@ai-ecoverse/slicc-kernel 1.180.0 → 1.181.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.
@@ -652,8 +652,8 @@ function dirsChannel(metadata) {
652
652
  function noticeOf(data) {
653
653
  if (Array.isArray(data)) return { paths: data.filter((p) => typeof p === "string"), trees: [] };
654
654
  const notice = data ?? {};
655
- const strings = (list) => Array.isArray(list) ? list.filter((p) => typeof p === "string") : [];
656
- return { paths: strings(notice.paths), trees: strings(notice.trees), outside: true };
655
+ const strings2 = (list) => Array.isArray(list) ? list.filter((p) => typeof p === "string") : [];
656
+ return { paths: strings2(notice.paths), trees: strings2(notice.trees), outside: true };
657
657
  }
658
658
 
659
659
  // src/fs/types.ts
@@ -8206,7 +8206,7 @@ var UserDb = class {
8206
8206
  };
8207
8207
 
8208
8208
  // src/client/protocol.ts
8209
- var PROTOCOL = [1, 15];
8209
+ var PROTOCOL = [1, 16];
8210
8210
  var FS_METHODS = [
8211
8211
  "readFile",
8212
8212
  "writeFile",
@@ -8362,9 +8362,9 @@ async function accountOf(l, user) {
8362
8362
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
8363
8363
  return found;
8364
8364
  }
8365
- async function runAs(l, self, requested) {
8366
- if (requested === void 0) return self;
8367
- const target = await accountOf(l, requested);
8365
+ async function runAs(l, self, requested2) {
8366
+ if (requested2 === void 0) return self;
8367
+ const target = await accountOf(l, requested2);
8368
8368
  if (self.uid !== 0 && target.uid !== self.uid) {
8369
8369
  throw fsError("EPERM", `${self.name} may not run as ${target.name}`);
8370
8370
  }
@@ -8373,21 +8373,9 @@ async function runAs(l, self, requested) {
8373
8373
  function clientCred(self) {
8374
8374
  return self ? credOf(self) : copyCred(ROOT);
8375
8375
  }
8376
- var MAX_SPAWN_TAGS = 8;
8377
- function spawnTags(value2) {
8378
- if (!Array.isArray(value2)) {
8379
- tagOption(value2);
8380
- return value2 === void 0 ? [] : [value2];
8381
- }
8382
- if (value2.length > MAX_SPAWN_TAGS) {
8383
- throw new TypeError(`a spawn adds at most ${MAX_SPAWN_TAGS} tags`);
8384
- }
8385
- for (const each of value2) tagOption(each ?? 0);
8386
- return value2;
8387
- }
8388
8376
  async function runTagged(l, self, options, tag, session = -1) {
8389
8377
  let tags = withTag(void 0, tag);
8390
- for (const added of spawnTags(options.tag)) tags = withTag(tags, added);
8378
+ for (const added of tagList(options.tag)) tags = withTag(tags, added);
8391
8379
  return {
8392
8380
  user: await runAs(l, self, options.user),
8393
8381
  tag,
@@ -8572,6 +8560,20 @@ function requestBinding(send2, port) {
8572
8560
  close: () => unbind?.()
8573
8561
  };
8574
8562
  }
8563
+ async function sudoRunAs(l, self, host, req, reply) {
8564
+ if (self.uid !== 0) throw sudoFailure("EPERM", "sudo: only a root client may run a sudo request");
8565
+ const { stdout, stderr, tag } = req.options;
8566
+ const running = await l.sudoRun(req.replay, {
8567
+ stdout,
8568
+ stderr,
8569
+ tag,
8570
+ base: host.tag,
8571
+ session: host.session
8572
+ });
8573
+ reply(req.id, { started: running.pid });
8574
+ const { status, signal } = await running.ended;
8575
+ return { result: status, ...signalField(signal) };
8576
+ }
8575
8577
  function answered(pending, req, reply, detach) {
8576
8578
  pending.then(
8577
8579
  ({ result, signal, transfer }) => {
@@ -8721,6 +8723,8 @@ function serveClient(port, host) {
8721
8723
  case "trace":
8722
8724
  case "untrace":
8723
8725
  return traces.answer(l, self, req);
8726
+ case "sudo-run":
8727
+ return sudoRunAs(l, self, host, req, reply);
8724
8728
  case "detach":
8725
8729
  return { result: true };
8726
8730
  default:
@@ -8904,7 +8908,7 @@ function satisfies(version, range2) {
8904
8908
  }
8905
8909
 
8906
8910
  // src/version.ts
8907
- var KERNEL_VERSION = true ? "1.180.0" : "0.0.0-dev";
8911
+ var KERNEL_VERSION = true ? "1.181.0" : "0.0.0-dev";
8908
8912
  var OS_RELEASE = "6.0.0-slicc";
8909
8913
  function procVersion() {
8910
8914
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9769,27 +9773,27 @@ function hashId(text) {
9769
9773
  return hex4(a) + hex4(b);
9770
9774
  }
9771
9775
  function split2(text, count2) {
9772
- const words2 = [];
9776
+ const words3 = [];
9773
9777
  let rest2 = text;
9774
9778
  for (let i = 0; i < count2; i++) {
9775
9779
  const match = /^(\S+)\s+/.exec(rest2);
9776
9780
  if (!match) return void 0;
9777
- words2.push(match[1]);
9781
+ words3.push(match[1]);
9778
9782
  rest2 = rest2.slice(match[0].length);
9779
9783
  }
9780
- return [words2, rest2.trim()];
9784
+ return [words3, rest2.trim()];
9781
9785
  }
9782
9786
  function jobOf(content, pending, ctx, env) {
9783
9787
  const fields = content.startsWith("@") ? 1 : 5;
9784
9788
  const parts2 = split2(content, fields + (ctx.system ? 1 : 0));
9785
9789
  if (!parts2 || parts2[1] === "") throw new Error("no command");
9786
- const [words2, command] = parts2;
9787
- const schedule = words2.slice(0, fields).join(" ");
9790
+ const [words3, command] = parts2;
9791
+ const schedule = words3.slice(0, fields).join(" ");
9788
9792
  const when = parseWhen(schedule);
9789
9793
  const zone = env.CRON_TZ;
9790
9794
  if (zone && !when.reboot && canonicalZone(zone) === void 0)
9791
9795
  throw new Error(`CRON_TZ: unknown time zone "${zone}"`);
9792
- const user = ctx.system ? words2[fields] : void 0;
9796
+ const user = ctx.system ? words3[fields] : void 0;
9793
9797
  if (user !== void 0 && !USER_NAME.test(user)) throw new Error(`bad user name ${user}`);
9794
9798
  if (pending !== void 0 && !JOB_NAME.test(pending))
9795
9799
  throw new Error(`bad job name "${pending}"`);
@@ -12770,9 +12774,11 @@ var SpawnError = class extends Error {
12770
12774
  };
12771
12775
  var ChangedError = class extends SpawnError {
12772
12776
  notice;
12773
- constructor(notice) {
12777
+ file;
12778
+ constructor(notice, file) {
12774
12779
  super("EPERM");
12775
12780
  this.notice = notice;
12781
+ this.file = file;
12776
12782
  }
12777
12783
  };
12778
12784
  function interrupted(signal) {
@@ -19818,21 +19824,21 @@ function unescaped(double) {
19818
19824
  }
19819
19825
  function splitWords(text) {
19820
19826
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
19821
- const words2 = [];
19827
+ const words3 = [];
19822
19828
  let word;
19823
19829
  while (piece.lastIndex < text.length) {
19824
19830
  const m = piece.exec(text);
19825
19831
  if (!m) return void 0;
19826
19832
  const [, single, double, escaped, plain3, space] = m;
19827
19833
  if (space !== void 0) {
19828
- if (word !== void 0) words2.push(word);
19834
+ if (word !== void 0) words3.push(word);
19829
19835
  word = void 0;
19830
19836
  continue;
19831
19837
  }
19832
19838
  word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain3));
19833
19839
  }
19834
- if (word !== void 0) words2.push(word);
19835
- return words2;
19840
+ if (word !== void 0) words3.push(word);
19841
+ return words3;
19836
19842
  }
19837
19843
  function quoteWord(word) {
19838
19844
  const text = hasControl(word) ? withoutControl(word) : word;
@@ -19853,14 +19859,14 @@ function addPlain(word, plain3) {
19853
19859
  }
19854
19860
  function splitPatterns(text) {
19855
19861
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
19856
- const words2 = [];
19862
+ const words3 = [];
19857
19863
  let word;
19858
19864
  while (piece.lastIndex < text.length) {
19859
19865
  const m = piece.exec(text);
19860
19866
  if (!m) return void 0;
19861
19867
  const [, single, double, escaped, plain3, space] = m;
19862
19868
  if (space !== void 0) {
19863
- if (word !== void 0) words2.push(word);
19869
+ if (word !== void 0) words3.push(word);
19864
19870
  word = void 0;
19865
19871
  continue;
19866
19872
  }
@@ -19868,8 +19874,8 @@ function splitPatterns(text) {
19868
19874
  if (plain3 !== void 0) addPlain(word, plain3);
19869
19875
  else addPart(word, single ?? (double !== void 0 ? unescaped(double) : escaped));
19870
19876
  }
19871
- if (word !== void 0) words2.push(word);
19872
- return words2;
19877
+ if (word !== void 0) words3.push(word);
19878
+ return words3;
19873
19879
  }
19874
19880
  var literal = (pattern) => pattern.map((p) => p ?? "*").join("");
19875
19881
  var isAny = (pattern) => pattern?.length === 1 && pattern[0] === null;
@@ -19942,14 +19948,14 @@ function whoError(who) {
19942
19948
  return `expected a user name, %group or ALL first, not ${shown(who)}`;
19943
19949
  }
19944
19950
  function cwdOf(word) {
19945
- const words2 = splitWords(word);
19946
- const path = words2?.length === 1 ? words2[0] : "";
19951
+ const words3 = splitWords(word);
19952
+ const path = words3?.length === 1 ? words3[0] : "";
19947
19953
  return path.startsWith("/") ? normalizePath(path) : "";
19948
19954
  }
19949
19955
  function commandOf2(text, after) {
19950
- const words2 = splitPatterns(text);
19951
- if (words2 === void 0) return "a quote in the command is not closed, or it ends in a \\";
19952
- const [first, ...args] = words2;
19956
+ const words3 = splitPatterns(text);
19957
+ if (words3 === void 0) return "a quote in the command is not closed, or it ends in a \\";
19958
+ const [first, ...args] = words3;
19953
19959
  const path = first ? literal(first) : "";
19954
19960
  if (path === "") return `expected a command after ${after}`;
19955
19961
  if (!path.startsWith("/")) return `expected an absolute command path, not ${shown(path)}`;
@@ -20262,6 +20268,9 @@ function recordable(cwd, argv) {
20262
20268
  function changedText(file) {
20263
20269
  return `sudo: ${file} changed since it was approved`;
20264
20270
  }
20271
+ function requestedText(file) {
20272
+ return `sudo: ${file} changed since it was requested`;
20273
+ }
20265
20274
  function changedFile(approved, now) {
20266
20275
  const files = /* @__PURE__ */ new Set([...Object.keys(approved), ...Object.keys(now)]);
20267
20276
  return [...files].find((file) => approved[file] !== now[file]);
@@ -20269,6 +20278,67 @@ function changedFile(approved, now) {
20269
20278
  function pinned(rule, bound3) {
20270
20279
  return rule.sha256 === void 0 || changedFile(rule.sha256, bound3) === void 0;
20271
20280
  }
20281
+ var REPLAY_ID = /^[!-~]{1,256}$/;
20282
+ var REPLAY_TAGS_MAX = 64;
20283
+ var REPLAY_ARGV_MAX = 4096;
20284
+ var REPLAY_BYTES_MAX = 1024 * 1024;
20285
+ var NOT_A_REQUEST = "sudo: that isn't a sudo request this kernel can run";
20286
+ function strings(value2, max) {
20287
+ return Array.isArray(value2) && value2.length <= max && value2.every((s) => typeof s === "string");
20288
+ }
20289
+ function words(value2) {
20290
+ if (!strings(value2, REPLAY_ARGV_MAX) || value2.length === 0) return false;
20291
+ const bytes = value2.reduce((n, s) => n + s.length + 1, 0);
20292
+ return bytes <= REPLAY_BYTES_MAX && !value2.some((s) => s.includes("\0"));
20293
+ }
20294
+ function filesOf(value2) {
20295
+ if (value2 === void 0) return {};
20296
+ if (!Array.isArray(value2) || value2.length > OPERANDS_MAX) return void 0;
20297
+ const files = {};
20298
+ for (const file of value2) {
20299
+ const { path, sha256: hex4 } = file ?? {};
20300
+ if (typeof path !== "string" || typeof hex4 !== "string") return void 0;
20301
+ files[path] = hex4;
20302
+ }
20303
+ return files;
20304
+ }
20305
+ function flagsOf(value2) {
20306
+ if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) return void 0;
20307
+ const entries = Object.entries(value2);
20308
+ if (entries.length > ROOT_ONLY_MAX || entries.some(([, v]) => typeof v !== "boolean")) {
20309
+ return void 0;
20310
+ }
20311
+ return Object.fromEntries(entries);
20312
+ }
20313
+ function replayOf(value2) {
20314
+ if (typeof value2 !== "object" || value2 === null) return failed2("EINVAL", NOT_A_REQUEST);
20315
+ const r = value2;
20316
+ if (r.validate === true) return failed2("EINVAL", "sudo: a sudo -v request runs no command");
20317
+ if (r.stdin === "pipe" || r.stdin === "file") {
20318
+ return failed2("EINVAL", `sudo: the request read a ${r.stdin}, and that input is gone`);
20319
+ }
20320
+ const { id: id2, user, uid, target, argv, path, cwd, stdin, tag, tags } = r;
20321
+ const rootOnly2 = flagsOf(r.rootOnly);
20322
+ const bound3 = filesOf(r.userWritable);
20323
+ const named = (s) => typeof s === "string" && s !== "";
20324
+ const absolute2 = (s) => typeof s === "string" && s.startsWith("/") && s.length <= PATH_MAX && !s.includes("\0");
20325
+ const valid = typeof id2 === "string" && REPLAY_ID.test(id2) && named(user) && named(target) && Number.isInteger(uid) && uid >= 0 && words(argv) && absolute2(path) && absolute2(cwd) && (stdin === "tty" || stdin === "none") && (tag === void 0 || typeof tag === "string") && (tags === void 0 || strings(tags, REPLAY_TAGS_MAX)) && rootOnly2 !== void 0 && bound3 !== void 0;
20326
+ if (!valid) return failed2("EINVAL", NOT_A_REQUEST);
20327
+ const all = tags ?? (tag === void 0 ? [] : [tag]);
20328
+ return {
20329
+ id: id2,
20330
+ user,
20331
+ uid,
20332
+ target,
20333
+ argv,
20334
+ path,
20335
+ cwd,
20336
+ stdin,
20337
+ rootOnly: rootOnly2,
20338
+ bound: bound3,
20339
+ tags: [...new Set(all)]
20340
+ };
20341
+ }
20272
20342
  var VISUDO_FILES_MAX = 64;
20273
20343
  var VISUDO_TEXT_MAX = 1024 * 1024;
20274
20344
  function visudoCall(req) {
@@ -20670,6 +20740,61 @@ var Sudo = class {
20670
20740
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
20671
20741
  return this.command(session, root, mine, call);
20672
20742
  }
20743
+ async replay(value2) {
20744
+ const r = replayOf(value2);
20745
+ if ("error" in r) return r;
20746
+ if (this.o.mode !== "enforce") {
20747
+ return failed2("EINVAL", `sudo: users are '${this.o.mode}', so no request needed approval`);
20748
+ }
20749
+ const self = await this.o.users.lookup(r.user);
20750
+ if (!self) return failed2("ENOENT", `sudo: unknown user ${r.user}`);
20751
+ if (self.uid !== r.uid) return failed2("EPERM", `sudo: ${self.name} is no longer uid ${r.uid}`);
20752
+ const target = await this.lookup(r.target);
20753
+ if (!target) return failed2("ENOENT", `sudo: unknown user ${r.target}`);
20754
+ const cred = credOf(self);
20755
+ const [word = "", ...args] = r.argv;
20756
+ const argv = [r.path, ...args];
20757
+ if (!await this.mayAsk(self, target, r, argv)) {
20758
+ const quoted = argv.map(quoteWord).join(" ");
20759
+ return failed2(
20760
+ "EACCES",
20761
+ `sudo: ${self.name} may no longer ask to run ${quoted} as ${target.name}`
20762
+ );
20763
+ }
20764
+ if (await this.canonical(r.cwd) !== r.cwd) {
20765
+ return failed2("EACCES", `sudo: ${r.cwd} changed since it was requested`);
20766
+ }
20767
+ if (!await this.reachable(cred, r.cwd)) {
20768
+ return failed2(
20769
+ "EACCES",
20770
+ `sudo: cannot run in ${r.cwd}: it isn't a directory ${self.name} can enter`
20771
+ );
20772
+ }
20773
+ const path = await this.locate(word, r.cwd);
20774
+ if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
20775
+ if (path !== r.path) return failed2("EPERM", `sudo: ${word} now runs ${path}, not ${r.path}`);
20776
+ const bound3 = await this.bound(path, r.cwd);
20777
+ if (!bound3) return failed2("ENOENT", `sudo: ${word}: command not found`);
20778
+ const changed = changedFile(r.bound, bound3);
20779
+ if (changed !== void 0) return failed2("EPERM", requestedText(changed));
20780
+ const now = await this.rootOnly(r.cwd, args);
20781
+ const flipped = Object.keys(r.rootOnly).find((at) => r.rootOnly[at] && now[at] !== true);
20782
+ if (flipped !== void 0) return failed2("EPERM", `sudo: ${flipped} is no longer root-only`);
20783
+ const { grant } = this.grant(self, target, cred, r.argv, path, r.cwd, void 0, bound3);
20784
+ (this.o.warn ?? console.warn)(
20785
+ `sudo: ${self.name} : TTY=unknown ; PWD=${quoteWord(r.cwd)} ; USER=${target.name} ; REPLAY=${r.id} ; COMMAND=${argv.map(quoteWord).join(" ")}`
20786
+ );
20787
+ return { grant, caller: self, target, tags: r.tags };
20788
+ }
20789
+ async mayAsk(self, target, r, argv) {
20790
+ if (self.uid === 0) return true;
20791
+ const cred = credOf(self);
20792
+ const { rules } = await this.policyRules({ cred, pid: 0, stdin: r.stdin });
20793
+ const applies = await this.applies(self, cred);
20794
+ return rules.some(
20795
+ (rule) => applies(rule.who) && (rule.target === "ALL" || rule.target === target.name) && (rule.cwd === void 0 || rule.cwd === r.cwd) && stdinMatches(rule, r.stdin) && commandMatches(rule.cmnd, argv) && pinned(rule, r.bound)
20796
+ );
20797
+ }
20673
20798
  async command(s, root, mine, call) {
20674
20799
  const { caller, self, target, parsed: parsed2 } = s;
20675
20800
  const { cred } = caller;
@@ -20849,8 +20974,8 @@ ${lines2}
20849
20974
  const st = await fs.lstat(path).catch(() => void 0);
20850
20975
  if (st && !await this.trusted(path)) return false;
20851
20976
  const old = st ? await fs.readFile(path) : "";
20852
- const words2 = recordedCommand(argv);
20853
- const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words2}
20977
+ const words3 = recordedCommand(argv);
20978
+ const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words3}
20854
20979
  `;
20855
20980
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
20856
20981
  ${line}`;
@@ -22155,8 +22280,8 @@ function ipv6(host) {
22155
22280
  const head = groups(halves[0]);
22156
22281
  const back = halves.length === 2 ? groups(halves[1]) : [];
22157
22282
  if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
22158
- const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
22159
- const missing6 = 8 - words2.length - tail2.length;
22283
+ const words3 = [...head, ...back].map((g) => Number.parseInt(g, 16));
22284
+ const missing6 = 8 - words3.length - tail2.length;
22160
22285
  if (halves.length === 2 ? missing6 < 1 : missing6 !== 0) return void 0;
22161
22286
  const all = [
22162
22287
  ...head.map((g) => Number.parseInt(g, 16)),
@@ -25797,7 +25922,7 @@ function selectFlag(req) {
25797
25922
  throw new KernelError("EINVAL", "ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS: alpn with select");
25798
25923
  return TLS_SERVER_SELECT;
25799
25924
  }
25800
- function flagsOf(req) {
25925
+ function flagsOf2(req) {
25801
25926
  return certFlags(req) | selectFlag(req) | (req.keylog ? TLS_KEYLOG : 0);
25802
25927
  }
25803
25928
  async function engineOf(proc, { select, keylog }) {
@@ -25848,7 +25973,7 @@ async function serverOf(proc, req) {
25848
25973
  let mine;
25849
25974
  try {
25850
25975
  mine = req.ca && req.ca.length > 0 ? storeOf(engine, req.ca) : void 0;
25851
- const flags2 = flagsOf(req);
25976
+ const flags2 = flagsOf2(req);
25852
25977
  const store = mine ?? (flags2 & CERT_FLAGS ? await defaultStore(engine, supportOf(proc.net).roots) : void 0);
25853
25978
  return {
25854
25979
  engine,
@@ -27737,7 +27862,7 @@ var WRITE_ACCESS = 3;
27737
27862
  var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
27738
27863
  var STUCK_ESCAPES = /* @__PURE__ */ new Set([SIG.INT, SIG.TERM, SIG.QUIT, SIG.HUP]);
27739
27864
  var DEFAULT_UMASK2 = 18;
27740
- function words(value2) {
27865
+ function words2(value2) {
27741
27866
  if (!Array.isArray(value2) || !value2.every((w) => typeof w === "string")) {
27742
27867
  throw new KernelError("EINVAL");
27743
27868
  }
@@ -29190,7 +29315,7 @@ var WasmProcess = class {
29190
29315
  if (!sudo) throw new KernelError("ENOSYS");
29191
29316
  const call = {
29192
29317
  op: "proc-sudo",
29193
- args: words(req.args),
29318
+ args: words2(req.args),
29194
29319
  cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
29195
29320
  ...typeof req.term === "string" ? { term: req.term } : {}
29196
29321
  };
@@ -33800,7 +33925,40 @@ function describe(req, tty) {
33800
33925
  }
33801
33926
  function refuseChanged(approved, now) {
33802
33927
  const changed = changedFile(approved, now);
33803
- if (changed !== void 0) throw new ChangedError(changedText(changed));
33928
+ if (changed !== void 0) throw new ChangedError(changedText(changed), changed);
33929
+ }
33930
+ var O_WRONLY2 = 1;
33931
+ var OUTPUT_MODE = 384;
33932
+ var ERRNO_TEXT = {
33933
+ EACCES: "Permission denied",
33934
+ EISDIR: "Is a directory",
33935
+ ENOENT: "No such file or directory",
33936
+ ENOTDIR: "Not a directory",
33937
+ EROFS: "Read-only file system"
33938
+ };
33939
+ function sudoFailure(errno3, message3) {
33940
+ return Object.assign(new Error(message3), { code: errno3 });
33941
+ }
33942
+ function sudoCause(err) {
33943
+ const code = codeOf8(err);
33944
+ if (typeof code !== "string") throw err;
33945
+ return code;
33946
+ }
33947
+ function requested(err) {
33948
+ if (err instanceof ChangedError) throw sudoFailure(err.code, requestedText(err.file));
33949
+ throw err;
33950
+ }
33951
+ var MAX_SPAWN_TAGS = 8;
33952
+ function tagList(value2) {
33953
+ if (!Array.isArray(value2)) {
33954
+ tagOption(value2);
33955
+ return value2 === void 0 ? [] : [value2];
33956
+ }
33957
+ if (value2.length > MAX_SPAWN_TAGS) {
33958
+ throw new TypeError(`a spawn adds at most ${MAX_SPAWN_TAGS} tags`);
33959
+ }
33960
+ for (const each of value2) tagOption(each ?? 0);
33961
+ return value2;
33804
33962
  }
33805
33963
  function linksOf2(req) {
33806
33964
  return req.links === void 0 ? {} : { links: req.links };
@@ -33926,27 +34084,27 @@ function appendedRoots(given) {
33926
34084
  }
33927
34085
  return [...roots];
33928
34086
  }
33929
- function plainEnv(words2) {
33930
- const next3 = words2[1];
33931
- if (baseName2(words2[0] ?? "") !== "env" || next3 === void 0) return false;
34087
+ function plainEnv(words3) {
34088
+ const next3 = words3[1];
34089
+ if (baseName2(words3[0] ?? "") !== "env" || next3 === void 0) return false;
33932
34090
  return next3 === "-S" || !next3.startsWith("-") && !next3.includes("=");
33933
34091
  }
33934
- function shebangCommand(words2) {
33935
- if (!plainEnv(words2)) {
33936
- const arg2 = words2.slice(1).join(" ");
33937
- return { interp: words2[0], args: arg2 ? [arg2] : [] };
34092
+ function shebangCommand(words3) {
34093
+ if (!plainEnv(words3)) {
34094
+ const arg2 = words3.slice(1).join(" ");
34095
+ return { interp: words3[0], args: arg2 ? [arg2] : [] };
33938
34096
  }
33939
- const split3 = words2[1] === "-S";
33940
- const [interp, ...rest2] = words2.slice(split3 ? 2 : 1);
34097
+ const split3 = words3[1] === "-S";
34098
+ const [interp, ...rest2] = words3.slice(split3 ? 2 : 1);
33941
34099
  const arg = rest2.join(" ");
33942
34100
  return { interp, args: split3 ? rest2 : arg ? [arg] : [] };
33943
34101
  }
33944
34102
  function interpreter(head) {
33945
- const words2 = shebang(head);
33946
- if (!words2) return "node";
33947
- if (baseName2(words2[0]) === "env") words2.shift();
33948
- if (words2[0] === "-S") words2.shift();
33949
- return baseName2(words2[0] ?? "");
34103
+ const words3 = shebang(head);
34104
+ if (!words3) return "node";
34105
+ if (baseName2(words3[0]) === "env") words3.shift();
34106
+ if (words3[0] === "-S") words3.shift();
34107
+ return baseName2(words3[0] ?? "");
33950
34108
  }
33951
34109
  function expandDefaults(defaults2, env) {
33952
34110
  const out = {};
@@ -34721,9 +34879,9 @@ var Launcher = class {
34721
34879
  const head = await this.head(script);
34722
34880
  if (!head) return void 0;
34723
34881
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
34724
- const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
34725
- if (!words2) return void 0;
34726
- const { interp, args } = shebangCommand(words2);
34882
+ const words3 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
34883
+ if (!words3) return void 0;
34884
+ const { interp, args } = shebangCommand(words3);
34727
34885
  const passed = [...args, command ? script : file, ...argv.slice(1)];
34728
34886
  if (!interp) return void 0;
34729
34887
  const found = await this.resolve(interp, interp, cwd, scope);
@@ -34954,7 +35112,7 @@ var Launcher = class {
34954
35112
  const hex4 = pinned2[real];
34955
35113
  if (hex4 !== void 0) return { [real]: hex4 };
34956
35114
  if (await this.sudoer.trusted(real)) return {};
34957
- throw new ChangedError(changedText(real));
35115
+ throw new ChangedError(changedText(real), real);
34958
35116
  }
34959
35117
  async pinnedBytes(pin, bytes) {
34960
35118
  const [real = ""] = Object.keys(pin);
@@ -35233,6 +35391,10 @@ var Launcher = class {
35233
35391
  }
35234
35392
  async launch(asked, req) {
35235
35393
  const cwd = await physicalPathAsync(this.base, req.cwd);
35394
+ if (req.exactCwd && cwd !== req.cwd) {
35395
+ await req.fds.closeAll();
35396
+ throw sudoFailure("EACCES", `sudo: ${req.cwd} changed since it was requested`);
35397
+ }
35236
35398
  const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
35237
35399
  await this.pnpmReady();
35238
35400
  let loaded;
@@ -36928,6 +37090,96 @@ var Launcher = class {
36928
37090
  const signal = handle.termsig();
36929
37091
  return { status, ...signal ? { signal } : {}, stdout: concat7(out), stderr: concat7(err) };
36930
37092
  }
37093
+ async sudoRun(replay, options = {}) {
37094
+ const added = tagList(options.tag);
37095
+ const replayed = await this.sudoer.replay(replay);
37096
+ if ("error" in replayed) throw sudoFailure(replayed.errno, replayed.error);
37097
+ const { grant, caller, target } = replayed;
37098
+ let tags = withTag(void 0, options.base);
37099
+ for (const tag of [...replayed.tags, ...added]) tags = withTag(tags, tag);
37100
+ const tagged = { tags, tagBase: options.base === void 0 ? 0 : 1 };
37101
+ await this.swaps.settled();
37102
+ this.resetCatalogs();
37103
+ const req = { file: grant.file, argv: grant.argv, env: grant.env, cwd: grant.cwd };
37104
+ const ctx = { ppid: 0, cred: grant.cred, scope: "" };
37105
+ const planned = await this.planOne(req, ctx).catch((err2) => {
37106
+ const code = sudoCause(err2);
37107
+ const word = grant.argv[0] ?? grant.file;
37108
+ if (code === "ENOENT") throw sudoFailure(code, `sudo: ${word}: command not found`);
37109
+ throw sudoFailure(code, `sudo: ${grant.file}: ${ERRNO_TEXT[code] ?? code}`);
37110
+ });
37111
+ const pin = await this.unchanged(planned, grant.file, grant.cwd, grant).catch(requested);
37112
+ const fds = await this.sudoOutputs(options, caller, this.stackFor(tags, tagged.tagBase));
37113
+ const err = fds.get(2).file;
37114
+ const report = (message3) => void Promise.resolve(err.write?.(encoder10.encode(`${message3}
37115
+ `))).catch(() => void 0);
37116
+ const replan = () => this.planOne(req, ctx);
37117
+ if (!await this.grantedCwd(grant.caller, grant.cwd)) {
37118
+ await fds.closeAll();
37119
+ throw sudoFailure("EACCES", `sudo: ${grant.cwd} changed since it was requested`);
37120
+ }
37121
+ const handle = await this.launch(
37122
+ { ...planned, replan },
37123
+ {
37124
+ ...pin,
37125
+ env: grant.env,
37126
+ cwd: grant.cwd,
37127
+ fds,
37128
+ report,
37129
+ ...this.identity(target, tagged, options.session),
37130
+ ...caller.umask !== void 0 ? { umask: caller.umask } : {},
37131
+ links: grant.cred.euid,
37132
+ exactCwd: true
37133
+ }
37134
+ ).catch(requested);
37135
+ const ended = handle.exited.then((status) => {
37136
+ const signal = handle.termsig();
37137
+ return { status, ...signal ? { signal } : {} };
37138
+ });
37139
+ return { pid: handle.pid, ended };
37140
+ }
37141
+ async sudoOutputs(options, caller, stack) {
37142
+ const fds = new FdTable();
37143
+ const opened2 = /* @__PURE__ */ new Map();
37144
+ fds.installAt(0, deviceFile("null"));
37145
+ try {
37146
+ fds.installAt(1, await this.sudoOutput(options.stdout, caller, stack, opened2));
37147
+ fds.installAt(2, await this.sudoOutput(options.stderr, caller, stack, opened2));
37148
+ } catch (err) {
37149
+ await fds.closeAll();
37150
+ throw err;
37151
+ }
37152
+ return fds;
37153
+ }
37154
+ async sudoOutput(path, caller, stack, opened2) {
37155
+ if (path === void 0) return deviceFile("null");
37156
+ if (typeof path !== "string" || !path.startsWith("/")) {
37157
+ throw sudoFailure("EINVAL", `sudo: ${String(path)} is not an absolute path`);
37158
+ }
37159
+ const cred = credOf(caller);
37160
+ const walked = await walk(this.fs, cred, normalizePath(path), true, caller.uid);
37161
+ const real = walked.path;
37162
+ const same3 = opened2.get(real);
37163
+ if (same3) return same3.retain();
37164
+ try {
37165
+ if (walked.denied !== void 0) throw fsError("EACCES", path);
37166
+ await writable({ ...this.execGuard(cred, 0), links: caller.uid }, "open", real);
37167
+ const owner = { uid: caller.uid, gid: caller.gid, mode: OUTPUT_MODE };
37168
+ await stack.noLinks.writeFile(real, new Uint8Array(0), owner);
37169
+ } catch (err) {
37170
+ const code = sudoCause(err);
37171
+ throw sudoFailure(code, `sudo: ${path}: ${ERRNO_TEXT[code] ?? code}`);
37172
+ }
37173
+ const file = vfsFile(stack.fs, { path: real, flags: O_WRONLY2, position: 0 }, stack.nodes);
37174
+ const again = await walk(this.fs, cred, real, true, caller.uid);
37175
+ const st = await stack.noLinks.lstat(real).catch(() => void 0);
37176
+ if (again.path !== real || again.denied !== void 0 || !st?.isFile) {
37177
+ await file.release();
37178
+ throw sudoFailure("EACCES", `sudo: ${path} changed while it was opened`);
37179
+ }
37180
+ opened2.set(real, file);
37181
+ return file;
37182
+ }
36931
37183
  };
36932
37184
  function cronLocks() {
36933
37185
  return globalThis.navigator?.locks;
@@ -37393,11 +37645,38 @@ function policyAsker(port) {
37393
37645
  }
37394
37646
  };
37395
37647
  }
37648
+ function processOps(ready2, reply) {
37649
+ const terminals = /* @__PURE__ */ new Map();
37650
+ async function terminal(req) {
37651
+ const onData = (bytes) => reply(req.id, { fd: 1, bytes });
37652
+ const session = await (await ready2()).openTerminal(req.argv, { ...req.options, onData });
37653
+ terminals.set(req.id, session);
37654
+ reply(req.id, { started: session.pid });
37655
+ try {
37656
+ const status = await session.exited;
37657
+ const signal = session.termsig();
37658
+ return signal ? { status, signal } : status;
37659
+ } finally {
37660
+ terminals.delete(req.id);
37661
+ }
37662
+ }
37663
+ async function sudoRun(req) {
37664
+ const running = await (await ready2()).sudoRun(req.replay, req.options);
37665
+ reply(req.id, { started: running.pid });
37666
+ return running.ended;
37667
+ }
37668
+ function onTerminal(req) {
37669
+ const session = terminals.get(req.terminal);
37670
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
37671
+ act2(session, req);
37672
+ return true;
37673
+ }
37674
+ return { terminal, sudoRun, act: onTerminal };
37675
+ }
37396
37676
  function serveKernel(port, deps) {
37397
37677
  let launcher;
37398
37678
  let remote;
37399
37679
  let uplink;
37400
- const terminals = /* @__PURE__ */ new Map();
37401
37680
  const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
37402
37681
  const clients = /* @__PURE__ */ new Set();
37403
37682
  const sealed = sealedAsker(port);
@@ -37426,19 +37705,7 @@ function serveKernel(port, deps) {
37426
37705
  if (!launcher) throw new Error("the kernel is not initialized");
37427
37706
  return launcher;
37428
37707
  }
37429
- async function terminal(req) {
37430
- const onData = (bytes) => reply(req.id, { fd: 1, bytes });
37431
- const session = await (await ready2()).openTerminal(req.argv, { ...req.options, onData });
37432
- terminals.set(req.id, session);
37433
- reply(req.id, { started: session.pid });
37434
- try {
37435
- const status = await session.exited;
37436
- const signal = session.termsig();
37437
- return signal ? { status, signal } : status;
37438
- } finally {
37439
- terminals.delete(req.id);
37440
- }
37441
- }
37708
+ const processes2 = processOps(ready2, reply);
37442
37709
  async function init(req) {
37443
37710
  launcher = rootOf(deps, req).then(async (root) => {
37444
37711
  req = await rootNamed(deps, req, root);
@@ -37493,7 +37760,8 @@ function serveKernel(port, deps) {
37493
37760
  if (req.op === "init") return init(req);
37494
37761
  if (isAttestRequest(req)) return attestOp(await ready2(), req);
37495
37762
  if (req.op === "sealed-placeholders") return (await ready2()).setPlaceholders(req.list);
37496
- if (req.op === "open-terminal") return terminal(req);
37763
+ if (req.op === "open-terminal") return processes2.terminal(req);
37764
+ if (req.op === "sudo-run") return processes2.sudoRun(req);
37497
37765
  if (req.op === "connect") return connect2(req.user, req.tag, req.requests);
37498
37766
  if (isUsersRequest(req)) return usersOp(await ready2(), req);
37499
37767
  if (req.op === "routes" || req.op === "transport-traits") {
@@ -37502,12 +37770,7 @@ function serveKernel(port, deps) {
37502
37770
  if (req.op === "dial") return dial(await ready2(), req);
37503
37771
  if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
37504
37772
  return mountOp(req);
37505
- if (req.op === "terminal") {
37506
- const session = terminals.get(req.terminal);
37507
- if (!session) throw new Error(`no terminal ${req.terminal}`);
37508
- act2(session, req);
37509
- return true;
37510
- }
37773
+ if (req.op === "terminal") return processes2.act(req);
37511
37774
  return (await ready2()).run(req.argv, {
37512
37775
  ...req.options,
37513
37776
  onStdout: (bytes) => reply(req.id, { fd: 1, bytes }),