@ai-ecoverse/slicc-kernel 1.180.0 → 1.182.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
@@ -4825,10 +4825,10 @@ var CdpFacade = class {
4825
4825
  );
4826
4826
  return;
4827
4827
  }
4828
- await this.write(conn, json(await this.list(runtime)));
4828
+ await this.write(conn, json(await this.list(runtime, conn.peerStamp)));
4829
4829
  }
4830
- async list(runtime) {
4831
- const conn = await this.host(runtime);
4830
+ async list(runtime, stamp) {
4831
+ const conn = await this.host(runtime, stamp);
4832
4832
  try {
4833
4833
  const reply = await new Promise(
4834
4834
  (resolve2, reject) => {
@@ -4863,12 +4863,15 @@ var CdpFacade = class {
4863
4863
  conn.close();
4864
4864
  }
4865
4865
  }
4866
- host(runtime) {
4867
- return this.options.hosts.connect(runtime ? { runtime } : {});
4866
+ host(runtime, stamp) {
4867
+ return this.options.hosts.connect({
4868
+ ...runtime ? { runtime } : {},
4869
+ ...stamp ? { stamp } : {}
4870
+ });
4868
4871
  }
4869
4872
  async upgrade(conn, incoming, req, runtime) {
4870
4873
  const accept2 = await acceptKey(req.headers);
4871
- const host = await this.host(runtime);
4874
+ const host = await this.host(runtime, conn.peerStamp);
4872
4875
  const limits = {
4873
4876
  max: this.options.maxMessage ?? MAX_MESSAGE,
4874
4877
  closeWaitMs: this.options.closeWaitMs ?? CLOSE_WAIT_MS
@@ -4948,6 +4951,7 @@ function enableCdp(net, hosts) {
4948
4951
  let facade;
4949
4952
  net.kernelOnly(CDP_PORT, true);
4950
4953
  net.reserve(CDP_PORT);
4954
+ net.stampPeers(CDP_PORT);
4951
4955
  net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
4952
4956
  const started = new CdpFacade({ net, hosts });
4953
4957
  facade = started;
@@ -4990,7 +4994,7 @@ var CdpHosts = class {
4990
4994
  if (!entry) throw new HttpError(503, NO_CDP_HOST);
4991
4995
  let conn;
4992
4996
  try {
4993
- conn = await entry.hook(request3.runtime ? { runtime: request3.runtime } : {});
4997
+ conn = await entry.hook(hookRequest(request3));
4994
4998
  } catch (e) {
4995
4999
  throw new HttpError(502, `CDP host: ${e instanceof Error ? e.message : String(e)}`);
4996
5000
  }
@@ -5010,6 +5014,12 @@ var CdpHosts = class {
5010
5014
  for (const entry of [this.embedder, ...this.clients]) if (entry) closeAll(entry);
5011
5015
  }
5012
5016
  };
5017
+ function hookRequest({ runtime, stamp }) {
5018
+ return {
5019
+ ...runtime ? { runtime } : {},
5020
+ ...stamp ? { stamp: structuredClone(stamp) } : {}
5021
+ };
5022
+ }
5013
5023
  function closeAll(entry) {
5014
5024
  for (const conn of [...entry.open]) {
5015
5025
  conn.close();
@@ -7158,6 +7168,7 @@ var LoopbackNet = class {
7158
7168
  reserved = /* @__PURE__ */ new Set();
7159
7169
  facades = /* @__PURE__ */ new Map();
7160
7170
  proxyStamped;
7171
+ stamped = /* @__PURE__ */ new Set();
7161
7172
  nextEphemeral = EPHEMERAL_FIRST;
7162
7173
  route;
7163
7174
  origins;
@@ -7232,6 +7243,9 @@ var LoopbackNet = class {
7232
7243
  this.kernelOnly(port, true);
7233
7244
  this.facades.set(port, name2);
7234
7245
  }
7246
+ stampPeers(port) {
7247
+ this.stamped.add(port);
7248
+ }
7235
7249
  stampProxy(port) {
7236
7250
  this.proxyStamped = port;
7237
7251
  }
@@ -7240,7 +7254,7 @@ var LoopbackNet = class {
7240
7254
  }
7241
7255
  stampsPeers(addr) {
7242
7256
  if (addr.family !== "inet" || !isLoopback(canonicalHost(addr.host))) return false;
7243
- return this.facades.has(addr.port) || this.proxyStamped === addr.port;
7257
+ return this.facades.has(addr.port) || this.stamped.has(addr.port) || this.proxyStamped === addr.port;
7244
7258
  }
7245
7259
  activate(addr, start2) {
7246
7260
  this.activators.set(keyOf(addr), start2);
@@ -8206,7 +8220,7 @@ var UserDb = class {
8206
8220
  };
8207
8221
 
8208
8222
  // src/client/protocol.ts
8209
- var PROTOCOL = [1, 15];
8223
+ var PROTOCOL = [1, 17];
8210
8224
  var FS_METHODS = [
8211
8225
  "readFile",
8212
8226
  "writeFile",
@@ -8362,9 +8376,9 @@ async function accountOf(l, user) {
8362
8376
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
8363
8377
  return found;
8364
8378
  }
8365
- async function runAs(l, self, requested) {
8366
- if (requested === void 0) return self;
8367
- const target = await accountOf(l, requested);
8379
+ async function runAs(l, self, requested2) {
8380
+ if (requested2 === void 0) return self;
8381
+ const target = await accountOf(l, requested2);
8368
8382
  if (self.uid !== 0 && target.uid !== self.uid) {
8369
8383
  throw fsError("EPERM", `${self.name} may not run as ${target.name}`);
8370
8384
  }
@@ -8373,21 +8387,9 @@ async function runAs(l, self, requested) {
8373
8387
  function clientCred(self) {
8374
8388
  return self ? credOf(self) : copyCred(ROOT);
8375
8389
  }
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
8390
  async function runTagged(l, self, options, tag, session = -1) {
8389
8391
  let tags = withTag(void 0, tag);
8390
- for (const added of spawnTags(options.tag)) tags = withTag(tags, added);
8392
+ for (const added of tagList(options.tag)) tags = withTag(tags, added);
8391
8393
  return {
8392
8394
  user: await runAs(l, self, options.user),
8393
8395
  tag,
@@ -8572,6 +8574,20 @@ function requestBinding(send2, port) {
8572
8574
  close: () => unbind?.()
8573
8575
  };
8574
8576
  }
8577
+ async function sudoRunAs(l, self, host, req, reply) {
8578
+ if (self.uid !== 0) throw sudoFailure("EPERM", "sudo: only a root client may run a sudo request");
8579
+ const { stdout, stderr, tag } = req.options;
8580
+ const running = await l.sudoRun(req.replay, {
8581
+ stdout,
8582
+ stderr,
8583
+ tag,
8584
+ base: host.tag,
8585
+ session: host.session
8586
+ });
8587
+ reply(req.id, { started: running.pid });
8588
+ const { status, signal } = await running.ended;
8589
+ return { result: status, ...signalField(signal) };
8590
+ }
8575
8591
  function answered(pending, req, reply, detach) {
8576
8592
  pending.then(
8577
8593
  ({ result, signal, transfer }) => {
@@ -8721,6 +8737,8 @@ function serveClient(port, host) {
8721
8737
  case "trace":
8722
8738
  case "untrace":
8723
8739
  return traces.answer(l, self, req);
8740
+ case "sudo-run":
8741
+ return sudoRunAs(l, self, host, req, reply);
8724
8742
  case "detach":
8725
8743
  return { result: true };
8726
8744
  default:
@@ -8904,7 +8922,7 @@ function satisfies(version, range2) {
8904
8922
  }
8905
8923
 
8906
8924
  // src/version.ts
8907
- var KERNEL_VERSION = true ? "1.180.0" : "0.0.0-dev";
8925
+ var KERNEL_VERSION = true ? "1.182.0" : "0.0.0-dev";
8908
8926
  var OS_RELEASE = "6.0.0-slicc";
8909
8927
  function procVersion() {
8910
8928
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9769,27 +9787,27 @@ function hashId(text) {
9769
9787
  return hex4(a) + hex4(b);
9770
9788
  }
9771
9789
  function split2(text, count2) {
9772
- const words2 = [];
9790
+ const words3 = [];
9773
9791
  let rest2 = text;
9774
9792
  for (let i = 0; i < count2; i++) {
9775
9793
  const match = /^(\S+)\s+/.exec(rest2);
9776
9794
  if (!match) return void 0;
9777
- words2.push(match[1]);
9795
+ words3.push(match[1]);
9778
9796
  rest2 = rest2.slice(match[0].length);
9779
9797
  }
9780
- return [words2, rest2.trim()];
9798
+ return [words3, rest2.trim()];
9781
9799
  }
9782
9800
  function jobOf(content, pending, ctx, env) {
9783
9801
  const fields = content.startsWith("@") ? 1 : 5;
9784
9802
  const parts2 = split2(content, fields + (ctx.system ? 1 : 0));
9785
9803
  if (!parts2 || parts2[1] === "") throw new Error("no command");
9786
- const [words2, command] = parts2;
9787
- const schedule = words2.slice(0, fields).join(" ");
9804
+ const [words3, command] = parts2;
9805
+ const schedule = words3.slice(0, fields).join(" ");
9788
9806
  const when = parseWhen(schedule);
9789
9807
  const zone = env.CRON_TZ;
9790
9808
  if (zone && !when.reboot && canonicalZone(zone) === void 0)
9791
9809
  throw new Error(`CRON_TZ: unknown time zone "${zone}"`);
9792
- const user = ctx.system ? words2[fields] : void 0;
9810
+ const user = ctx.system ? words3[fields] : void 0;
9793
9811
  if (user !== void 0 && !USER_NAME.test(user)) throw new Error(`bad user name ${user}`);
9794
9812
  if (pending !== void 0 && !JOB_NAME.test(pending))
9795
9813
  throw new Error(`bad job name "${pending}"`);
@@ -12770,9 +12788,11 @@ var SpawnError = class extends Error {
12770
12788
  };
12771
12789
  var ChangedError = class extends SpawnError {
12772
12790
  notice;
12773
- constructor(notice) {
12791
+ file;
12792
+ constructor(notice, file) {
12774
12793
  super("EPERM");
12775
12794
  this.notice = notice;
12795
+ this.file = file;
12776
12796
  }
12777
12797
  };
12778
12798
  function interrupted(signal) {
@@ -19818,21 +19838,21 @@ function unescaped(double) {
19818
19838
  }
19819
19839
  function splitWords(text) {
19820
19840
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
19821
- const words2 = [];
19841
+ const words3 = [];
19822
19842
  let word;
19823
19843
  while (piece.lastIndex < text.length) {
19824
19844
  const m = piece.exec(text);
19825
19845
  if (!m) return void 0;
19826
19846
  const [, single, double, escaped, plain3, space] = m;
19827
19847
  if (space !== void 0) {
19828
- if (word !== void 0) words2.push(word);
19848
+ if (word !== void 0) words3.push(word);
19829
19849
  word = void 0;
19830
19850
  continue;
19831
19851
  }
19832
19852
  word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain3));
19833
19853
  }
19834
- if (word !== void 0) words2.push(word);
19835
- return words2;
19854
+ if (word !== void 0) words3.push(word);
19855
+ return words3;
19836
19856
  }
19837
19857
  function quoteWord(word) {
19838
19858
  const text = hasControl(word) ? withoutControl(word) : word;
@@ -19853,14 +19873,14 @@ function addPlain(word, plain3) {
19853
19873
  }
19854
19874
  function splitPatterns(text) {
19855
19875
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
19856
- const words2 = [];
19876
+ const words3 = [];
19857
19877
  let word;
19858
19878
  while (piece.lastIndex < text.length) {
19859
19879
  const m = piece.exec(text);
19860
19880
  if (!m) return void 0;
19861
19881
  const [, single, double, escaped, plain3, space] = m;
19862
19882
  if (space !== void 0) {
19863
- if (word !== void 0) words2.push(word);
19883
+ if (word !== void 0) words3.push(word);
19864
19884
  word = void 0;
19865
19885
  continue;
19866
19886
  }
@@ -19868,8 +19888,8 @@ function splitPatterns(text) {
19868
19888
  if (plain3 !== void 0) addPlain(word, plain3);
19869
19889
  else addPart(word, single ?? (double !== void 0 ? unescaped(double) : escaped));
19870
19890
  }
19871
- if (word !== void 0) words2.push(word);
19872
- return words2;
19891
+ if (word !== void 0) words3.push(word);
19892
+ return words3;
19873
19893
  }
19874
19894
  var literal = (pattern) => pattern.map((p) => p ?? "*").join("");
19875
19895
  var isAny = (pattern) => pattern?.length === 1 && pattern[0] === null;
@@ -19942,14 +19962,14 @@ function whoError(who) {
19942
19962
  return `expected a user name, %group or ALL first, not ${shown(who)}`;
19943
19963
  }
19944
19964
  function cwdOf(word) {
19945
- const words2 = splitWords(word);
19946
- const path = words2?.length === 1 ? words2[0] : "";
19965
+ const words3 = splitWords(word);
19966
+ const path = words3?.length === 1 ? words3[0] : "";
19947
19967
  return path.startsWith("/") ? normalizePath(path) : "";
19948
19968
  }
19949
19969
  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;
19970
+ const words3 = splitPatterns(text);
19971
+ if (words3 === void 0) return "a quote in the command is not closed, or it ends in a \\";
19972
+ const [first, ...args] = words3;
19953
19973
  const path = first ? literal(first) : "";
19954
19974
  if (path === "") return `expected a command after ${after}`;
19955
19975
  if (!path.startsWith("/")) return `expected an absolute command path, not ${shown(path)}`;
@@ -20262,6 +20282,9 @@ function recordable(cwd, argv) {
20262
20282
  function changedText(file) {
20263
20283
  return `sudo: ${file} changed since it was approved`;
20264
20284
  }
20285
+ function requestedText(file) {
20286
+ return `sudo: ${file} changed since it was requested`;
20287
+ }
20265
20288
  function changedFile(approved, now) {
20266
20289
  const files = /* @__PURE__ */ new Set([...Object.keys(approved), ...Object.keys(now)]);
20267
20290
  return [...files].find((file) => approved[file] !== now[file]);
@@ -20269,6 +20292,67 @@ function changedFile(approved, now) {
20269
20292
  function pinned(rule, bound3) {
20270
20293
  return rule.sha256 === void 0 || changedFile(rule.sha256, bound3) === void 0;
20271
20294
  }
20295
+ var REPLAY_ID = /^[!-~]{1,256}$/;
20296
+ var REPLAY_TAGS_MAX = 64;
20297
+ var REPLAY_ARGV_MAX = 4096;
20298
+ var REPLAY_BYTES_MAX = 1024 * 1024;
20299
+ var NOT_A_REQUEST = "sudo: that isn't a sudo request this kernel can run";
20300
+ function strings(value2, max) {
20301
+ return Array.isArray(value2) && value2.length <= max && value2.every((s) => typeof s === "string");
20302
+ }
20303
+ function words(value2) {
20304
+ if (!strings(value2, REPLAY_ARGV_MAX) || value2.length === 0) return false;
20305
+ const bytes = value2.reduce((n, s) => n + s.length + 1, 0);
20306
+ return bytes <= REPLAY_BYTES_MAX && !value2.some((s) => s.includes("\0"));
20307
+ }
20308
+ function filesOf(value2) {
20309
+ if (value2 === void 0) return {};
20310
+ if (!Array.isArray(value2) || value2.length > OPERANDS_MAX) return void 0;
20311
+ const files = {};
20312
+ for (const file of value2) {
20313
+ const { path, sha256: hex4 } = file ?? {};
20314
+ if (typeof path !== "string" || typeof hex4 !== "string") return void 0;
20315
+ files[path] = hex4;
20316
+ }
20317
+ return files;
20318
+ }
20319
+ function flagsOf(value2) {
20320
+ if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) return void 0;
20321
+ const entries = Object.entries(value2);
20322
+ if (entries.length > ROOT_ONLY_MAX || entries.some(([, v]) => typeof v !== "boolean")) {
20323
+ return void 0;
20324
+ }
20325
+ return Object.fromEntries(entries);
20326
+ }
20327
+ function replayOf(value2) {
20328
+ if (typeof value2 !== "object" || value2 === null) return failed2("EINVAL", NOT_A_REQUEST);
20329
+ const r = value2;
20330
+ if (r.validate === true) return failed2("EINVAL", "sudo: a sudo -v request runs no command");
20331
+ if (r.stdin === "pipe" || r.stdin === "file") {
20332
+ return failed2("EINVAL", `sudo: the request read a ${r.stdin}, and that input is gone`);
20333
+ }
20334
+ const { id: id2, user, uid, target, argv, path, cwd, stdin, tag, tags } = r;
20335
+ const rootOnly2 = flagsOf(r.rootOnly);
20336
+ const bound3 = filesOf(r.userWritable);
20337
+ const named = (s) => typeof s === "string" && s !== "";
20338
+ const absolute2 = (s) => typeof s === "string" && s.startsWith("/") && s.length <= PATH_MAX && !s.includes("\0");
20339
+ 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;
20340
+ if (!valid) return failed2("EINVAL", NOT_A_REQUEST);
20341
+ const all = tags ?? (tag === void 0 ? [] : [tag]);
20342
+ return {
20343
+ id: id2,
20344
+ user,
20345
+ uid,
20346
+ target,
20347
+ argv,
20348
+ path,
20349
+ cwd,
20350
+ stdin,
20351
+ rootOnly: rootOnly2,
20352
+ bound: bound3,
20353
+ tags: [...new Set(all)]
20354
+ };
20355
+ }
20272
20356
  var VISUDO_FILES_MAX = 64;
20273
20357
  var VISUDO_TEXT_MAX = 1024 * 1024;
20274
20358
  function visudoCall(req) {
@@ -20670,6 +20754,61 @@ var Sudo = class {
20670
20754
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
20671
20755
  return this.command(session, root, mine, call);
20672
20756
  }
20757
+ async replay(value2) {
20758
+ const r = replayOf(value2);
20759
+ if ("error" in r) return r;
20760
+ if (this.o.mode !== "enforce") {
20761
+ return failed2("EINVAL", `sudo: users are '${this.o.mode}', so no request needed approval`);
20762
+ }
20763
+ const self = await this.o.users.lookup(r.user);
20764
+ if (!self) return failed2("ENOENT", `sudo: unknown user ${r.user}`);
20765
+ if (self.uid !== r.uid) return failed2("EPERM", `sudo: ${self.name} is no longer uid ${r.uid}`);
20766
+ const target = await this.lookup(r.target);
20767
+ if (!target) return failed2("ENOENT", `sudo: unknown user ${r.target}`);
20768
+ const cred = credOf(self);
20769
+ const [word = "", ...args] = r.argv;
20770
+ const argv = [r.path, ...args];
20771
+ if (!await this.mayAsk(self, target, r, argv)) {
20772
+ const quoted = argv.map(quoteWord).join(" ");
20773
+ return failed2(
20774
+ "EACCES",
20775
+ `sudo: ${self.name} may no longer ask to run ${quoted} as ${target.name}`
20776
+ );
20777
+ }
20778
+ if (await this.canonical(r.cwd) !== r.cwd) {
20779
+ return failed2("EACCES", `sudo: ${r.cwd} changed since it was requested`);
20780
+ }
20781
+ if (!await this.reachable(cred, r.cwd)) {
20782
+ return failed2(
20783
+ "EACCES",
20784
+ `sudo: cannot run in ${r.cwd}: it isn't a directory ${self.name} can enter`
20785
+ );
20786
+ }
20787
+ const path = await this.locate(word, r.cwd);
20788
+ if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
20789
+ if (path !== r.path) return failed2("EPERM", `sudo: ${word} now runs ${path}, not ${r.path}`);
20790
+ const bound3 = await this.bound(path, r.cwd);
20791
+ if (!bound3) return failed2("ENOENT", `sudo: ${word}: command not found`);
20792
+ const changed = changedFile(r.bound, bound3);
20793
+ if (changed !== void 0) return failed2("EPERM", requestedText(changed));
20794
+ const now = await this.rootOnly(r.cwd, args);
20795
+ const flipped = Object.keys(r.rootOnly).find((at) => r.rootOnly[at] && now[at] !== true);
20796
+ if (flipped !== void 0) return failed2("EPERM", `sudo: ${flipped} is no longer root-only`);
20797
+ const { grant } = this.grant(self, target, cred, r.argv, path, r.cwd, void 0, bound3);
20798
+ (this.o.warn ?? console.warn)(
20799
+ `sudo: ${self.name} : TTY=unknown ; PWD=${quoteWord(r.cwd)} ; USER=${target.name} ; REPLAY=${r.id} ; COMMAND=${argv.map(quoteWord).join(" ")}`
20800
+ );
20801
+ return { grant, caller: self, target, tags: r.tags };
20802
+ }
20803
+ async mayAsk(self, target, r, argv) {
20804
+ if (self.uid === 0) return true;
20805
+ const cred = credOf(self);
20806
+ const { rules } = await this.policyRules({ cred, pid: 0, stdin: r.stdin });
20807
+ const applies = await this.applies(self, cred);
20808
+ return rules.some(
20809
+ (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)
20810
+ );
20811
+ }
20673
20812
  async command(s, root, mine, call) {
20674
20813
  const { caller, self, target, parsed: parsed2 } = s;
20675
20814
  const { cred } = caller;
@@ -20849,8 +20988,8 @@ ${lines2}
20849
20988
  const st = await fs.lstat(path).catch(() => void 0);
20850
20989
  if (st && !await this.trusted(path)) return false;
20851
20990
  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}
20991
+ const words3 = recordedCommand(argv);
20992
+ const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words3}
20854
20993
  `;
20855
20994
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
20856
20995
  ${line}`;
@@ -22155,8 +22294,8 @@ function ipv6(host) {
22155
22294
  const head = groups(halves[0]);
22156
22295
  const back = halves.length === 2 ? groups(halves[1]) : [];
22157
22296
  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;
22297
+ const words3 = [...head, ...back].map((g) => Number.parseInt(g, 16));
22298
+ const missing6 = 8 - words3.length - tail2.length;
22160
22299
  if (halves.length === 2 ? missing6 < 1 : missing6 !== 0) return void 0;
22161
22300
  const all = [
22162
22301
  ...head.map((g) => Number.parseInt(g, 16)),
@@ -25797,7 +25936,7 @@ function selectFlag(req) {
25797
25936
  throw new KernelError("EINVAL", "ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS: alpn with select");
25798
25937
  return TLS_SERVER_SELECT;
25799
25938
  }
25800
- function flagsOf(req) {
25939
+ function flagsOf2(req) {
25801
25940
  return certFlags(req) | selectFlag(req) | (req.keylog ? TLS_KEYLOG : 0);
25802
25941
  }
25803
25942
  async function engineOf(proc, { select, keylog }) {
@@ -25848,7 +25987,7 @@ async function serverOf(proc, req) {
25848
25987
  let mine;
25849
25988
  try {
25850
25989
  mine = req.ca && req.ca.length > 0 ? storeOf(engine, req.ca) : void 0;
25851
- const flags2 = flagsOf(req);
25990
+ const flags2 = flagsOf2(req);
25852
25991
  const store = mine ?? (flags2 & CERT_FLAGS ? await defaultStore(engine, supportOf(proc.net).roots) : void 0);
25853
25992
  return {
25854
25993
  engine,
@@ -27737,7 +27876,7 @@ var WRITE_ACCESS = 3;
27737
27876
  var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
27738
27877
  var STUCK_ESCAPES = /* @__PURE__ */ new Set([SIG.INT, SIG.TERM, SIG.QUIT, SIG.HUP]);
27739
27878
  var DEFAULT_UMASK2 = 18;
27740
- function words(value2) {
27879
+ function words2(value2) {
27741
27880
  if (!Array.isArray(value2) || !value2.every((w) => typeof w === "string")) {
27742
27881
  throw new KernelError("EINVAL");
27743
27882
  }
@@ -29190,7 +29329,7 @@ var WasmProcess = class {
29190
29329
  if (!sudo) throw new KernelError("ENOSYS");
29191
29330
  const call = {
29192
29331
  op: "proc-sudo",
29193
- args: words(req.args),
29332
+ args: words2(req.args),
29194
29333
  cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
29195
29334
  ...typeof req.term === "string" ? { term: req.term } : {}
29196
29335
  };
@@ -33800,7 +33939,40 @@ function describe(req, tty) {
33800
33939
  }
33801
33940
  function refuseChanged(approved, now) {
33802
33941
  const changed = changedFile(approved, now);
33803
- if (changed !== void 0) throw new ChangedError(changedText(changed));
33942
+ if (changed !== void 0) throw new ChangedError(changedText(changed), changed);
33943
+ }
33944
+ var O_WRONLY2 = 1;
33945
+ var OUTPUT_MODE = 384;
33946
+ var ERRNO_TEXT = {
33947
+ EACCES: "Permission denied",
33948
+ EISDIR: "Is a directory",
33949
+ ENOENT: "No such file or directory",
33950
+ ENOTDIR: "Not a directory",
33951
+ EROFS: "Read-only file system"
33952
+ };
33953
+ function sudoFailure(errno3, message3) {
33954
+ return Object.assign(new Error(message3), { code: errno3 });
33955
+ }
33956
+ function sudoCause(err) {
33957
+ const code = codeOf8(err);
33958
+ if (typeof code !== "string") throw err;
33959
+ return code;
33960
+ }
33961
+ function requested(err) {
33962
+ if (err instanceof ChangedError) throw sudoFailure(err.code, requestedText(err.file));
33963
+ throw err;
33964
+ }
33965
+ var MAX_SPAWN_TAGS = 8;
33966
+ function tagList(value2) {
33967
+ if (!Array.isArray(value2)) {
33968
+ tagOption(value2);
33969
+ return value2 === void 0 ? [] : [value2];
33970
+ }
33971
+ if (value2.length > MAX_SPAWN_TAGS) {
33972
+ throw new TypeError(`a spawn adds at most ${MAX_SPAWN_TAGS} tags`);
33973
+ }
33974
+ for (const each of value2) tagOption(each ?? 0);
33975
+ return value2;
33804
33976
  }
33805
33977
  function linksOf2(req) {
33806
33978
  return req.links === void 0 ? {} : { links: req.links };
@@ -33926,27 +34098,27 @@ function appendedRoots(given) {
33926
34098
  }
33927
34099
  return [...roots];
33928
34100
  }
33929
- function plainEnv(words2) {
33930
- const next3 = words2[1];
33931
- if (baseName2(words2[0] ?? "") !== "env" || next3 === void 0) return false;
34101
+ function plainEnv(words3) {
34102
+ const next3 = words3[1];
34103
+ if (baseName2(words3[0] ?? "") !== "env" || next3 === void 0) return false;
33932
34104
  return next3 === "-S" || !next3.startsWith("-") && !next3.includes("=");
33933
34105
  }
33934
- function shebangCommand(words2) {
33935
- if (!plainEnv(words2)) {
33936
- const arg2 = words2.slice(1).join(" ");
33937
- return { interp: words2[0], args: arg2 ? [arg2] : [] };
34106
+ function shebangCommand(words3) {
34107
+ if (!plainEnv(words3)) {
34108
+ const arg2 = words3.slice(1).join(" ");
34109
+ return { interp: words3[0], args: arg2 ? [arg2] : [] };
33938
34110
  }
33939
- const split3 = words2[1] === "-S";
33940
- const [interp, ...rest2] = words2.slice(split3 ? 2 : 1);
34111
+ const split3 = words3[1] === "-S";
34112
+ const [interp, ...rest2] = words3.slice(split3 ? 2 : 1);
33941
34113
  const arg = rest2.join(" ");
33942
34114
  return { interp, args: split3 ? rest2 : arg ? [arg] : [] };
33943
34115
  }
33944
34116
  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] ?? "");
34117
+ const words3 = shebang(head);
34118
+ if (!words3) return "node";
34119
+ if (baseName2(words3[0]) === "env") words3.shift();
34120
+ if (words3[0] === "-S") words3.shift();
34121
+ return baseName2(words3[0] ?? "");
33950
34122
  }
33951
34123
  function expandDefaults(defaults2, env) {
33952
34124
  const out = {};
@@ -34721,9 +34893,9 @@ var Launcher = class {
34721
34893
  const head = await this.head(script);
34722
34894
  if (!head) return void 0;
34723
34895
  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);
34896
+ const words3 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
34897
+ if (!words3) return void 0;
34898
+ const { interp, args } = shebangCommand(words3);
34727
34899
  const passed = [...args, command ? script : file, ...argv.slice(1)];
34728
34900
  if (!interp) return void 0;
34729
34901
  const found = await this.resolve(interp, interp, cwd, scope);
@@ -34954,7 +35126,7 @@ var Launcher = class {
34954
35126
  const hex4 = pinned2[real];
34955
35127
  if (hex4 !== void 0) return { [real]: hex4 };
34956
35128
  if (await this.sudoer.trusted(real)) return {};
34957
- throw new ChangedError(changedText(real));
35129
+ throw new ChangedError(changedText(real), real);
34958
35130
  }
34959
35131
  async pinnedBytes(pin, bytes) {
34960
35132
  const [real = ""] = Object.keys(pin);
@@ -35233,6 +35405,10 @@ var Launcher = class {
35233
35405
  }
35234
35406
  async launch(asked, req) {
35235
35407
  const cwd = await physicalPathAsync(this.base, req.cwd);
35408
+ if (req.exactCwd && cwd !== req.cwd) {
35409
+ await req.fds.closeAll();
35410
+ throw sudoFailure("EACCES", `sudo: ${req.cwd} changed since it was requested`);
35411
+ }
35236
35412
  const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
35237
35413
  await this.pnpmReady();
35238
35414
  let loaded;
@@ -36928,6 +37104,96 @@ var Launcher = class {
36928
37104
  const signal = handle.termsig();
36929
37105
  return { status, ...signal ? { signal } : {}, stdout: concat7(out), stderr: concat7(err) };
36930
37106
  }
37107
+ async sudoRun(replay, options = {}) {
37108
+ const added = tagList(options.tag);
37109
+ const replayed = await this.sudoer.replay(replay);
37110
+ if ("error" in replayed) throw sudoFailure(replayed.errno, replayed.error);
37111
+ const { grant, caller, target } = replayed;
37112
+ let tags = withTag(void 0, options.base);
37113
+ for (const tag of [...replayed.tags, ...added]) tags = withTag(tags, tag);
37114
+ const tagged = { tags, tagBase: options.base === void 0 ? 0 : 1 };
37115
+ await this.swaps.settled();
37116
+ this.resetCatalogs();
37117
+ const req = { file: grant.file, argv: grant.argv, env: grant.env, cwd: grant.cwd };
37118
+ const ctx = { ppid: 0, cred: grant.cred, scope: "" };
37119
+ const planned = await this.planOne(req, ctx).catch((err2) => {
37120
+ const code = sudoCause(err2);
37121
+ const word = grant.argv[0] ?? grant.file;
37122
+ if (code === "ENOENT") throw sudoFailure(code, `sudo: ${word}: command not found`);
37123
+ throw sudoFailure(code, `sudo: ${grant.file}: ${ERRNO_TEXT[code] ?? code}`);
37124
+ });
37125
+ const pin = await this.unchanged(planned, grant.file, grant.cwd, grant).catch(requested);
37126
+ const fds = await this.sudoOutputs(options, caller, this.stackFor(tags, tagged.tagBase));
37127
+ const err = fds.get(2).file;
37128
+ const report = (message3) => void Promise.resolve(err.write?.(encoder10.encode(`${message3}
37129
+ `))).catch(() => void 0);
37130
+ const replan = () => this.planOne(req, ctx);
37131
+ if (!await this.grantedCwd(grant.caller, grant.cwd)) {
37132
+ await fds.closeAll();
37133
+ throw sudoFailure("EACCES", `sudo: ${grant.cwd} changed since it was requested`);
37134
+ }
37135
+ const handle = await this.launch(
37136
+ { ...planned, replan },
37137
+ {
37138
+ ...pin,
37139
+ env: grant.env,
37140
+ cwd: grant.cwd,
37141
+ fds,
37142
+ report,
37143
+ ...this.identity(target, tagged, options.session),
37144
+ ...caller.umask !== void 0 ? { umask: caller.umask } : {},
37145
+ links: grant.cred.euid,
37146
+ exactCwd: true
37147
+ }
37148
+ ).catch(requested);
37149
+ const ended = handle.exited.then((status) => {
37150
+ const signal = handle.termsig();
37151
+ return { status, ...signal ? { signal } : {} };
37152
+ });
37153
+ return { pid: handle.pid, ended };
37154
+ }
37155
+ async sudoOutputs(options, caller, stack) {
37156
+ const fds = new FdTable();
37157
+ const opened2 = /* @__PURE__ */ new Map();
37158
+ fds.installAt(0, deviceFile("null"));
37159
+ try {
37160
+ fds.installAt(1, await this.sudoOutput(options.stdout, caller, stack, opened2));
37161
+ fds.installAt(2, await this.sudoOutput(options.stderr, caller, stack, opened2));
37162
+ } catch (err) {
37163
+ await fds.closeAll();
37164
+ throw err;
37165
+ }
37166
+ return fds;
37167
+ }
37168
+ async sudoOutput(path, caller, stack, opened2) {
37169
+ if (path === void 0) return deviceFile("null");
37170
+ if (typeof path !== "string" || !path.startsWith("/")) {
37171
+ throw sudoFailure("EINVAL", `sudo: ${String(path)} is not an absolute path`);
37172
+ }
37173
+ const cred = credOf(caller);
37174
+ const walked = await walk(this.fs, cred, normalizePath(path), true, caller.uid);
37175
+ const real = walked.path;
37176
+ const same3 = opened2.get(real);
37177
+ if (same3) return same3.retain();
37178
+ try {
37179
+ if (walked.denied !== void 0) throw fsError("EACCES", path);
37180
+ await writable({ ...this.execGuard(cred, 0), links: caller.uid }, "open", real);
37181
+ const owner = { uid: caller.uid, gid: caller.gid, mode: OUTPUT_MODE };
37182
+ await stack.noLinks.writeFile(real, new Uint8Array(0), owner);
37183
+ } catch (err) {
37184
+ const code = sudoCause(err);
37185
+ throw sudoFailure(code, `sudo: ${path}: ${ERRNO_TEXT[code] ?? code}`);
37186
+ }
37187
+ const file = vfsFile(stack.fs, { path: real, flags: O_WRONLY2, position: 0 }, stack.nodes);
37188
+ const again = await walk(this.fs, cred, real, true, caller.uid);
37189
+ const st = await stack.noLinks.lstat(real).catch(() => void 0);
37190
+ if (again.path !== real || again.denied !== void 0 || !st?.isFile) {
37191
+ await file.release();
37192
+ throw sudoFailure("EACCES", `sudo: ${path} changed while it was opened`);
37193
+ }
37194
+ opened2.set(real, file);
37195
+ return file;
37196
+ }
36931
37197
  };
36932
37198
  function cronLocks() {
36933
37199
  return globalThis.navigator?.locks;
@@ -37393,11 +37659,38 @@ function policyAsker(port) {
37393
37659
  }
37394
37660
  };
37395
37661
  }
37662
+ function processOps(ready2, reply) {
37663
+ const terminals = /* @__PURE__ */ new Map();
37664
+ async function terminal(req) {
37665
+ const onData = (bytes) => reply(req.id, { fd: 1, bytes });
37666
+ const session = await (await ready2()).openTerminal(req.argv, { ...req.options, onData });
37667
+ terminals.set(req.id, session);
37668
+ reply(req.id, { started: session.pid });
37669
+ try {
37670
+ const status = await session.exited;
37671
+ const signal = session.termsig();
37672
+ return signal ? { status, signal } : status;
37673
+ } finally {
37674
+ terminals.delete(req.id);
37675
+ }
37676
+ }
37677
+ async function sudoRun(req) {
37678
+ const running = await (await ready2()).sudoRun(req.replay, req.options);
37679
+ reply(req.id, { started: running.pid });
37680
+ return running.ended;
37681
+ }
37682
+ function onTerminal(req) {
37683
+ const session = terminals.get(req.terminal);
37684
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
37685
+ act2(session, req);
37686
+ return true;
37687
+ }
37688
+ return { terminal, sudoRun, act: onTerminal };
37689
+ }
37396
37690
  function serveKernel(port, deps) {
37397
37691
  let launcher;
37398
37692
  let remote;
37399
37693
  let uplink;
37400
- const terminals = /* @__PURE__ */ new Map();
37401
37694
  const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
37402
37695
  const clients = /* @__PURE__ */ new Set();
37403
37696
  const sealed = sealedAsker(port);
@@ -37426,19 +37719,7 @@ function serveKernel(port, deps) {
37426
37719
  if (!launcher) throw new Error("the kernel is not initialized");
37427
37720
  return launcher;
37428
37721
  }
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
- }
37722
+ const processes2 = processOps(ready2, reply);
37442
37723
  async function init(req) {
37443
37724
  launcher = rootOf(deps, req).then(async (root) => {
37444
37725
  req = await rootNamed(deps, req, root);
@@ -37493,7 +37774,8 @@ function serveKernel(port, deps) {
37493
37774
  if (req.op === "init") return init(req);
37494
37775
  if (isAttestRequest(req)) return attestOp(await ready2(), req);
37495
37776
  if (req.op === "sealed-placeholders") return (await ready2()).setPlaceholders(req.list);
37496
- if (req.op === "open-terminal") return terminal(req);
37777
+ if (req.op === "open-terminal") return processes2.terminal(req);
37778
+ if (req.op === "sudo-run") return processes2.sudoRun(req);
37497
37779
  if (req.op === "connect") return connect2(req.user, req.tag, req.requests);
37498
37780
  if (isUsersRequest(req)) return usersOp(await ready2(), req);
37499
37781
  if (req.op === "routes" || req.op === "transport-traits") {
@@ -37502,12 +37784,7 @@ function serveKernel(port, deps) {
37502
37784
  if (req.op === "dial") return dial(await ready2(), req);
37503
37785
  if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
37504
37786
  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
- }
37787
+ if (req.op === "terminal") return processes2.act(req);
37511
37788
  return (await ready2()).run(req.argv, {
37512
37789
  ...req.options,
37513
37790
  onStdout: (bytes) => reply(req.id, { fd: 1, bytes }),