@ai-ecoverse/slicc-kernel 1.178.1 → 1.178.3

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
@@ -3709,7 +3709,7 @@ function satisfies(version, range2) {
3709
3709
  }
3710
3710
 
3711
3711
  // src/version.ts
3712
- var KERNEL_VERSION = true ? "1.178.1" : "0.0.0-dev";
3712
+ var KERNEL_VERSION = true ? "1.178.3" : "0.0.0-dev";
3713
3713
  var OS_RELEASE = "6.0.0-slicc";
3714
3714
  function procVersion() {
3715
3715
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -5004,9 +5004,9 @@ var UnlinkedKeeper = class {
5004
5004
  if (kept.whole) return kept.undoing(async () => void 0);
5005
5005
  await kept.undoing(
5006
5006
  () => replace(target, async (gated, sealed) => {
5007
- const pinned = await keeper.pin(target, gated, sealed);
5008
- if (pinned.moved.has(target)) pinned.commit();
5009
- else await keeper.settle(pinned, () => fs.rm(path, options));
5007
+ const pinned2 = await keeper.pin(target, gated, sealed);
5008
+ if (pinned2.moved.has(target)) pinned2.commit();
5009
+ else await keeper.settle(pinned2, () => fs.rm(path, options));
5010
5010
  })
5011
5011
  );
5012
5012
  },
@@ -5019,8 +5019,8 @@ var UnlinkedKeeper = class {
5019
5019
  () => keeper.marks.around(
5020
5020
  [source],
5021
5021
  () => replace(target, async (gated, sealed) => {
5022
- const pinned = await keeper.pin(target, gated, sealed);
5023
- await keeper.settle(pinned, () => fs.rename(from, to));
5022
+ const pinned2 = await keeper.pin(target, gated, sealed);
5023
+ await keeper.settle(pinned2, () => fs.rename(from, to));
5024
5024
  })
5025
5025
  )
5026
5026
  );
@@ -5055,14 +5055,14 @@ var UnlinkedKeeper = class {
5055
5055
  }
5056
5056
  return false;
5057
5057
  }
5058
- async settle(pinned, op) {
5058
+ async settle(pinned2, op) {
5059
5059
  try {
5060
5060
  await op();
5061
5061
  } catch (err) {
5062
- await pinned.rollback();
5062
+ await pinned2.rollback();
5063
5063
  throw err;
5064
5064
  }
5065
- pinned.commit();
5065
+ pinned2.commit();
5066
5066
  }
5067
5067
  heldWithin(target) {
5068
5068
  const held = /* @__PURE__ */ new Map();
@@ -8294,6 +8294,13 @@ var SpawnError = class extends Error {
8294
8294
  this.code = code;
8295
8295
  }
8296
8296
  };
8297
+ var ChangedError = class extends SpawnError {
8298
+ notice;
8299
+ constructor(notice) {
8300
+ super("EPERM");
8301
+ this.notice = notice;
8302
+ }
8303
+ };
8297
8304
  function interrupted(signal) {
8298
8305
  const { promise, reject } = Promise.withResolvers();
8299
8306
  const fail3 = () => reject(new KernelError("EINTR"));
@@ -12766,11 +12773,11 @@ function systemFile(name2, listing, now, memory) {
12766
12773
  listing.processes.reduce((sum2, p) => sum2 + p.memory, 0)
12767
12774
  );
12768
12775
  }
12769
- const live = listing.processes.filter((p) => p.state !== "Z");
12776
+ const live2 = listing.processes.filter((p) => p.state !== "Z");
12770
12777
  if (name2 === "loadavg") {
12771
12778
  const last = Math.max(0, ...listing.processes.map((p) => p.pid));
12772
12779
  const [one, five, fifteen] = (listing.load ?? [0, 0, 0]).map((n) => n.toFixed(2));
12773
- return `${one} ${five} ${fifteen} 1/${live.length} ${last}
12780
+ return `${one} ${five} ${fifteen} 1/${live2.length} ${last}
12774
12781
  `;
12775
12782
  }
12776
12783
  const sum = (key) => listing.cpu?.[key] ?? listing.processes.reduce((n, p) => n + (p[key] ?? 0), 0);
@@ -15259,30 +15266,49 @@ function timestampKey(owner, type) {
15259
15266
  }
15260
15267
  return parent ? JSON.stringify(["ppid", parent.pid, parent.started]) : void 0;
15261
15268
  }
15269
+ function boundKey(bound3) {
15270
+ return JSON.stringify(
15271
+ Object.keys(bound3).sort().map((path) => [path, bound3[path]])
15272
+ );
15273
+ }
15274
+ function writable2(bound3) {
15275
+ return bound3 !== void 0 && Object.keys(bound3).length > 0;
15276
+ }
15277
+ function live(stamp, policy, now) {
15278
+ return stamp.at <= now && (policy.minutes < 0 || now - stamp.at < policy.minutes * MINUTE_MS2);
15279
+ }
15262
15280
  var Timestamps = class {
15263
15281
  users = /* @__PURE__ */ new Map();
15264
15282
  now;
15265
15283
  constructor(now) {
15266
15284
  this.now = now;
15267
15285
  }
15268
- current(owner, policy) {
15286
+ current(owner, policy, bound3) {
15269
15287
  if (policy.minutes === 0) return false;
15270
15288
  const key = timestampKey(owner, policy.type);
15271
- const at = key === void 0 ? void 0 : this.users.get(owner.uid)?.get(key);
15272
- if (at === void 0) return false;
15289
+ const stamp = key === void 0 ? void 0 : this.users.get(owner.uid)?.get(key);
15290
+ if (stamp === void 0) return false;
15273
15291
  const now = this.now();
15274
- return at <= now && (policy.minutes < 0 || now - at < policy.minutes * MINUTE_MS2);
15292
+ if (!live(stamp, policy, now)) {
15293
+ this.drop(owner.uid, key);
15294
+ return false;
15295
+ }
15296
+ if (!writable2(bound3)) return true;
15297
+ return stamp.covered.has(boundKey(bound3));
15275
15298
  }
15276
- stamp(owner, policy) {
15299
+ stamp(owner, policy, bound3) {
15277
15300
  const key = policy.minutes === 0 ? void 0 : timestampKey(owner, policy.type);
15278
15301
  if (key === void 0) return false;
15279
15302
  const now = this.now();
15280
15303
  const records = this.users.get(owner.uid) ?? /* @__PURE__ */ new Map();
15281
15304
  this.users.set(owner.uid, records);
15305
+ const prior = records.get(key);
15306
+ const covered = prior !== void 0 && live(prior, policy, now) ? prior.covered : /* @__PURE__ */ new Set();
15307
+ if (writable2(bound3)) covered.add(boundKey(bound3));
15282
15308
  records.delete(key);
15283
- records.set(key, now);
15284
- for (const [old, at] of records) {
15285
- const stale = policy.minutes > 0 && (at > now || now - at >= policy.minutes * MINUTE_MS2);
15309
+ records.set(key, { at: now, covered });
15310
+ for (const [old, stamp] of records) {
15311
+ const stale = policy.minutes > 0 && !live(stamp, policy, now);
15286
15312
  if (stale || records.size > TIMESTAMPS_MAX) records.delete(old);
15287
15313
  }
15288
15314
  return true;
@@ -15296,6 +15322,11 @@ var Timestamps = class {
15296
15322
  remove(uid) {
15297
15323
  this.users.delete(uid);
15298
15324
  }
15325
+ drop(uid, key) {
15326
+ const records = this.users.get(uid);
15327
+ records?.delete(key);
15328
+ if (records?.size === 0) this.users.delete(uid);
15329
+ }
15299
15330
  };
15300
15331
 
15301
15332
  // src/sudoers.ts
@@ -15304,8 +15335,10 @@ var SUDOERS_D = "/etc/sudoers.d";
15304
15335
  var NAME3 = /^[a-z_][a-z0-9_.-]*$/;
15305
15336
  var WORD = String.raw`(?:'[^']*'|"(?:[^"\\]|\\[\s\S])*"|\\[\s\S]|[^\s'"\\])+`;
15306
15337
  var KERNEL_FORM = new RegExp(
15307
- String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()]*)\)\s+)?(?:Cwd\s+(${WORD})\s+)?(?:Stdin\s+(\S+)\s+)?Cmnd(?:\s+(.*))?$`
15338
+ String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()]*)\)\s+)?(?:Cwd\s+(${WORD})\s+)?(?:Stdin\s+(\S+)\s+)?((?:Sha256\s+${WORD}\s+)*)Cmnd(?:\s+(.*))?$`
15308
15339
  );
