@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.
package/dist/node.js CHANGED
@@ -2454,6 +2454,7 @@ var LoopbackNet = class {
2454
2454
  reserved = /* @__PURE__ */ new Set();
2455
2455
  facades = /* @__PURE__ */ new Map();
2456
2456
  proxyStamped;
2457
+ stamped = /* @__PURE__ */ new Set();
2457
2458
  nextEphemeral = EPHEMERAL_FIRST;
2458
2459
  route;
2459
2460
  origins;
@@ -2528,6 +2529,9 @@ var LoopbackNet = class {
2528
2529
  this.kernelOnly(port, true);
2529
2530
  this.facades.set(port, name2);
2530
2531
  }
2532
+ stampPeers(port) {
2533
+ this.stamped.add(port);
2534
+ }
2531
2535
  stampProxy(port) {
2532
2536
  this.proxyStamped = port;
2533
2537
  }
@@ -2536,7 +2540,7 @@ var LoopbackNet = class {
2536
2540
  }
2537
2541
  stampsPeers(addr) {
2538
2542
  if (addr.family !== "inet" || !isLoopback(canonicalHost(addr.host))) return false;
2539
- return this.facades.has(addr.port) || this.proxyStamped === addr.port;
2543
+ return this.facades.has(addr.port) || this.stamped.has(addr.port) || this.proxyStamped === addr.port;
2540
2544
  }
2541
2545
  activate(addr, start2) {
2542
2546
  this.activators.set(keyOf(addr), start2);
@@ -3444,10 +3448,10 @@ var CdpFacade = class {
3444
3448
  );
3445
3449
  return;
3446
3450
  }
3447
- await this.write(conn, json(await this.list(runtime)));
3451
+ await this.write(conn, json(await this.list(runtime, conn.peerStamp)));
3448
3452
  }