15340
+ var SHA256_CLAUSE = new RegExp(String.raw`Sha256\s+(${WORD})`, "g");
15341
+ var HEX = /^[0-9a-f]{64}$/;
15309
15342
  var SUDOERS_FORM = /^(\S+)\s+([^\s=]+)\s*=\s*(.*)$/;
15310
15343
  var RUNAS = /^\(([^()]*)\)\s*/;
15311
15344
  var TAG = /^([A-Z_]+)\s*:\s*/;
@@ -15314,7 +15347,7 @@ var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
15314
15347
  var CONTROL2 = /[\p{Cc}\u2028\u2029]/u;
15315
15348
  var CONTROLS = /[\p{Cc}\u2028\u2029]/gu;
15316
15349
  var SHOWN_MAX = 60;
15317
- var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] Cmnd <command\u2026|*>";
15350
+ var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] [Sha256 <file>=<sha256>\u2026] Cmnd <command\u2026|*>";
15318
15351
  var SUDOERS_SYNTAX = "<user|%group|ALL> ALL=[(<target>[,<target>\u2026])] [NOPASSWD:] <command\u2026|ALL>";
15319
15352
  var EXPECTED = `expected '${KERNEL_SYNTAX}' or '${SUDOERS_SYNTAX}'`;
15320
15353
  var TIMESTAMP_TIMEOUT = 5;
@@ -15481,6 +15514,23 @@ function commandOf2(text, after) {
15481
15514
  if (only && /\s""$/.test(text)) return { path, args: [] };
15482
15515
  return args.length === 0 ? { path } : { path, args };
15483
15516
  }
15517
+ function sha256Of(clauses) {
15518
+ const files = {};
15519
+ for (const [, word = ""] of clauses.matchAll(SHA256_CLAUSE)) {
15520
+ const [text = ""] = splitWords(word) ?? [];
15521
+ const at = text.lastIndexOf("=");
15522
+ const path = text.slice(0, at);
15523
+ const hex4 = text.slice(at + 1).toLowerCase();
15524
+ if (!path.startsWith("/") || !HEX.test(hex4)) {
15525
+ return `Sha256 expects <absolute file>=<sha256 in hex>, not ${shown(word)}`;
15526
+ }
15527
+ files[normalizePath(path)] = hex4;
15528
+ }
15529
+ return files;
15530
+ }
15531
+ function sha256Text(files) {
15532
+ return Object.entries(files).map(([path, hex4]) => `Sha256 ${quoteWord(`${path}=${hex4}`)} `).join("");
15533
+ }
15484
15534
  function targetError(list) {
15485
15535
  if (list.includes(",")) {
15486
15536
  return `(${list}) names one user in this form: write one line per user, or use '${SUDOERS_SYNTAX}'`;
@@ -15488,19 +15538,22 @@ function targetError(list) {
15488
15538
  return `expected one user name or ALL in (\u2026), not ${shown(`(${list})`)}`;
15489
15539
  }
15490
15540
  function kernelRules(m) {
15491
- const [, nopasswd, who = "", target = "root", dir, stdin, rest2 = ""] = m;
15541
+ const [, nopasswd, who = "", target = "root", dir, stdin, sums = "", rest2 = ""] = m;
15492
15542
  const bad = whoError(who);
15493
15543
  if (bad !== void 0) return bad;
15494
15544
  if (target !== "ALL" && !NAME3.test(target)) return targetError(target);
15495
15545
  const cwd = dir === void 0 ? void 0 : cwdOf(dir);
15496
15546
  if (cwd === "") return `Cwd expects one absolute directory, not ${shown(dir)}`;
15497
15547
  if (stdin !== void 0 && stdin !== "none") return `Stdin takes none, not ${shown(stdin)}`;
15548
+ const sha2564 = sha256Of(sums);
15549
+ if (typeof sha2564 === "string") return sha2564;
15498
15550
  const rule = {
15499
15551
  nopasswd: !!nopasswd,
15500
15552
  who,
15501
15553
  target,
15502
15554
  ...cwd !== void 0 ? { cwd } : {},
15503
- ...stdin === "none" ? { stdin: "none" } : {}
15555
+ ...stdin === "none" ? { stdin: "none" } : {},
15556
+ ...sums === "" ? {} : { sha256: sha2564 }
15504
15557
  };
15505
15558
  const text = rest2.trim();
15506
15559
  if (text === "*") return [{ ...rule, cmnd: "*" }];
@@ -16130,12 +16183,26 @@ var RAN_ONCE = "sudo: ran once; this command line can't be recorded as always";
16130
16183
  var ROOT_ONLY_MAX = 64;
16131
16184
  var OPERANDS_MAX = 256;
16132
16185
  var PATH_MAX = 4096;
16186
+ function userWritable(bound3) {
16187
+ const files = Object.entries(bound3).map(([path, hex4]) => ({ path, sha256: hex4 }));
16188
+ return files.length > 0 ? { userWritable: files } : {};
16189
+ }
16133
16190
  function stdinMatches(rule, stdin) {
16134
16191
  return rule.stdin === void 0 || stdin === "tty" || stdin === "none";
16135
16192
  }
16136
16193
  function recordable(cwd, argv) {
16137
16194
  return !hasControl(cwd) && !argv.some(hasControl);
16138
16195
  }
16196
+ function changedText(file) {
16197
+ return `sudo: ${file} changed since it was approved`;
16198
+ }
16199
+ function changedFile(approved, now) {
16200
+ const files = /* @__PURE__ */ new Set([...Object.keys(approved), ...Object.keys(now)]);
16201
+ return [...files].find((file) => approved[file] !== now[file]);
16202
+ }
16203
+ function pinned(rule, bound3) {
16204
+ return rule.sha256 === void 0 || changedFile(rule.sha256, bound3) === void 0;
16205
+ }
16139
16206
  var VISUDO_FILES_MAX = 64;
16140
16207
  var VISUDO_TEXT_MAX = 1024 * 1024;
16141
16208
  function visudoCall(req) {
@@ -16266,11 +16333,13 @@ function usable(parsed2) {
16266
16333
  function policyOf(settings) {
16267
16334
  return { type: settings.timestampType, minutes: settings.timestampTimeout };
16268
16335
  }
16269
- function described(rule) {
16336
+ function described(rule, writable3) {
16270
16337
  const cmnd = rule.cmnd === "*" ? "ALL" : commandText(rule.cmnd);
16271
16338
  const cwd = rule.cwd === void 0 ? "" : `CWD=${quoteWord(rule.cwd)} `;
16272
16339
  const stdin = rule.stdin === void 0 ? "" : "STDIN=none ";
16273
- return ` (${rule.target}) ${cwd}${stdin}${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}`;
16340
+ const sums = Object.entries(rule.sha256 ?? {}).map(([path, hex4]) => `SHA256=${quoteWord(path)}:${hex4} `).join("");
16341
+ const flag = writable3 ? " # user-writable command" : "";
16342
+ return ` (${rule.target}) ${cwd}${stdin}${sums}${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}${flag}`;
16274
16343
  }
16275
16344
  var Sudo = class {
16276
16345
  o;
@@ -16461,15 +16530,31 @@ var Sudo = class {
16461
16530
  if (walked.denied !== void 0 || walked.missing) return false;
16462
16531
  return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
16463
16532
  }
16464
- async untrusted(path, cwd, word) {
16465
- const files = this.o.backing ? await this.o.backing(path, cwd) : [];
16466
- if (!files) return failed2("ENOENT", `sudo: ${word}: command not found`);
16467
- for (const file of [path, ...files]) {
16468
- if (!await this.trusted(file)) {
16469
- return failed2("EPERM", `sudo: ${file} must be root's, and writable by root only`);
16470
- }
16533
+ async real(file) {
16534
+ const { fs } = this.o;
16535
+ return await fs.realpath?.(file, true).catch(() => file) ?? file;
16536
+ }
16537
+ async digests(files) {
16538
+ const { fs } = this.o;
16539
+ const found = {};
16540
+ for (const file of new Set(files)) {
16541
+ if (await this.trusted(file)) continue;
16542
+ const real = await this.real(file);
16543
+ found[real] = await fs.readFileBuffer(real).then(sha2562, () => "");
16471
16544
  }
16472
- return void 0;
16545
+ return found;
16546
+ }
16547
+ async bound(path, cwd) {
16548
+ const files = this.o.backing ? await this.o.backing(path, cwd) : [];
16549
+ const bound3 = files && await this.digests([path, ...files]);
16550
+ return bound3 && !Object.values(bound3).includes("") ? bound3 : void 0;
16551
+ }
16552
+ async writable(rule) {
16553
+ if (rule.cmnd === "*") return false;
16554
+ const { path } = rule.cmnd;
16555
+ const bound3 = await this.bound(path, "/");
16556
+ if (bound3) return Object.keys(bound3).length > 0;
16557
+ return !await this.trusted(await this.existing(path));
16473
16558
  }
16474
16559
  async locate(word, cwd) {
16475
16560
  const { fs } = this.o;
@@ -16514,7 +16599,7 @@ var Sudo = class {
16514
16599
  const root = cred.euid === 0;
16515
16600
  const rules = root ? [] : policy.rules;
16516
16601
  const mine = rules.filter((rule) => applies(rule.who));
16517
- if (parsed2.list) return this.listed(self, root, mine, settings.matching);
16602
+ if (parsed2.list) return await this.listed(self, root, mine, settings.matching);
16518
16603
  const session = { caller, self, target, owner, policy: policyOf(settings), parsed: parsed2, signal };
16519
16604
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
16520
16605
  return this.command(session, root, mine, call);
@@ -16531,14 +16616,13 @@ var Sudo = class {
16531
16616
  );
16532
16617
  }
16533
16618
  const path = await this.locate(word, cwd);
16534
- if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
16535
- const untrusted2 = await this.untrusted(path, cwd, word);
16536
- if (untrusted2) return untrusted2;
16619
+ const bound3 = path && await this.bound(path, cwd);
16620
+ if (!path || !bound3) return failed2("ENOENT", `sudo: ${word}: command not found`);
16537
16621
  const argv = [path, ...args];
16538
16622
  const matching = mine.filter(
16539
- (rule) => (rule.target === "ALL" || rule.target === target.name) && (rule.cwd === void 0 || rule.cwd === cwd) && stdinMatches(rule, caller.stdin) && commandMatches(rule.cmnd, argv)
16623
+ (rule) => (rule.target === "ALL" || rule.target === target.name) && (rule.cwd === void 0 || rule.cwd === cwd) && stdinMatches(rule, caller.stdin) && commandMatches(rule.cmnd, argv) && pinned(rule, bound3)
16540
16624
  );
16541
- const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
16625
+ const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term, bound3);
16542
16626
  if (root || matching.some((rule) => rule.nopasswd)) return grant();
16543
16627
  const quoted = argv.map(quoteWord).join(" ");
16544
16628
  if (matching.length === 0) {
@@ -16547,22 +16631,23 @@ var Sudo = class {
16547
16631
  `sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
16548
16632
  );
16549
16633
  }
16550
- if (!parsed2.reset && this.timestamps.current(s.owner, s.policy)) {
16551
- this.timestamps.stamp(s.owner, s.policy);
16634
+ if (!parsed2.reset && this.timestamps.current(s.owner, s.policy, bound3)) {
16635
+ this.timestamps.stamp(s.owner, s.policy, bound3);
16552
16636
  this.logged(s, cwd, argv);
16553
16637
  return grant();
16554
16638
  }
16555
16639
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
16556
16640
  return this.pendingFor(cred.ruid, async () => {
16557
- const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd });
16641
+ const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd, bound: bound3 });
16558
16642
  if ("error" in answer2) return answer2;
16559
16643
  if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
16560
16644
  return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
16561
16645
  }
16562
- const changed = await this.untrusted(path, cwd, word);
16563
- if (changed) return changed;
16564
- const result = answer2.answer === "once" ? grant() : await this.always(self, target, cwd, argv, recordable(cwd, parsed2.command), grant);
16565
- if (!("error" in result)) this.stamped(s, answer2);
16646
+ const now = await this.bound(path, cwd);
16647
+ if (!now) return failed2("ENOENT", `sudo: ${word}: command not found`);
16648
+ const shown2 = recordable(cwd, [...parsed2.command, ...Object.keys(bound3)]);
16649
+ const result = answer2.answer === "once" ? grant() : await this.always(self, target, { cwd, argv, bound: bound3 }, now, shown2, grant);
16650
+ if (!("error" in result)) this.stamped(s, answer2, bound3);
16566
16651
  return result;
16567
16652
  });
16568
16653
  }
@@ -16580,7 +16665,7 @@ var Sudo = class {
16580
16665
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
16581
16666
  const cwd = await this.canonical(dir) ?? normalizePath(dir);
16582
16667
  return this.pendingFor(s.caller.cred.ruid, async () => {
16583
- const answer2 = await this.ask(s, { argv: [], path: "", cwd, validate: true });
16668
+ const answer2 = await this.ask(s, { argv: [], path: "", cwd, bound: {}, validate: true });
16584
16669
  if ("error" in answer2) return answer2;
16585
16670
  if (!answer2.person) await s.caller.report?.(NOT_STAMPED);
16586
16671
  this.stamped(s, answer2);
@@ -16596,8 +16681,8 @@ var Sudo = class {
16596
16681
  this.pending.delete(uid);
16597
16682
  }
16598
16683
  }
16599
- stamped(s, answer2) {
16600
- if (answer2.person && !s.parsed.reset) this.timestamps.stamp(s.owner, s.policy);
16684
+ stamped(s, answer2, bound3 = {}) {
16685
+ if (answer2.person && !s.parsed.reset) this.timestamps.stamp(s.owner, s.policy, bound3);
16601
16686
  }
16602
16687
  logged(s, cwd, argv) {
16603
16688
  const { caller, self, target, owner, policy } = s;
@@ -16608,14 +16693,17 @@ var Sudo = class {
16608
16693
  `sudo: ${self.name} : TTY=${tty} ; PWD=${quoteWord(cwd)} ; USER=${target.name} ; TIMESTAMP=${type} ; COMMAND=${command}`
16609
16694
  );
16610
16695
  }
16611
- async always(self, target, cwd, argv, shown2, grant) {
16696
+ async always(self, target, line, now, shown2, grant) {
16697
+ const { cwd, argv, bound: bound3 } = line;
16612
16698
  if (!shown2 || !recordable(cwd, argv)) return { ...grant(), notice: RAN_ONCE };
16613
- if (!await this.remember(self, target, cwd, argv)) {
16699
+ const changed = changedFile(bound3, now);
16700
+ if (changed !== void 0) return failed2("EPERM", changedText(changed));
16701
+ if (!await this.remember(self, target, line)) {
16614
16702
  return failed2("EIO", "sudo: the rule could not be recorded");
16615
16703
  }
16616
16704
  return grant();
16617
16705
  }
16618
- listed(self, root, rules, defaults2) {
16706
+ async listed(self, root, rules, defaults2) {
16619
16707
  const host = this.o.hostname;
16620
16708
  if (root) {
16621
16709
  return {
@@ -16627,7 +16715,9 @@ var Sudo = class {
16627
16715
  if (rules.length === 0) {
16628
16716
  return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
16629
16717
  }
16630
- const lines2 = rules.map(described).join("\n");
16718
+ const shown2 = [];
16719
+ for (const rule of rules) shown2.push(described(rule, await this.writable(rule)));
16720
+ const lines2 = shown2.join("\n");
16631
16721
  const params = defaults2.flatMap((d) => d.params).join(", ");
16632
16722
  const matching = params === "" ? "" : `Matching Defaults entries for ${self.name} on ${host}:
16633
16723
  ${params}
@@ -16660,9 +16750,10 @@ ${lines2}
16660
16750
  ...caller.tty ? { tty: caller.tty } : {},
16661
16751
  createdAt,
16662
16752
  expiresAt: createdAt + timeout,
16663
- always: asked.validate ? false : recordable(cwd, [path, ...argv]),
16753
+ always: asked.validate ? false : recordable(cwd, [path, ...argv, ...Object.keys(asked.bound)]),
16664
16754
  stdin: caller.stdin,
16665
16755
  rootOnly: asked.validate ? {} : await this.rootOnly(cwd, argv.slice(1)),
16756
+ ...userWritable(asked.bound),
16666
16757
  ...asked.validate ? { validate: true } : {},
16667
16758
  timestamp: true
16668
16759
  };
@@ -16682,7 +16773,7 @@ ${lines2}
16682
16773
  requester.removeEventListener("abort", withdraw);
16683
16774
  }
16684
16775
  }
16685
- async remember(self, target, cwd, argv) {
16776
+ async remember(self, target, { cwd, argv, bound: bound3 }) {
16686
16777
  const { fs } = this.o;
16687
16778
  const path = `${SUDOERS_D}/${self.name}`;
16688
16779
  if (!NAME3.test(self.name)) return false;
@@ -16693,7 +16784,7 @@ ${lines2}
16693
16784
  if (st && !await this.trusted(path)) return false;
16694
16785
  const old = st ? await fs.readFile(path) : "";
16695
16786
  const words2 = recordedCommand(argv);
16696
- const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none Cmnd ${words2}
16787
+ const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words2}
16697
16788
  `;
16698
16789
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
16699
16790
  ${line}`;
@@ -16704,7 +16795,7 @@ ${line}`;
16704
16795
  return false;
16705
16796
  }
16706
16797
  }
16707
- grant(self, target, cred, argv, file, cwd, term) {
16798
+ grant(self, target, cred, argv, file, cwd, term, files) {
16708
16799
  const env = {
16709
16800
  ...this.o.env(),
16710
16801
  ...userEnv(target, this.o.pnpmHome),
@@ -16717,7 +16808,9 @@ ${line}`;
16717
16808
  SUDO_GID: String(cred.rgid),
16718
16809
  SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
16719
16810
  };
16720
- return { grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred } };
16811
+ return {
16812
+ grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred, files }
16813
+ };
16721
16814
  }
16722
16815
  };
16723
16816
 
@@ -22789,18 +22882,18 @@ var VfsNodes = class {
22789
22882
  if (id2 !== void 0) this.know(node, id2);
22790
22883
  return node;
22791
22884
  }
22792
- open(path, pin, writable2 = false, id2, ino) {
22885
+ open(path, pin, writable3 = false, id2, ino) {
22793
22886
  const found = this.named(path, id2);
22794
22887
  const same2 = !found || ino === void 0 || found.ino === void 0 || found.ino === ino;
22795
22888
  if (found && !same2) this.orphan(found);
22796
22889
  const node = (same2 ? found : void 0) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
22797
22890
  node.ino ??= ino;
22798
22891
  node.opens++;
22799
- if (writable2) node.writers++;
22892
+ if (writable3) node.writers++;
22800
22893
  if (node.opens === 1) this.opened?.(node);
22801
22894
  return node;
22802
22895
  }
22803
- adopt(path, contents2, dirty, writable2 = false, id2) {
22896
+ adopt(path, contents2, dirty, writable3 = false, id2) {
22804
22897
  let node = this.named(path, id2);
22805
22898
  if (!node) {
22806
22899
  node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
@@ -22808,7 +22901,7 @@ var VfsNodes = class {
22808
22901
  node.replace(contents2);
22809
22902
  }
22810
22903
  node.opens++;
22811
- if (writable2) node.writers++;
22904
+ if (writable3) node.writers++;
22812
22905
  if (node.opens === 1) this.opened?.(node);
22813
22906
  return node;
22814
22907
  }
@@ -22821,11 +22914,11 @@ var VfsNodes = class {
22821
22914
  if (node.writers > 0 && within7(path, prefix)) return true;
22822
22915
  return false;
22823
22916
  }
22824
- closed(node, writable2 = false) {
22917
+ closed(node, writable3 = false) {
22825
22918
  node.opens--;
22826
- if (writable2) node.writers--;
22919
+ if (writable3) node.writers--;
22827
22920
  if (node.opens > 0) {
22828
- if (writable2 && node.writers === 0) this.onClosed?.(node.path);
22921
+ if (writable3 && node.writers === 0) this.onClosed?.(node.path);
22829
22922
  return;
22830
22923
  }
22831
22924
  this.forget(node);
@@ -23144,8 +23237,8 @@ function keepingOpen(fs, nodes, changed = () => {
23144
23237
  function vfsFile(fs, opts, nodes) {
23145
23238
  const access2 = opts.flags & O_ACCMODE;
23146
23239
  const readable2 = access2 !== O_WRONLY;
23147
- const writable2 = access2 === O_WRONLY || access2 === O_RDWR;
23148
- const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable2, opts.id) : nodes.open(opts.path, opts.pin, writable2, opts.id, opts.ino);
23240
+ const writable3 = access2 === O_WRONLY || access2 === O_RDWR;
23241
+ const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable3, opts.id) : nodes.open(opts.path, opts.pin, writable3, opts.id, opts.ino);
23149
23242
  let openError;
23150
23243
  const atOpen = (op) => {
23151
23244
  void node.serial(op).catch((err) => {
@@ -23172,7 +23265,7 @@ function vfsFile(fs, opts, nodes) {
23172
23265
  offset += out.length;
23173
23266
  return out;
23174
23267
  }) : void 0,
23175
- write: writable2 ? (bytes) => serial(async () => {
23268
+ write: writable3 ? (bytes) => serial(async () => {
23176
23269
  if (opts.flags & O_APPEND) offset = await node.append(bytes);
23177
23270
  else offset += await node.pwrite(bytes, offset);
23178
23271
  return bytes.length;
@@ -23191,11 +23284,11 @@ function vfsFile(fs, opts, nodes) {
23191
23284
  return node.pread(max, at);
23192
23285
  }),
23193
23286
  pwrite: (bytes, at) => serial(() => {
23194
- if (!writable2) throw new KernelError("EBADF");
23287
+ if (!writable3) throw new KernelError("EBADF");
23195
23288
  return node.pwrite(bytes, at);
23196
23289
  }),
23197
23290
  resize: (size) => serial(() => {
23198
- if (!writable2) throw new KernelError("EBADF");
23291
+ if (!writable3) throw new KernelError("EBADF");
23199
23292
  return node.truncate(size);
23200
23293
  }),
23201
23294
  stat: () => serial(async () => ({
@@ -23211,7 +23304,7 @@ function vfsFile(fs, opts, nodes) {
23211
23304
  takeOpenError();
23212
23305
  await node.flush();
23213
23306
  } finally {
23214
- nodes?.closed(node, writable2);
23307
+ nodes?.closed(node, writable3);
23215
23308
  }
23216
23309
  })
23217
23310
  });
@@ -24887,14 +24980,20 @@ var WasmProcess = class {
24887
24980
  if (!("grant" in answer2)) return answer2;
24888
24981
  if (this.dying) throw new KernelError("EINTR");
24889
24982
  if (answer2.notice !== void 0) await this.complainLine(answer2.notice);
24890
- const { file, argv, env, cwd, cred, caller } = answer2.grant;
24983
+ const { file, argv, env, cwd, cred, caller, files } = answer2.grant;
24891
24984
  const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
24892
- return {
24893
- pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
24894
- cred,
24895
- caller
24896
- })
24897
- };
24985
+ try {
24986
+ return {
24987
+ pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
24988
+ cred,
24989
+ caller,
24990
+ files
24991
+ })
24992
+ };
24993
+ } catch (err) {
24994
+ if (err instanceof ChangedError) return { error: err.notice, errno: err.code };
24995
+ throw err;
24996
+ }
24898
24997
  }
24899
24998
  stdinKind() {
24900
24999
  if (!this.fds.has(0)) return "none";
@@ -24934,8 +25033,8 @@ var WasmProcess = class {
24934
25033
  };
24935
25034
  withDl(state) {
24936
25035
  if (this.dlLog.length === 0) return state;
24937
- const { next: next3, handle, free, live } = this.closures;
24938
- const dl = { log: [...this.dlLog], next: next3, handle, free: [...free], live: [...live] };
25036
+ const { next: next3, handle, free, live: live2 } = this.closures;
25037
+ const dl = { log: [...this.dlLog], next: next3, handle, free: [...free], live: [...live2] };
24939
25038
  return { ...state, dl };
24940
25039
  }
24941
25040
  seedDl(dl) {
@@ -26153,8 +26252,8 @@ var RemoteWatch = class {
26153
26252
  const { held = [], pending = [] } = await this.locks.query();
26154
26253
  if (![...held, ...pending].some((lock) => lock.name === name2)) {
26155
26254
  this.mourned.delete(peer);
26156
- const live = this.peers.has(peer) || this.flushes.has(peer);
26157
- if (tries > 1 && live) {
26255
+ const live2 = this.peers.has(peer) || this.flushes.has(peer);
26256
+ if (tries > 1 && live2) {
26158
26257
  setTimeout(() => void this.mourn(peer, tries - 1), MOURN_RETRY_MS);
26159
26258
  } else this.flushedAt(peer);
26160
26259
  return;
@@ -28362,11 +28461,11 @@ async function positioned(handle, op) {
28362
28461
  }
28363
28462
  return;
28364
28463
  }
28365
- const writable2 = await handle.createWritable({ keepExistingData: true });
28464
+ const writable3 = await handle.createWritable({ keepExistingData: true });
28366
28465
  try {
28367
28466
  await op({
28368
28467
  write: async (at, bytes) => {
28369
- await writable2.write({
28468
+ await writable3.write({
28370
28469
  type: "write",
28371
28470
  position: at,
28372
28471
  data: bytes
@@ -28374,15 +28473,15 @@ async function positioned(handle, op) {
28374
28473
  extent.wrote(at + bytes.length);
28375
28474
  },
28376
28475
  truncate: async (n) => {
28377
- await writable2.truncate(n);
28476
+ await writable3.truncate(n);
28378
28477
  extent.cut(n);
28379
28478
  }
28380
28479
  });
28381
28480
  } catch (err) {
28382
- await writable2.abort();
28481
+ await writable3.abort();
28383
28482
  throw err;
28384
28483
  }
28385
- await writable2.close();
28484
+ await writable3.close();
28386
28485
  if (extent.big()) extent.check((await handle.getFile()).size);
28387
28486
  }
28388
28487
  function isHandle(child) {
@@ -29464,21 +29563,21 @@ var OpfsFs = class {
29464
29563
  }
29465
29564
  async writeSession(path, truncate) {
29466
29565
  const [handle, , target] = await this.fileFor(path);
29467
- const writable2 = await this.retrying(
29566
+ const writable3 = await this.retrying(
29468
29567
  path,
29469
29568
  () => this.io(handle.createWritable({ keepExistingData: !truncate })),
29470
29569
  true
29471
29570
  );
29472
29571
  return {
29473
- write: (at, bytes) => writable2.write({ type: "write", position: at, data: bytes }).catch((err) => {
29572
+ write: (at, bytes) => writable3.write({ type: "write", position: at, data: bytes }).catch((err) => {
29474
29573
  throw translate(err, path, "write", false);
29475
29574
  }),
29476
- truncate: (size) => writable2.truncate(size).catch((err) => {
29575
+ truncate: (size) => writable3.truncate(size).catch((err) => {
29477
29576
  throw translate(err, path, "write", false);
29478
29577
  }),
29479
29578
  close: async () => {
29480
29579
  this.wrote(handle);
29481
- await writable2.close().catch((err) => {
29580
+ await writable3.close().catch((err) => {
29482
29581
  this.wrote(handle);
29483
29582
  throw translate(err, path, "commit", false);
29484
29583
  });
@@ -29530,19 +29629,19 @@ var OpfsFs = class {
29530
29629
  const [file, existed, target] = await this.fileFor(path, owner?.existing !== true);
29531
29630
  if (!existed) await this.stampNew(target, owner, false);
29532
29631
  if (!existed && content.length === 0) return;
29533
- let writable2;
29632
+ let writable3;
29534
29633
  this.wrote(file);
29535
29634
  try {
29536
- writable2 = await this.io(file.createWritable());
29537
- await writable2.write(content);
29538
- await writable2.close();
29635
+ writable3 = await this.io(file.createWritable());
29636
+ await writable3.write(content);
29637
+ await writable3.close();
29539
29638
  if (typeof content !== "string" && content.length >= CHECKED_BYTES) {
29540
29639
  const extent = new Extent();
29541
29640
  extent.cut(content.length);
29542
29641
  extent.check((await file.getFile()).size);
29543
29642
  }
29544
29643
  } catch (err) {
29545
- await writable2?.abort().catch(() => void 0);
29644
+ await writable3?.abort().catch(() => void 0);
29546
29645
  throw translate(err, path, "commit", false);
29547
29646
  } finally {
29548
29647
  this.wrote(file);
@@ -30267,7 +30366,7 @@ var OpfsFs = class {
30267
30366
  created4 = true;
30268
30367
  }
30269
30368
  let target = destination;
30270
- let writable2;
30369
+ let writable3;
30271
30370
  let handle;
30272
30371
  try {
30273
30372
  const kept = created4 ? this.files.get(destination) : void 0;
@@ -30286,11 +30385,11 @@ var OpfsFs = class {
30286
30385
  if (entry?.fifo || entry?.socket) throw fsError("ENXIO", to);
30287
30386
  }
30288
30387
  this.wrote(handle);
30289
- writable2 = await handle.createWritable();
30290
- await writable2.write(file);
30291
- await writable2.close();
30388
+ writable3 = await handle.createWritable();
30389
+ await writable3.write(file);
30390
+ await writable3.close();
30292
30391
  } catch (err) {
30293
- await writable2?.abort().catch(() => void 0);
30392
+ await writable3?.abort().catch(() => void 0);
30294
30393
  if (created4) await this.rm(target, { file: true }).catch(() => void 0);
30295
30394
  throw translate(err, to, "commit", false);
30296
30395
  } finally {
@@ -30379,10 +30478,10 @@ var OpfsFs = class {
30379
30478
  }
30380
30479
  if (handle.kind === "file") {
30381
30480
  const made2 = await this.io(dir.getFileHandle(name2, { create: true }));
30382
- const writable2 = await this.io(made2.createWritable());
30481
+ const writable3 = await this.io(made2.createWritable());
30383
30482
  const file = await this.io(handle.getFile());
30384
- await writable2.write(file);
30385
- await writable2.close();
30483
+ await writable3.write(file);
30484
+ await writable3.close();
30386
30485
  copies?.set(rest2, { before: copies.get(rest2)?.before ?? file.lastModified, made: made2 });
30387
30486
  return;
30388
30487
  }
@@ -31094,7 +31193,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
31094
31193
  if (remote.client.token === remote.token && !remote.client.gone) return remote;
31095
31194
  throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
31096
31195
  };
31097
- const live = (fh) => {
31196
+ const live2 = (fh) => {
31098
31197
  const remote = handles.get(fh);
31099
31198
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
31100
31199
  return fresh3(remote);
@@ -31113,7 +31212,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
31113
31212
  }, againMs);
31114
31213
  timer.unref?.();
31115
31214
  });
31116
- async function writable2(client, path, flags2) {
31215
+ async function writable3(client, path, flags2) {
31117
31216
  const { ifMatch: _, ...asked } = flags2;
31118
31217
  const opening2 = () => client.call("open", { path: rel(path), ...asked });
31119
31218
  try {
@@ -31144,7 +31243,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
31144
31243
  async open(path, flags2) {
31145
31244
  const client = current();
31146
31245
  const token = client.token;
31147
- const { opened: opened2, restore } = await writable2(client, path, flags2);
31246
+ const { opened: opened2, restore } = await writable3(client, path, flags2);
31148
31247
  if (flags2.ifMatch !== void 0 && opened2.attr?.etag !== flags2.ifMatch) {
31149
31248
  await client.call("release", { fh: opened2.fh }, true);
31150
31249
  throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
@@ -31159,7 +31258,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
31159
31258
  return nextFh;
31160
31259
  },
31161
31260
  async read(fh, offset, size) {
31162
- const remote = live(fh);
31261
+ const remote = live2(fh);
31163
31262
  return remote.client.read(
31164
31263
  remote.fh,
31165
31264
  offset,
@@ -31168,7 +31267,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
31168
31267
  );
31169
31268
  },
31170
31269
  async write(fh, offset, bytes) {
31171
- const remote = live(fh);
31270
+ const remote = live2(fh);
31172
31271
  const most = remote.client.maxIo;
31173
31272
  for (let at = 0; at < bytes.length; at += most) {
31174
31273
  await remote.client.write(remote.fh, offset + at, bytes.subarray(at, at + most));
@@ -32466,6 +32565,10 @@ function describe(req, tty) {
32466
32565
  };
32467
32566
  return { argv: [req.argv0, ...req.args], tty, started: Date.now(), ...exe };
32468
32567
  }
32568
+ function refuseChanged(approved, now) {
32569
+ const changed = changedFile(approved, now);
32570
+ if (changed !== void 0) throw new ChangedError(changedText(changed));
32571
+ }
32469
32572
  function linksOf2(req) {
32470
32573
  return req.links === void 0 ? {} : { links: req.links };
32471
32574
  }
@@ -33599,6 +33702,27 @@ var Launcher = class {
33599
33702
  }
33600
33703
  return true;
33601
33704
  }
33705
+ async unchanged(planned, file, cwd, grant) {
33706
+ const approved = grant?.files;
33707
+ if (!approved) return {};
33708
+ const files = await this.backingOf(planned, file, cwd, "");
33709
+ refuseChanged(approved, await this.sudoer.digests([file, ...files]));
33710
+ return { pinned: approved };
33711
+ }
33712
+ async pinOf(path, caller) {
33713
+ const { pinned: pinned2 } = caller;
33714
+ if (!pinned2) return {};
33715
+ const real = await this.sudoer.real(path);
33716
+ const hex4 = pinned2[real];
33717
+ if (hex4 !== void 0) return { [real]: hex4 };
33718
+ if (await this.sudoer.trusted(real)) return {};
33719
+ throw new ChangedError(changedText(real));
33720
+ }
33721
+ async pinnedBytes(pin, bytes) {
33722
+ const [real = ""] = Object.keys(pin);
33723
+ refuseChanged(pin, { [real]: await sha2562(bytes) });
33724
+ return bytes;
33725
+ }
33602
33726
  async grantedCwd(caller, cwd) {
33603
33727
  const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
33604
33728
  return real === cwd && await this.sudoer.reachable(caller, cwd);
@@ -33687,13 +33811,19 @@ var Launcher = class {
33687
33811
  return found;
33688
33812
  }
33689
33813
  async module(path, caller, asked) {
33690
- const key = await this.cacheKey(path, asked);
33814
+ const pin = await this.pinOf(path, caller);
33815
+ const sum = Object.values(pin)[0];
33816
+ const key = sum === void 0 ? await this.cacheKey(path, asked) : `${path}:sha256:${sum}`;
33691
33817
  return this.cached(this.compiled, key, async () => {
33692
- const { bytes, key: read } = await this.keyed(path, caller, asked);
33818
+ const { bytes: read, key: readKey } = await this.keyed(path, caller, asked);
33819
+ const bytes = sum === void 0 ? read : await this.pinnedBytes(pin, read);
33693
33820
  const memory = importedMemory(bytes);
33694
33821
  const foreign = foreignImports(bytes);
33695
33822
  const compiled = await WebAssembly.compile(bytes);
33696
- return { value: { module: compiled, ...memory ? { memory } : {}, foreign }, key: read };
33823
+ return {
33824
+ value: { module: compiled, ...memory ? { memory } : {}, foreign },
33825
+ key: sum === void 0 ? readKey : key
33826
+ };
33697
33827
  });
33698
33828
  }
33699
33829
  async checkedSources(planned, caller) {
@@ -33718,6 +33848,11 @@ var Launcher = class {
33718
33848
  }
33719
33849
  }
33720
33850
  async text(path, caller, asked) {
33851
+ const pin = await this.pinOf(path, caller);
33852
+ if (Object.keys(pin).length > 0) {
33853
+ const { bytes } = await this.keyed(path, caller, asked);
33854
+ return new TextDecoder().decode(await this.pinnedBytes(pin, bytes));
33855
+ }
33721
33856
  if (!this.integrity) return this.base.readFile(path);
33722
33857
  const key = await this.cacheKey(path, asked);
33723
33858
  return this.cached(this.texts, key, async () => {
@@ -33906,7 +34041,12 @@ var Launcher = class {
33906
34041
  const own = env.HOSTNAME;
33907
34042
  if (asked.target.abi === "wasi") env.HOSTNAME = this.hostname;
33908
34043
  const planned = chosen2(asked, env);
33909
- const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tags?.[0] };
34044
+ const caller = {
34045
+ pid: req.ppid ?? 0,
34046
+ uid: req.cred.euid,
34047
+ tag: req.tags?.[0],
34048
+ pinned: req.pinned
34049
+ };
33910
34050
  if (this.integrity) await this.checkedSources(planned, caller);
33911
34051
  const [program, exeId] = await this.loaded(planned.target, env, caller);
33912
34052
  if (readsEtcHostname(program)) setOrDrop(env, "HOSTNAME", own);
@@ -34421,6 +34561,7 @@ var Launcher = class {
34421
34561
  const scope = grant ? "" : await this.scopeOf(cred.euid);
34422
34562
  const file = booked === void 0 ? req.file : await this.ticketFile(req, fds);
34423
34563
  const planned = await this.checkedPlan({ ...req, file }, { ppid, cred, scope }, fds);
34564
+ const pin = await this.unchanged(planned, file, req.cwd, grant);
34424
34565
  const { recheck, ...redeemed } = await this.redeemed(
34425
34566
  { ...req, file },
34426
34567
  { ppid, cred, scope, booked },
@@ -34432,6 +34573,7 @@ var Launcher = class {
34432
34573
  const replan = () => this.planOrSearch({ ...req, file }, { ppid, cred, scope });
34433
34574
  const again = { ...planned, replan, recheck };
34434
34575
  const handle = await this.launch(again, {
34576
+ ...pin,
34435
34577
  ...deciding ? { decided: deciding.promise } : {},
34436
34578
  cwd: req.cwd,
34437
34579
  cwdFrom: inheritedCwd(req.chdir, this.cwdSlots.get(ppid)),
@@ -37132,7 +37274,7 @@ function dialStream(port, onClose) {
37132
37274
  else finish(errorWithCode(data.error));
37133
37275
  });
37134
37276
  port.start?.();
37135
- const writable2 = new WritableStream({
37277
+ const writable3 = new WritableStream({
37136
37278
  write(chunk2) {
37137
37279
  if (closed) throw errorWithCode("EPIPE: the connection is closed");
37138
37280
  const copy = fresh(chunk2);
@@ -37151,7 +37293,7 @@ function dialStream(port, onClose) {
37151
37293
  shut();
37152
37294
  finish(error);
37153
37295
  };
37154
- return { readable: readable2, writable: writable2, close, fail: fail3 };
37296
+ return { readable: readable2, writable: writable3, close, fail: fail3 };
37155
37297
  }
37156
37298
 
37157
37299
  // src/client/loopback-fetch.ts
@@ -37577,10 +37719,10 @@ function endedOf(done4, signal) {
37577
37719
  async function runOn(channel, argv, opts) {
37578
37720
  const { onStdout, onStderr, ...rest2 } = opts;
37579
37721
  const chunks2 = { 1: [], 2: [] };
37580
- const live = { 1: new TextDecoder(), 2: new TextDecoder() };
37722
+ const live2 = { 1: new TextDecoder(), 2: new TextDecoder() };
37581
37723
  const tee = (fd, on) => (bytes) => {
37582
37724
  chunks2[fd].push(bytes);
37583
- on?.(live[fd].decode(bytes, { stream: true }));
37725
+ on?.(live2[fd].decode(bytes, { stream: true }));
37584
37726
  };
37585
37727
  const child = await spawnOn(channel, argv, {
37586
37728
  ...rest2,
@@ -37795,7 +37937,7 @@ var TERMINATED = "the kernel is terminated";
37795
37937
  var EXIT_GRACE_MS = 50;
37796
37938
  var WARM_IDLE_MS = 3e4;
37797
37939
  var MAX_WARM_WORKERS = 64;
37798
- function warmPool(make, live, idleMs = WARM_IDLE_MS, size = 1) {
37940
+ function warmPool(make, live2, idleMs = WARM_IDLE_MS, size = 1) {
37799
37941
  const spares = [];
37800
37942
  let idle2;
37801
37943
  let closed = false;
@@ -37805,7 +37947,7 @@ function warmPool(make, live, idleMs = WARM_IDLE_MS, size = 1) {
37805
37947
  spares.splice(at, 1);
37806
37948
  if (spares.length > 0) return;
37807
37949
  clearTimeout(idle2);
37808
- live.delete(kill);
37950
+ live2.delete(kill);
37809
37951
  };
37810
37952
  const kill = () => {
37811
37953
  for (const worker of [...spares]) {
@@ -37826,7 +37968,7 @@ function warmPool(make, live, idleMs = WARM_IDLE_MS, size = 1) {
37826
37968
  worker.once("error", () => drop(worker));
37827
37969
  worker.once("exit", () => drop(worker));
37828
37970
  spares.push(worker);
37829
- live.add(kill);
37971
+ live2.add(kill);
37830
37972
  }
37831
37973
  clearTimeout(idle2);
37832
37974
  idle2 = setTimeout(kill, idleMs);
@@ -37899,12 +38041,12 @@ function sourceModule(source) {
37899
38041
  return { url, source, revoke: () => {
37900
38042
  } };
37901
38043
  }
37902
- function warmPoolOf(options, file, workerData, live) {
38044
+ function warmPoolOf(options, file, workerData, live2) {
37903
38045
  const size = sparesOf(options);
37904
38046
  if (size === 0) return void 0;
37905
- return warmPool(() => processWorker(file, workerData, true), live, WARM_IDLE_MS, size);
38047
+ return warmPool(() => processWorker(file, workerData, true), live2, WARM_IDLE_MS, size);
37906
38048
  }
37907
- function nodeWorker(file, live, closed, workerData, take, internals = false) {
38049
+ function nodeWorker(file, live2, closed, workerData, take, internals = false) {
37908
38050
  const errors = /* @__PURE__ */ new Set();
37909
38051
  const messages = /* @__PURE__ */ new Map();
37910
38052
  const fail3 = (message3) => {
@@ -37921,9 +38063,9 @@ function nodeWorker(file, live, closed, workerData, take, internals = false) {
37921
38063
  void worker.terminate();
37922
38064
  fail3(TERMINATED);
37923
38065
  };
37924
- live.add(kill);
38066
+ live2.add(kill);
37925
38067
  worker.once("exit", (code) => {
37926
- live.delete(kill);
38068
+ live2.delete(kill);
37927
38069
  setTimeout(() => {
37928
38070
  if (!ending) fail3(`the process worker exited with ${code}`);
37929
38071
  }, EXIT_GRACE_MS);
@@ -38080,23 +38222,23 @@ async function createNodeKernel(options = {}) {
38080
38222
  const root = options.root ?? memoryRoot();
38081
38223
  const fs = new OpfsFs(root, void 0, void 0, { ranged: isMemoryRoot(root) });
38082
38224
  await fs.reconcile();
38083
- const live = /* @__PURE__ */ new Set();
38225
+ const live2 = /* @__PURE__ */ new Set();
38084
38226
  const pending = /* @__PURE__ */ new Set();
38085
38227
  const clients = /* @__PURE__ */ new Set();
38086
38228
  let terminated = false;
38087
38229
  const file = options.worker ?? new URL("./node-process-worker.cjs", import.meta.url);
38088
38230
  const cache = cacheDirOf(options.compileCache);
38089
38231
  const workerData = cacheData(cache);
38090
- const warm = warmPoolOf(options, file, workerData, live);
38232
+ const warm = warmPoolOf(options, file, workerData, live2);
38091
38233
  const transport = options.network?.transport;
38092
38234
  const launcher = new Launcher({
38093
38235
  fs,
38094
38236
  moduleUrl: sourceModule,
38095
38237
  unlinkOwner: pidOwner(process),
38096
- createWorker: () => nodeWorker(file, live, terminated, workerData, warm ? () => warm.take() : void 0, true),
38238
+ createWorker: () => nodeWorker(file, live2, terminated, workerData, warm ? () => warm.take() : void 0, true),
38097
38239
  createDriverWorker: () => nodeWorker(
38098
38240
  options.driverWorker ?? new URL("./node-driver-worker.js", import.meta.url),
38099
- live,
38241
+ live2,
38100
38242
  terminated
38101
38243
  ),
38102
38244
  ...options.modules ? { modules: options.modules } : {},
@@ -38227,7 +38369,7 @@ async function createNodeKernel(options = {}) {
38227
38369
  warm?.close();
38228
38370
  for (const client of [...clients]) client.detach();
38229
38371
  for (const reject of [...pending]) reject(new Error(TERMINATED));
38230
- for (const kill of [...live]) kill();
38372
+ for (const kill of [...live2]) kill();
38231
38373
  launcher.cdp.close();
38232
38374
  launcher.endFills();
38233
38375
  launcher.unmountAll();