3449
- async list(runtime) {
3450
- const conn = await this.host(runtime);
3453
+ async list(runtime, stamp) {
3454
+ const conn = await this.host(runtime, stamp);
3451
3455
  try {
3452
3456
  const reply = await new Promise(
3453
3457
  (resolve2, reject) => {
@@ -3482,12 +3486,15 @@ var CdpFacade = class {
3482
3486
  conn.close();
3483
3487
  }
3484
3488
  }
3485
- host(runtime) {
3486
- return this.options.hosts.connect(runtime ? { runtime } : {});
3489
+ host(runtime, stamp) {
3490
+ return this.options.hosts.connect({
3491
+ ...runtime ? { runtime } : {},
3492
+ ...stamp ? { stamp } : {}
3493
+ });
3487
3494
  }
3488
3495
  async upgrade(conn, incoming, req, runtime) {
3489
3496
  const accept2 = await acceptKey(req.headers);
3490
- const host = await this.host(runtime);
3497
+ const host = await this.host(runtime, conn.peerStamp);
3491
3498
  const limits = {
3492
3499
  max: this.options.maxMessage ?? MAX_MESSAGE,
3493
3500
  closeWaitMs: this.options.closeWaitMs ?? CLOSE_WAIT_MS
@@ -3567,6 +3574,7 @@ function enableCdp(net, hosts) {
3567
3574
  let facade;
3568
3575
  net.kernelOnly(CDP_PORT, true);
3569
3576
  net.reserve(CDP_PORT);
3577
+ net.stampPeers(CDP_PORT);
3570
3578
  net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
3571
3579
  const started = new CdpFacade({ net, hosts });
3572
3580
  facade = started;
@@ -3609,7 +3617,7 @@ var CdpHosts = class {
3609
3617
  if (!entry) throw new HttpError(503, NO_CDP_HOST);
3610
3618
  let conn;
3611
3619
  try {
3612
- conn = await entry.hook(request.runtime ? { runtime: request.runtime } : {});
3620
+ conn = await entry.hook(hookRequest(request));
3613
3621
  } catch (e) {
3614
3622
  throw new HttpError(502, `CDP host: ${e instanceof Error ? e.message : String(e)}`);
3615
3623
  }
@@ -3629,6 +3637,12 @@ var CdpHosts = class {
3629
3637
  for (const entry of [this.embedder, ...this.clients]) if (entry) closeAll(entry);
3630
3638
  }
3631
3639
  };
3640
+ function hookRequest({ runtime, stamp }) {
3641
+ return {
3642
+ ...runtime ? { runtime } : {},
3643
+ ...stamp ? { stamp: structuredClone(stamp) } : {}
3644
+ };
3645
+ }
3632
3646
  function closeAll(entry) {
3633
3647
  for (const conn of [...entry.open]) {
3634
3648
  conn.close();
@@ -3787,7 +3801,7 @@ function satisfies(version, range2) {
3787
3801
  }
3788
3802
 
3789
3803
  // src/version.ts
3790
- var KERNEL_VERSION = true ? "1.180.0" : "0.0.0-dev";
3804
+ var KERNEL_VERSION = true ? "1.182.0" : "0.0.0-dev";
3791
3805
  var OS_RELEASE = "6.0.0-slicc";
3792
3806
  function procVersion() {
3793
3807
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -4652,27 +4666,27 @@ function hashId(text) {
4652
4666
  return hex4(a) + hex4(b);
4653
4667
  }
4654
4668
  function split(text, count2) {
4655
- const words2 = [];
4669
+ const words3 = [];
4656
4670
  let rest2 = text;
4657
4671
  for (let i = 0; i < count2; i++) {
4658
4672
  const match = /^(\S+)\s+/.exec(rest2);
4659
4673
  if (!match) return void 0;
4660
- words2.push(match[1]);
4674
+ words3.push(match[1]);
4661
4675
  rest2 = rest2.slice(match[0].length);
4662
4676
  }
4663
- return [words2, rest2.trim()];
4677
+ return [words3, rest2.trim()];
4664
4678
  }
4665
4679
  function jobOf(content, pending, ctx, env) {
4666
4680
  const fields = content.startsWith("@") ? 1 : 5;
4667
4681
  const parts2 = split(content, fields + (ctx.system ? 1 : 0));
4668
4682
  if (!parts2 || parts2[1] === "") throw new Error("no command");
4669
- const [words2, command] = parts2;
4670
- const schedule = words2.slice(0, fields).join(" ");
4683
+ const [words3, command] = parts2;
4684
+ const schedule = words3.slice(0, fields).join(" ");
4671
4685
  const when = parseWhen(schedule);
4672
4686
  const zone = env.CRON_TZ;
4673
4687
  if (zone && !when.reboot && canonicalZone(zone) === void 0)
4674
4688
  throw new Error(`CRON_TZ: unknown time zone "${zone}"`);
4675
- const user = ctx.system ? words2[fields] : void 0;
4689
+ const user = ctx.system ? words3[fields] : void 0;
4676
4690
  if (user !== void 0 && !USER_NAME.test(user)) throw new Error(`bad user name ${user}`);
4677
4691
  if (pending !== void 0 && !JOB_NAME.test(pending))
4678
4692
  throw new Error(`bad job name "${pending}"`);
@@ -8374,9 +8388,11 @@ var SpawnError = class extends Error {
8374
8388
  };
8375
8389
  var ChangedError = class extends SpawnError {
8376
8390
  notice;
8377
- constructor(notice) {
8391
+ file;
8392
+ constructor(notice, file) {
8378
8393
  super("EPERM");
8379
8394
  this.notice = notice;
8395
+ this.file = file;
8380
8396
  }
8381
8397
  };
8382
8398
  function interrupted(signal) {
@@ -15453,21 +15469,21 @@ function unescaped(double) {
15453
15469
  }
15454
15470
  function splitWords(text) {
15455
15471
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
15456
- const words2 = [];
15472
+ const words3 = [];
15457
15473
  let word;
15458
15474
  while (piece.lastIndex < text.length) {
15459
15475
  const m = piece.exec(text);
15460
15476
  if (!m) return void 0;
15461
15477
  const [, single, double, escaped, plain3, space] = m;
15462
15478
  if (space !== void 0) {
15463
- if (word !== void 0) words2.push(word);
15479
+ if (word !== void 0) words3.push(word);
15464
15480
  word = void 0;
15465
15481
  continue;
15466
15482
  }
15467
15483
  word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain3));
15468
15484
  }
15469
- if (word !== void 0) words2.push(word);
15470
- return words2;
15485
+ if (word !== void 0) words3.push(word);
15486
+ return words3;
15471
15487
  }
15472
15488
  function quoteWord(word) {
15473
15489
  const text = hasControl(word) ? withoutControl(word) : word;
@@ -15488,14 +15504,14 @@ function addPlain(word, plain3) {
15488
15504
  }
15489
15505
  function splitPatterns(text) {
15490
15506
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
15491
- const words2 = [];
15507
+ const words3 = [];
15492
15508
  let word;
15493
15509
  while (piece.lastIndex < text.length) {
15494
15510
  const m = piece.exec(text);
15495
15511
  if (!m) return void 0;
15496
15512
  const [, single, double, escaped, plain3, space] = m;
15497
15513
  if (space !== void 0) {
15498
- if (word !== void 0) words2.push(word);
15514
+ if (word !== void 0) words3.push(word);
15499
15515
  word = void 0;
15500
15516
  continue;
15501
15517
  }
@@ -15503,8 +15519,8 @@ function splitPatterns(text) {
15503
15519
  if (plain3 !== void 0) addPlain(word, plain3);
15504
15520
  else addPart(word, single ?? (double !== void 0 ? unescaped(double) : escaped));
15505
15521
  }
15506
- if (word !== void 0) words2.push(word);
15507
- return words2;
15522
+ if (word !== void 0) words3.push(word);
15523
+ return words3;
15508
15524
  }
15509
15525
  var literal = (pattern) => pattern.map((p) => p ?? "*").join("");
15510
15526
  var isAny = (pattern) => pattern?.length === 1 && pattern[0] === null;
@@ -15577,14 +15593,14 @@ function whoError(who) {
15577
15593
  return `expected a user name, %group or ALL first, not ${shown(who)}`;
15578
15594
  }
15579
15595
  function cwdOf(word) {
15580
- const words2 = splitWords(word);
15581
- const path = words2?.length === 1 ? words2[0] : "";
15596
+ const words3 = splitWords(word);
15597
+ const path = words3?.length === 1 ? words3[0] : "";
15582
15598
  return path.startsWith("/") ? normalizePath(path) : "";
15583
15599
  }
15584
15600
  function commandOf2(text, after) {
15585
- const words2 = splitPatterns(text);
15586
- if (words2 === void 0) return "a quote in the command is not closed, or it ends in a \\";
15587
- const [first, ...args] = words2;
15601
+ const words3 = splitPatterns(text);
15602
+ if (words3 === void 0) return "a quote in the command is not closed, or it ends in a \\";
15603
+ const [first, ...args] = words3;
15588
15604
  const path = first ? literal(first) : "";
15589
15605
  if (path === "") return `expected a command after ${after}`;
15590
15606
  if (!path.startsWith("/")) return `expected an absolute command path, not ${shown(path)}`;
@@ -16274,6 +16290,9 @@ function recordable(cwd, argv) {
16274
16290
  function changedText(file) {
16275
16291
  return `sudo: ${file} changed since it was approved`;
16276
16292
  }
16293
+ function requestedText(file) {
16294
+ return `sudo: ${file} changed since it was requested`;
16295
+ }
16277
16296
  function changedFile(approved, now) {
16278
16297
  const files = /* @__PURE__ */ new Set([...Object.keys(approved), ...Object.keys(now)]);
16279
16298
  return [...files].find((file) => approved[file] !== now[file]);
@@ -16281,6 +16300,67 @@ function changedFile(approved, now) {
16281
16300
  function pinned(rule, bound3) {
16282
16301
  return rule.sha256 === void 0 || changedFile(rule.sha256, bound3) === void 0;
16283
16302
  }
16303
+ var REPLAY_ID = /^[!-~]{1,256}$/;
16304
+ var REPLAY_TAGS_MAX = 64;
16305
+ var REPLAY_ARGV_MAX = 4096;
16306
+ var REPLAY_BYTES_MAX = 1024 * 1024;
16307
+ var NOT_A_REQUEST = "sudo: that isn't a sudo request this kernel can run";
16308
+ function strings(value2, max) {
16309
+ return Array.isArray(value2) && value2.length <= max && value2.every((s) => typeof s === "string");
16310
+ }
16311
+ function words(value2) {
16312
+ if (!strings(value2, REPLAY_ARGV_MAX) || value2.length === 0) return false;
16313
+ const bytes = value2.reduce((n, s) => n + s.length + 1, 0);
16314
+ return bytes <= REPLAY_BYTES_MAX && !value2.some((s) => s.includes("\0"));
16315
+ }
16316
+ function filesOf(value2) {
16317
+ if (value2 === void 0) return {};
16318
+ if (!Array.isArray(value2) || value2.length > OPERANDS_MAX) return void 0;
16319
+ const files = {};
16320
+ for (const file of value2) {
16321
+ const { path, sha256: hex4 } = file ?? {};
16322
+ if (typeof path !== "string" || typeof hex4 !== "string") return void 0;
16323
+ files[path] = hex4;
16324
+ }
16325
+ return files;
16326
+ }
16327
+ function flagsOf(value2) {
16328
+ if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) return void 0;
16329
+ const entries = Object.entries(value2);
16330
+ if (entries.length > ROOT_ONLY_MAX || entries.some(([, v]) => typeof v !== "boolean")) {
16331
+ return void 0;
16332
+ }
16333
+ return Object.fromEntries(entries);
16334
+ }
16335
+ function replayOf(value2) {
16336
+ if (typeof value2 !== "object" || value2 === null) return failed2("EINVAL", NOT_A_REQUEST);
16337
+ const r = value2;
16338
+ if (r.validate === true) return failed2("EINVAL", "sudo: a sudo -v request runs no command");
16339
+ if (r.stdin === "pipe" || r.stdin === "file") {
16340
+ return failed2("EINVAL", `sudo: the request read a ${r.stdin}, and that input is gone`);
16341
+ }
16342
+ const { id: id2, user, uid, target, argv, path, cwd, stdin, tag, tags } = r;
16343
+ const rootOnly2 = flagsOf(r.rootOnly);
16344
+ const bound3 = filesOf(r.userWritable);
16345
+ const named = (s) => typeof s === "string" && s !== "";
16346
+ const absolute2 = (s) => typeof s === "string" && s.startsWith("/") && s.length <= PATH_MAX && !s.includes("\0");
16347
+ const valid2 = 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;
16348
+ if (!valid2) return failed2("EINVAL", NOT_A_REQUEST);
16349
+ const all = tags ?? (tag === void 0 ? [] : [tag]);
16350
+ return {
16351
+ id: id2,
16352
+ user,
16353
+ uid,
16354
+ target,
16355
+ argv,
16356
+ path,
16357
+ cwd,
16358
+ stdin,
16359
+ rootOnly: rootOnly2,
16360
+ bound: bound3,
16361
+ tags: [...new Set(all)]
16362
+ };
16363
+ }
16284
16364
  var VISUDO_FILES_MAX = 64;
16285
16365
  var VISUDO_TEXT_MAX = 1024 * 1024;
16286
16366
  function visudoCall(req) {
@@ -16682,6 +16762,61 @@ var Sudo = class {
16682
16762
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
16683
16763
  return this.command(session, root, mine, call);
16684
16764
  }
16765
+ async replay(value2) {
16766
+ const r = replayOf(value2);
16767
+ if ("error" in r) return r;
16768
+ if (this.o.mode !== "enforce") {
16769
+ return failed2("EINVAL", `sudo: users are '${this.o.mode}', so no request needed approval`);
16770
+ }
16771
+ const self = await this.o.users.lookup(r.user);
16772
+ if (!self) return failed2("ENOENT", `sudo: unknown user ${r.user}`);
16773
+ if (self.uid !== r.uid) return failed2("EPERM", `sudo: ${self.name} is no longer uid ${r.uid}`);
16774
+ const target = await this.lookup(r.target);
16775
+ if (!target) return failed2("ENOENT", `sudo: unknown user ${r.target}`);
16776
+ const cred = credOf(self);
16777
+ const [word = "", ...args] = r.argv;
16778
+ const argv = [r.path, ...args];
16779
+ if (!await this.mayAsk(self, target, r, argv)) {
16780
+ const quoted = argv.map(quoteWord).join(" ");
16781
+ return failed2(
16782
+ "EACCES",
16783
+ `sudo: ${self.name} may no longer ask to run ${quoted} as ${target.name}`
16784
+ );
16785
+ }
16786
+ if (await this.canonical(r.cwd) !== r.cwd) {
16787
+ return failed2("EACCES", `sudo: ${r.cwd} changed since it was requested`);
16788
+ }
16789
+ if (!await this.reachable(cred, r.cwd)) {
16790
+ return failed2(
16791
+ "EACCES",
16792
+ `sudo: cannot run in ${r.cwd}: it isn't a directory ${self.name} can enter`
16793
+ );
16794
+ }
16795
+ const path = await this.locate(word, r.cwd);
16796
+ if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
16797
+ if (path !== r.path) return failed2("EPERM", `sudo: ${word} now runs ${path}, not ${r.path}`);
16798
+ const bound3 = await this.bound(path, r.cwd);
16799
+ if (!bound3) return failed2("ENOENT", `sudo: ${word}: command not found`);
16800
+ const changed = changedFile(r.bound, bound3);
16801
+ if (changed !== void 0) return failed2("EPERM", requestedText(changed));
16802
+ const now = await this.rootOnly(r.cwd, args);
16803
+ const flipped = Object.keys(r.rootOnly).find((at) => r.rootOnly[at] && now[at] !== true);
16804
+ if (flipped !== void 0) return failed2("EPERM", `sudo: ${flipped} is no longer root-only`);
16805
+ const { grant } = this.grant(self, target, cred, r.argv, path, r.cwd, void 0, bound3);
16806
+ (this.o.warn ?? console.warn)(
16807
+ `sudo: ${self.name} : TTY=unknown ; PWD=${quoteWord(r.cwd)} ; USER=${target.name} ; REPLAY=${r.id} ; COMMAND=${argv.map(quoteWord).join(" ")}`
16808
+ );
16809
+ return { grant, caller: self, target, tags: r.tags };
16810
+ }
16811
+ async mayAsk(self, target, r, argv) {
16812
+ if (self.uid === 0) return true;
16813
+ const cred = credOf(self);
16814
+ const { rules } = await this.policyRules({ cred, pid: 0, stdin: r.stdin });
16815
+ const applies = await this.applies(self, cred);
16816
+ return rules.some(
16817
+ (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)
16818
+ );
16819
+ }
16685
16820
  async command(s, root, mine, call) {
16686
16821
  const { caller, self, target, parsed: parsed2 } = s;
16687
16822
  const { cred } = caller;
@@ -16861,8 +16996,8 @@ ${lines2}
16861
16996
  const st = await fs.lstat(path).catch(() => void 0);
16862
16997
  if (st && !await this.trusted(path)) return false;
16863
16998
  const old = st ? await fs.readFile(path) : "";
16864
- const words2 = recordedCommand(argv);
16865
- const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words2}
16999
+ const words3 = recordedCommand(argv);
17000
+ const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words3}
16866
17001
  `;
16867
17002
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
16868
17003
  ${line}`;
@@ -18167,8 +18302,8 @@ function ipv6(host) {
18167
18302
  const head = groups(halves[0]);
18168
18303
  const back = halves.length === 2 ? groups(halves[1]) : [];
18169
18304
  if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
18170
- const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
18171
- const missing6 = 8 - words2.length - tail2.length;
18305
+ const words3 = [...head, ...back].map((g) => Number.parseInt(g, 16));
18306
+ const missing6 = 8 - words3.length - tail2.length;
18172
18307
  if (halves.length === 2 ? missing6 < 1 : missing6 !== 0) return void 0;
18173
18308
  const all = [
18174
18309
  ...head.map((g) => Number.parseInt(g, 16)),
@@ -21652,7 +21787,7 @@ function selectFlag(req) {
21652
21787
  throw new KernelError("EINVAL", "ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS: alpn with select");
21653
21788
  return TLS_SERVER_SELECT;
21654
21789
  }
21655
- function flagsOf(req) {
21790
+ function flagsOf2(req) {
21656
21791
  return certFlags(req) | selectFlag(req) | (req.keylog ? TLS_KEYLOG : 0);
21657
21792
  }
21658
21793
  async function engineOf(proc, { select, keylog }) {
@@ -21703,7 +21838,7 @@ async function serverOf(proc, req) {
21703
21838
  let mine;
21704
21839
  try {
21705
21840
  mine = req.ca && req.ca.length > 0 ? storeOf(engine, req.ca) : void 0;
21706
- const flags2 = flagsOf(req);
21841
+ const flags2 = flagsOf2(req);
21707
21842
  const store = mine ?? (flags2 & CERT_FLAGS ? await defaultStore(engine, supportOf(proc.net).roots) : void 0);
21708
21843
  return {
21709
21844
  engine,
@@ -23592,7 +23727,7 @@ var WRITE_ACCESS = 3;
23592
23727
  var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
23593
23728
  var STUCK_ESCAPES = /* @__PURE__ */ new Set([SIG.INT, SIG.TERM, SIG.QUIT, SIG.HUP]);
23594
23729
  var DEFAULT_UMASK2 = 18;
23595
- function words(value2) {
23730
+ function words2(value2) {
23596
23731
  if (!Array.isArray(value2) || !value2.every((w) => typeof w === "string")) {
23597
23732
  throw new KernelError("EINVAL");
23598
23733
  }
@@ -25045,7 +25180,7 @@ var WasmProcess = class {
25045
25180
  if (!sudo) throw new KernelError("ENOSYS");
25046
25181
  const call = {
25047
25182
  op: "proc-sudo",
25048
- args: words(req.args),
25183
+ args: words2(req.args),
25049
25184
  cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
25050
25185
  ...typeof req.term === "string" ? { term: req.term } : {}
25051
25186
  };
@@ -28334,8 +28469,8 @@ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
28334
28469
  function noticeOf(data) {
28335
28470
  if (Array.isArray(data)) return { paths: data.filter((p) => typeof p === "string"), trees: [] };
28336
28471
  const notice = data ?? {};
28337
- const strings = (list) => Array.isArray(list) ? list.filter((p) => typeof p === "string") : [];
28338
- return { paths: strings(notice.paths), trees: strings(notice.trees), outside: true };
28472
+ const strings2 = (list) => Array.isArray(list) ? list.filter((p) => typeof p === "string") : [];
28473
+ return { paths: strings2(notice.paths), trees: strings2(notice.trees), outside: true };
28339
28474
  }
28340
28475
 
28341
28476
  // src/fs/opfs.ts
@@ -32648,7 +32783,40 @@ function describe(req, tty) {
32648
32783
  }
32649
32784
  function refuseChanged(approved, now) {
32650
32785
  const changed = changedFile(approved, now);
32651
- if (changed !== void 0) throw new ChangedError(changedText(changed));
32786
+ if (changed !== void 0) throw new ChangedError(changedText(changed), changed);
32787
+ }
32788
+ var O_WRONLY2 = 1;
32789
+ var OUTPUT_MODE = 384;
32790
+ var ERRNO_TEXT = {
32791
+ EACCES: "Permission denied",
32792
+ EISDIR: "Is a directory",
32793
+ ENOENT: "No such file or directory",
32794
+ ENOTDIR: "Not a directory",
32795
+ EROFS: "Read-only file system"
32796
+ };
32797
+ function sudoFailure(errno3, message3) {
32798
+ return Object.assign(new Error(message3), { code: errno3 });
32799
+ }
32800
+ function sudoCause(err) {
32801
+ const code = codeOf8(err);
32802
+ if (typeof code !== "string") throw err;
32803
+ return code;
32804
+ }
32805
+ function requested(err) {
32806
+ if (err instanceof ChangedError) throw sudoFailure(err.code, requestedText(err.file));
32807
+ throw err;
32808
+ }
32809
+ var MAX_SPAWN_TAGS = 8;
32810
+ function tagList(value2) {
32811
+ if (!Array.isArray(value2)) {
32812
+ tagOption(value2);
32813
+ return value2 === void 0 ? [] : [value2];
32814
+ }
32815
+ if (value2.length > MAX_SPAWN_TAGS) {
32816
+ throw new TypeError(`a spawn adds at most ${MAX_SPAWN_TAGS} tags`);
32817
+ }
32818
+ for (const each of value2) tagOption(each ?? 0);
32819
+ return value2;
32652
32820
  }
32653
32821
  function linksOf2(req) {
32654
32822
  return req.links === void 0 ? {} : { links: req.links };
@@ -32774,27 +32942,27 @@ function appendedRoots(given) {
32774
32942
  }
32775
32943
  return [...roots];
32776
32944
  }
32777
- function plainEnv(words2) {
32778
- const next3 = words2[1];
32779
- if (baseName2(words2[0] ?? "") !== "env" || next3 === void 0) return false;
32945
+ function plainEnv(words3) {
32946
+ const next3 = words3[1];
32947
+ if (baseName2(words3[0] ?? "") !== "env" || next3 === void 0) return false;
32780
32948
  return next3 === "-S" || !next3.startsWith("-") && !next3.includes("=");
32781
32949
  }
32782
- function shebangCommand(words2) {
32783
- if (!plainEnv(words2)) {
32784
- const arg2 = words2.slice(1).join(" ");
32785
- return { interp: words2[0], args: arg2 ? [arg2] : [] };
32950
+ function shebangCommand(words3) {
32951
+ if (!plainEnv(words3)) {
32952
+ const arg2 = words3.slice(1).join(" ");
32953
+ return { interp: words3[0], args: arg2 ? [arg2] : [] };
32786
32954
  }
32787
- const split3 = words2[1] === "-S";
32788
- const [interp, ...rest2] = words2.slice(split3 ? 2 : 1);
32955
+ const split3 = words3[1] === "-S";
32956
+ const [interp, ...rest2] = words3.slice(split3 ? 2 : 1);
32789
32957
  const arg = rest2.join(" ");
32790
32958
  return { interp, args: split3 ? rest2 : arg ? [arg] : [] };
32791
32959
  }
32792
32960
  function interpreter(head) {
32793
- const words2 = shebang(head);
32794
- if (!words2) return "node";
32795
- if (baseName2(words2[0]) === "env") words2.shift();
32796
- if (words2[0] === "-S") words2.shift();
32797
- return baseName2(words2[0] ?? "");
32961
+ const words3 = shebang(head);
32962
+ if (!words3) return "node";
32963
+ if (baseName2(words3[0]) === "env") words3.shift();
32964
+ if (words3[0] === "-S") words3.shift();
32965
+ return baseName2(words3[0] ?? "");
32798
32966
  }
32799
32967
  function expandDefaults(defaults2, env) {
32800
32968
  const out = {};
@@ -33569,9 +33737,9 @@ var Launcher = class {
33569
33737
  const head = await this.head(script);
33570
33738
  if (!head) return void 0;
33571
33739
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
33572
- const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
33573
- if (!words2) return void 0;
33574
- const { interp, args } = shebangCommand(words2);
33740
+ const words3 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
33741
+ if (!words3) return void 0;
33742
+ const { interp, args } = shebangCommand(words3);
33575
33743
  const passed = [...args, command ? script : file, ...argv.slice(1)];
33576
33744
  if (!interp) return void 0;
33577
33745
  const found = await this.resolve(interp, interp, cwd, scope);
@@ -33802,7 +33970,7 @@ var Launcher = class {
33802
33970
  const hex4 = pinned2[real];
33803
33971
  if (hex4 !== void 0) return { [real]: hex4 };
33804
33972
  if (await this.sudoer.trusted(real)) return {};
33805
- throw new ChangedError(changedText(real));
33973
+ throw new ChangedError(changedText(real), real);
33806
33974
  }
33807
33975
  async pinnedBytes(pin, bytes) {
33808
33976
  const [real = ""] = Object.keys(pin);
@@ -34081,6 +34249,10 @@ var Launcher = class {
34081
34249
  }
34082
34250
  async launch(asked, req) {
34083
34251
  const cwd = await physicalPathAsync(this.base, req.cwd);
34252
+ if (req.exactCwd && cwd !== req.cwd) {
34253
+ await req.fds.closeAll();
34254
+ throw sudoFailure("EACCES", `sudo: ${req.cwd} changed since it was requested`);
34255
+ }
34084
34256
  const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
34085
34257
  await this.pnpmReady();
34086
34258
  let loaded;
@@ -35776,6 +35948,96 @@ var Launcher = class {
35776
35948
  const signal = handle.termsig();
35777
35949
  return { status, ...signal ? { signal } : {}, stdout: concat7(out), stderr: concat7(err) };
35778
35950
  }
35951
+ async sudoRun(replay, options = {}) {
35952
+ const added = tagList(options.tag);
35953
+ const replayed = await this.sudoer.replay(replay);
35954
+ if ("error" in replayed) throw sudoFailure(replayed.errno, replayed.error);
35955
+ const { grant, caller, target } = replayed;
35956
+ let tags = withTag(void 0, options.base);
35957
+ for (const tag of [...replayed.tags, ...added]) tags = withTag(tags, tag);
35958
+ const tagged = { tags, tagBase: options.base === void 0 ? 0 : 1 };
35959
+ await this.swaps.settled();
35960
+ this.resetCatalogs();
35961
+ const req = { file: grant.file, argv: grant.argv, env: grant.env, cwd: grant.cwd };
35962
+ const ctx = { ppid: 0, cred: grant.cred, scope: "" };
35963
+ const planned = await this.planOne(req, ctx).catch((err2) => {
35964
+ const code = sudoCause(err2);
35965
+ const word = grant.argv[0] ?? grant.file;
35966
+ if (code === "ENOENT") throw sudoFailure(code, `sudo: ${word}: command not found`);
35967
+ throw sudoFailure(code, `sudo: ${grant.file}: ${ERRNO_TEXT[code] ?? code}`);
35968
+ });
35969
+ const pin = await this.unchanged(planned, grant.file, grant.cwd, grant).catch(requested);
35970
+ const fds = await this.sudoOutputs(options, caller, this.stackFor(tags, tagged.tagBase));
35971
+ const err = fds.get(2).file;
35972
+ const report = (message3) => void Promise.resolve(err.write?.(encoder10.encode(`${message3}
35973
+ `))).catch(() => void 0);
35974
+ const replan = () => this.planOne(req, ctx);
35975
+ if (!await this.grantedCwd(grant.caller, grant.cwd)) {
35976
+ await fds.closeAll();
35977
+ throw sudoFailure("EACCES", `sudo: ${grant.cwd} changed since it was requested`);
35978
+ }
35979
+ const handle = await this.launch(
35980
+ { ...planned, replan },
35981
+ {
35982
+ ...pin,
35983
+ env: grant.env,
35984
+ cwd: grant.cwd,
35985
+ fds,
35986
+ report,
35987
+ ...this.identity(target, tagged, options.session),
35988
+ ...caller.umask !== void 0 ? { umask: caller.umask } : {},
35989
+ links: grant.cred.euid,
35990
+ exactCwd: true
35991
+ }
35992
+ ).catch(requested);
35993
+ const ended = handle.exited.then((status) => {
35994
+ const signal = handle.termsig();
35995
+ return { status, ...signal ? { signal } : {} };
35996
+ });
35997
+ return { pid: handle.pid, ended };
35998
+ }
35999
+ async sudoOutputs(options, caller, stack) {
36000
+ const fds = new FdTable();
36001
+ const opened2 = /* @__PURE__ */ new Map();
36002
+ fds.installAt(0, deviceFile("null"));
36003
+ try {
36004
+ fds.installAt(1, await this.sudoOutput(options.stdout, caller, stack, opened2));
36005
+ fds.installAt(2, await this.sudoOutput(options.stderr, caller, stack, opened2));
36006
+ } catch (err) {
36007
+ await fds.closeAll();
36008
+ throw err;
36009
+ }
36010
+ return fds;
36011
+ }
36012
+ async sudoOutput(path, caller, stack, opened2) {
36013
+ if (path === void 0) return deviceFile("null");
36014
+ if (typeof path !== "string" || !path.startsWith("/")) {
36015
+ throw sudoFailure("EINVAL", `sudo: ${String(path)} is not an absolute path`);
36016
+ }
36017
+ const cred = credOf(caller);
36018
+ const walked = await walk(this.fs, cred, normalizePath(path), true, caller.uid);
36019
+ const real = walked.path;
36020
+ const same2 = opened2.get(real);
36021
+ if (same2) return same2.retain();
36022
+ try {
36023
+ if (walked.denied !== void 0) throw fsError("EACCES", path);
36024
+ await writable({ ...this.execGuard(cred, 0), links: caller.uid }, "open", real);
36025
+ const owner = { uid: caller.uid, gid: caller.gid, mode: OUTPUT_MODE };
36026
+ await stack.noLinks.writeFile(real, new Uint8Array(0), owner);
36027
+ } catch (err) {
36028
+ const code = sudoCause(err);
36029
+ throw sudoFailure(code, `sudo: ${path}: ${ERRNO_TEXT[code] ?? code}`);
36030
+ }
36031
+ const file = vfsFile(stack.fs, { path: real, flags: O_WRONLY2, position: 0 }, stack.nodes);
36032
+ const again = await walk(this.fs, cred, real, true, caller.uid);
36033
+ const st = await stack.noLinks.lstat(real).catch(() => void 0);
36034
+ if (again.path !== real || again.denied !== void 0 || !st?.isFile) {
36035
+ await file.release();
36036
+ throw sudoFailure("EACCES", `sudo: ${path} changed while it was opened`);
36037
+ }
36038
+ opened2.set(real, file);
36039
+ return file;
36040
+ }
35779
36041
  };
35780
36042
  function cronLocks() {
35781
36043
  return globalThis.navigator?.locks;
@@ -36260,7 +36522,7 @@ function serveTransport(peer, transport, {
36260
36522
  }
36261
36523
 
36262
36524
  // src/client/protocol.ts
36263
- var PROTOCOL = [1, 15];
36525
+ var PROTOCOL = [1, 17];
36264
36526
  var FS_METHODS = [
36265
36527
  "readFile",
36266
36528
  "writeFile",
@@ -36416,9 +36678,9 @@ async function accountOf(l, user) {
36416
36678
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
36417
36679
  return found;
36418
36680
  }
36419
- async function runAs(l, self, requested) {
36420
- if (requested === void 0) return self;
36421
- const target = await accountOf(l, requested);
36681
+ async function runAs(l, self, requested2) {
36682
+ if (requested2 === void 0) return self;
36683
+ const target = await accountOf(l, requested2);
36422
36684
  if (self.uid !== 0 && target.uid !== self.uid) {
36423
36685
  throw fsError("EPERM", `${self.name} may not run as ${target.name}`);
36424
36686
  }
@@ -36427,21 +36689,9 @@ async function runAs(l, self, requested) {
36427
36689
  function clientCred(self) {
36428
36690
  return self ? credOf(self) : copyCred(ROOT);
36429
36691
  }
36430
- var MAX_SPAWN_TAGS = 8;
36431
- function spawnTags(value2) {
36432
- if (!Array.isArray(value2)) {
36433
- tagOption(value2);
36434
- return value2 === void 0 ? [] : [value2];
36435
- }
36436
- if (value2.length > MAX_SPAWN_TAGS) {
36437
- throw new TypeError(`a spawn adds at most ${MAX_SPAWN_TAGS} tags`);
36438
- }
36439
- for (const each of value2) tagOption(each ?? 0);
36440
- return value2;
36441
- }
36442
36692
  async function runTagged(l, self, options, tag, session = -1) {
36443
36693
  let tags = withTag(void 0, tag);
36444
- for (const added of spawnTags(options.tag)) tags = withTag(tags, added);
36694
+ for (const added of tagList(options.tag)) tags = withTag(tags, added);
36445
36695
  return {
36446
36696
  user: await runAs(l, self, options.user),
36447
36697
  tag,
@@ -36626,6 +36876,20 @@ function requestBinding(send2, port) {
36626
36876
  close: () => unbind?.()
36627
36877
  };
36628
36878
  }
36879
+ async function sudoRunAs(l, self, host, req, reply) {
36880
+ if (self.uid !== 0) throw sudoFailure("EPERM", "sudo: only a root client may run a sudo request");
36881
+ const { stdout, stderr, tag } = req.options;
36882
+ const running = await l.sudoRun(req.replay, {
36883
+ stdout,
36884
+ stderr,
36885
+ tag,
36886
+ base: host.tag,
36887
+ session: host.session
36888
+ });
36889
+ reply(req.id, { started: running.pid });
36890
+ const { status, signal } = await running.ended;
36891
+ return { result: status, ...signalField(signal) };
36892
+ }
36629
36893
  function answered(pending, req, reply, detach) {
36630
36894
  pending.then(
36631
36895
  ({ result, signal, transfer }) => {
@@ -36775,6 +37039,8 @@ function serveClient(port, host) {
36775
37039
  case "trace":
36776
37040
  case "untrace":
36777
37041
  return traces.answer(l, self, req);
37042
+ case "sudo-run":
37043
+ return sudoRunAs(l, self, host, req, reply);
36778
37044
  case "detach":
36779
37045
  return { result: true };
36780
37046
  default:
@@ -36880,16 +37146,16 @@ function placeOf(address) {
36880
37146
  const kind = isIP3(plain3);
36881
37147
  if (kind === 4) return ipv4Place(plain3);
36882
37148
  if (kind !== 6) return void 0;
36883
- const words2 = hextets2(plain3);
36884
- const embedded2 = words2.slice(0, 5).every((w) => w === 0) && (words2[5] === 65535 || words2[5] === 0);
36885
- if (embedded2 && (words2[5] === 65535 || words2[6] !== 0)) return ipv4Place(ipv4Of(words2));
36886
- if (words2.slice(0, 7).every((w) => w === 0) && (words2[7] ?? 0) <= 1) return "loopback";
36887
- if (((words2[0] ?? 0) & 65472) === 65152) return "link-local";
36888
- if (words2.join(":") === METADATA_V6) return "link-local";
37149
+ const words3 = hextets2(plain3);
37150
+ const embedded2 = words3.slice(0, 5).every((w) => w === 0) && (words3[5] === 65535 || words3[5] === 0);
37151
+ if (embedded2 && (words3[5] === 65535 || words3[6] !== 0)) return ipv4Place(ipv4Of(words3));
37152
+ if (words3.slice(0, 7).every((w) => w === 0) && (words3[7] ?? 0) <= 1) return "loopback";
37153
+ if (((words3[0] ?? 0) & 65472) === 65152) return "link-local";
37154
+ if (words3.join(":") === METADATA_V6) return "link-local";
36889
37155
  return void 0;
36890
37156
  }
36891
- function ipv4Of(words2) {
36892
- const [high = 0, low = 0] = words2.slice(6);
37157
+ function ipv4Of(words3) {
37158
+ const [high = 0, low = 0] = words3.slice(6);
36893
37159
  return [high >> 8, high & 255, low >> 8, low & 255].join(".");
36894
37160
  }
36895
37161
  function hextets2(address) {
@@ -37970,6 +38236,19 @@ async function spawnOn(channel, argv, opts) {
37970
38236
  signal: async (name2 = "SIGTERM") => void await channel.call({ op: "kill", pid: -pgid, signal: name2 })
37971
38237
  };
37972
38238
  }
38239
+ async function sudoRunOn(channel, replay, { stdout, stderr, tag }) {
38240
+ await since(channel, 16, "sudoRun", async () => {
38241
+ });
38242
+ const started = Promise.withResolvers();
38243
+ let endedBy;
38244
+ const { done: done4 } = channel.request(
38245
+ { op: "sudo-run", replay, options: { stdout, stderr, tag } },
38246
+ { started: started.resolve, ended: (signal) => endedBy = signal }
38247
+ );
38248
+ done4.catch(started.reject);
38249
+ const pid = await started.promise;
38250
+ return { pid, pgid: pid, ended: endedOf(done4, () => endedBy) };
38251
+ }
37973
38252
  function endedOf(done4, signal) {
37974
38253
  return quiet(
37975
38254
  done4.then((status) => {
@@ -38171,6 +38450,7 @@ async function attachKernel(port, options = {}) {
38171
38450
  return () => channel.traitsListeners.delete(listener);
38172
38451
  },
38173
38452
  trace: (tag, onEvents, options2 = {}) => traceOn(channel, tag, onEvents, options2),
38453
+ sudoRun: (replay, options2 = {}) => sudoRunOn(channel, replay, options2),
38174
38454
  async close(o = {}) {
38175
38455
  if (channel.failure) return;
38176
38456
  await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
@@ -38485,6 +38765,30 @@ function checkedRequests(hook) {
38485
38765
  const kinds = kindsOf(hook);
38486
38766
  return { kinds, handle: (request) => hook.handle(request) };
38487
38767
  }
38768
+ function nodeAccounts(launcher, guard2) {
38769
+ return {
38770
+ users: {
38771
+ mode: launcher.usersMode,
38772
+ add: (user) => guard2(() => launcher.users.add(user)),
38773
+ remove: (name2, options) => guard2(() => launcher.users.remove(name2, options)),
38774
+ list: () => guard2(() => launcher.users.list())
38775
+ },
38776
+ groups: {
38777
+ list: () => guard2(() => launcher.users.groups()),
38778
+ add: (name2, options) => guard2(() => launcher.users.addGroup(name2, options?.gid)),
38779
+ addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
38780
+ removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
38781
+ }
38782
+ };
38783
+ }
38784
+ async function nodeSudoRun(launcher, guard2, replay, options) {
38785
+ const running = await guard2(() => launcher.sudoRun(replay, options));
38786
+ const ended = guard2(async () => {
38787
+ const { status, signal } = await running.ended;
38788
+ return { status, ...signalField(signal) };
38789
+ });
38790
+ return { pid: running.pid, pgid: running.pid, ended: quiet(ended) };
38791
+ }
38488
38792
  async function createNodeKernel(options = {}) {
38489
38793
  const hooks = sealedHooks(options.sealed);
38490
38794
  const timeZone = systemTimeZone(options.timeZone, clockZone(options.cron?.clock));
@@ -38613,18 +38917,8 @@ async function createNodeKernel(options = {}) {
38613
38917
  mounts: async () => launcher.mounts.listed(),
38614
38918
  setRoutes: (routes) => guard2(async () => launcher.setRoutes(routes)),
38615
38919
  updateTransportTraits: () => guard2(async () => launcher.updateTransportTraits()),
38616
- users: {
38617
- mode: launcher.usersMode,
38618
- add: (user) => guard2(() => launcher.users.add(user)),
38619
- remove: (name2, options2) => guard2(() => launcher.users.remove(name2, options2)),
38620
- list: () => guard2(() => launcher.users.list())
38621
- },
38622
- groups: {
38623
- list: () => guard2(() => launcher.users.groups()),
38624
- add: (name2, options2) => guard2(() => launcher.users.addGroup(name2, options2?.gid)),
38625
- addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
38626
- removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
38627
- },
38920
+ ...nodeAccounts(launcher, guard2),
38921
+ sudoRun: (replay, sudoOptions = {}) => nodeSudoRun(launcher, guard2, replay, sudoOptions),
38628
38922
  async connect(connectOptions = {}) {
38629
38923
  if (terminated) throw new Error(TERMINATED);
38630
38924
  const { user } = connectOptions;