@ai-ecoverse/slicc-kernel 1.178.1 → 1.178.2

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.
@@ -8777,7 +8777,7 @@ function satisfies(version, range2) {
8777
8777
  }
8778
8778
 
8779
8779
  // src/version.ts
8780
- var KERNEL_VERSION = true ? "1.178.1" : "0.0.0-dev";
8780
+ var KERNEL_VERSION = true ? "1.178.2" : "0.0.0-dev";
8781
8781
  var OS_RELEASE = "6.0.0-slicc";
8782
8782
  function procVersion() {
8783
8783
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -10059,9 +10059,9 @@ var UnlinkedKeeper = class {
10059
10059
  if (kept.whole) return kept.undoing(async () => void 0);
10060
10060
  await kept.undoing(
10061
10061
  () => replace(target, async (gated, sealed) => {
10062
- const pinned = await keeper.pin(target, gated, sealed);
10063
- if (pinned.moved.has(target)) pinned.commit();
10064
- else await keeper.settle(pinned, () => fs.rm(path, options));
10062
+ const pinned2 = await keeper.pin(target, gated, sealed);
10063
+ if (pinned2.moved.has(target)) pinned2.commit();
10064
+ else await keeper.settle(pinned2, () => fs.rm(path, options));
10065
10065
  })
10066
10066
  );
10067
10067
  },
@@ -10074,8 +10074,8 @@ var UnlinkedKeeper = class {
10074
10074
  () => keeper.marks.around(
10075
10075
  [source],
10076
10076
  () => replace(target, async (gated, sealed) => {
10077
- const pinned = await keeper.pin(target, gated, sealed);
10078
- await keeper.settle(pinned, () => fs.rename(from, to));
10077
+ const pinned2 = await keeper.pin(target, gated, sealed);
10078
+ await keeper.settle(pinned2, () => fs.rename(from, to));
10079
10079
  })
10080
10080
  )
10081
10081
  );
@@ -10110,14 +10110,14 @@ var UnlinkedKeeper = class {
10110
10110
  }
10111
10111
  return false;
10112
10112
  }
10113
- async settle(pinned, op) {
10113
+ async settle(pinned2, op) {
10114
10114
  try {
10115
10115
  await op();
10116
10116
  } catch (err) {
10117
- await pinned.rollback();
10117
+ await pinned2.rollback();
10118
10118
  throw err;
10119
10119
  }
10120
- pinned.commit();
10120
+ pinned2.commit();
10121
10121
  }
10122
10122
  heldWithin(target) {
10123
10123
  const held = /* @__PURE__ */ new Map();
@@ -12641,6 +12641,13 @@ var SpawnError = class extends Error {
12641
12641
  this.code = code;
12642
12642
  }
12643
12643
  };
12644
+ var ChangedError = class extends SpawnError {
12645
+ notice;
12646
+ constructor(notice) {
12647
+ super("EPERM");
12648
+ this.notice = notice;
12649
+ }
12650
+ };
12644
12651
  function interrupted(signal) {
12645
12652
  const { promise, reject } = Promise.withResolvers();
12646
12653
  const fail2 = () => reject(new KernelError("EINTR"));
@@ -19620,8 +19627,10 @@ var SUDOERS_D = "/etc/sudoers.d";
19620
19627
  var NAME4 = /^[a-z_][a-z0-9_.-]*$/;
19621
19628
  var WORD = String.raw`(?:'[^']*'|"(?:[^"\\]|\\[\s\S])*"|\\[\s\S]|[^\s'"\\])+`;
19622
19629
  var KERNEL_FORM = new RegExp(
19623
- String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()]*)\)\s+)?(?:Cwd\s+(${WORD})\s+)?(?:Stdin\s+(\S+)\s+)?Cmnd(?:\s+(.*))?$`
19630
+ String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()]*)\)\s+)?(?:Cwd\s+(${WORD})\s+)?(?:Stdin\s+(\S+)\s+)?((?:Sha256\s+${WORD}\s+)*)Cmnd(?:\s+(.*))?$`
19624
19631
  );
19632
+ var SHA256_CLAUSE = new RegExp(String.raw`Sha256\s+(${WORD})`, "g");
19633
+ var HEX = /^[0-9a-f]{64}$/;
19625
19634
  var SUDOERS_FORM = /^(\S+)\s+([^\s=]+)\s*=\s*(.*)$/;
19626
19635
  var RUNAS = /^\(([^()]*)\)\s*/;
19627
19636
  var TAG = /^([A-Z_]+)\s*:\s*/;
@@ -19630,7 +19639,7 @@ var PLAIN2 = /^[A-Za-z0-9_@%+=:,./-]+$/;
19630
19639
  var CONTROL2 = /[\p{Cc}\u2028\u2029]/u;
19631
19640
  var CONTROLS = /[\p{Cc}\u2028\u2029]/gu;
19632
19641
  var SHOWN_MAX = 60;
19633
- var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] Cmnd <command\u2026|*>";
19642
+ var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] [Sha256 <file>=<sha256>\u2026] Cmnd <command\u2026|*>";
19634
19643
  var SUDOERS_SYNTAX = "<user|%group|ALL> ALL=[(<target>[,<target>\u2026])] [NOPASSWD:] <command\u2026|ALL>";
19635
19644
  var EXPECTED = `expected '${KERNEL_SYNTAX}' or '${SUDOERS_SYNTAX}'`;
19636
19645
  var TIMESTAMP_TIMEOUT = 5;
@@ -19797,6 +19806,23 @@ function commandOf2(text, after) {
19797
19806
  if (only && /\s""$/.test(text)) return { path, args: [] };
19798
19807
  return args.length === 0 ? { path } : { path, args };
19799
19808
  }
19809
+ function sha256Of(clauses) {
19810
+ const files = {};
19811
+ for (const [, word = ""] of clauses.matchAll(SHA256_CLAUSE)) {
19812
+ const [text = ""] = splitWords(word) ?? [];
19813
+ const at = text.lastIndexOf("=");
19814
+ const path = text.slice(0, at);
19815
+ const hex4 = text.slice(at + 1).toLowerCase();
19816
+ if (!path.startsWith("/") || !HEX.test(hex4)) {
19817
+ return `Sha256 expects <absolute file>=<sha256 in hex>, not ${shown(word)}`;
19818
+ }
19819
+ files[normalizePath(path)] = hex4;
19820
+ }
19821
+ return files;
19822
+ }
19823
+ function sha256Text(files) {
19824
+ return Object.entries(files).map(([path, hex4]) => `Sha256 ${quoteWord(`${path}=${hex4}`)} `).join("");
19825
+ }
19800
19826
  function targetError(list) {
19801
19827
  if (list.includes(",")) {
19802
19828
  return `(${list}) names one user in this form: write one line per user, or use '${SUDOERS_SYNTAX}'`;
@@ -19804,19 +19830,22 @@ function targetError(list) {
19804
19830
  return `expected one user name or ALL in (\u2026), not ${shown(`(${list})`)}`;
19805
19831
  }
19806
19832
  function kernelRules(m) {
19807
- const [, nopasswd, who = "", target = "root", dir, stdin, rest2 = ""] = m;
19833
+ const [, nopasswd, who = "", target = "root", dir, stdin, sums = "", rest2 = ""] = m;
19808
19834
  const bad = whoError(who);
19809
19835
  if (bad !== void 0) return bad;
19810
19836
  if (target !== "ALL" && !NAME4.test(target)) return targetError(target);
19811
19837
  const cwd = dir === void 0 ? void 0 : cwdOf(dir);
19812
19838
  if (cwd === "") return `Cwd expects one absolute directory, not ${shown(dir)}`;
19813
19839
  if (stdin !== void 0 && stdin !== "none") return `Stdin takes none, not ${shown(stdin)}`;
19840
+ const sha2564 = sha256Of(sums);
19841
+ if (typeof sha2564 === "string") return sha2564;
19814
19842
  const rule = {
19815
19843
  nopasswd: !!nopasswd,
19816
19844
  who,
19817
19845
  target,
19818
19846
  ...cwd !== void 0 ? { cwd } : {},
19819
- ...stdin === "none" ? { stdin: "none" } : {}
19847
+ ...stdin === "none" ? { stdin: "none" } : {},
19848
+ ...sums === "" ? {} : { sha256: sha2564 }
19820
19849
  };
19821
19850
  const text = rest2.trim();
19822
19851
  if (text === "*") return [{ ...rule, cmnd: "*" }];
@@ -20069,12 +20098,26 @@ var RAN_ONCE = "sudo: ran once; this command line can't be recorded as always";
20069
20098
  var ROOT_ONLY_MAX = 64;
20070
20099
  var OPERANDS_MAX = 256;
20071
20100
  var PATH_MAX = 4096;
20101
+ function userWritable(bound3) {
20102
+ const files = Object.entries(bound3).map(([path, hex4]) => ({ path, sha256: hex4 }));
20103
+ return files.length > 0 ? { userWritable: files } : {};
20104
+ }
20072
20105
  function stdinMatches(rule, stdin) {
20073
20106
  return rule.stdin === void 0 || stdin === "tty" || stdin === "none";
20074
20107
  }
20075
20108
  function recordable(cwd, argv) {
20076
20109
  return !hasControl(cwd) && !argv.some(hasControl);
20077
20110
  }
20111
+ function changedText(file) {
20112
+ return `sudo: ${file} changed since it was approved`;
20113
+ }
20114
+ function changedFile(approved, now) {
20115
+ const files = /* @__PURE__ */ new Set([...Object.keys(approved), ...Object.keys(now)]);
20116
+ return [...files].find((file) => approved[file] !== now[file]);
20117
+ }
20118
+ function pinned(rule, bound3) {
20119
+ return rule.sha256 === void 0 || changedFile(rule.sha256, bound3) === void 0;
20120
+ }
20078
20121
  var VISUDO_FILES_MAX = 64;
20079
20122
  var VISUDO_TEXT_MAX = 1024 * 1024;
20080
20123
  function visudoCall(req) {
@@ -20205,11 +20248,13 @@ function usable(parsed2) {
20205
20248
  function policyOf(settings) {
20206
20249
  return { type: settings.timestampType, minutes: settings.timestampTimeout };
20207
20250
  }
20208
- function described(rule) {
20251
+ function described(rule, writable2) {
20209
20252
  const cmnd = rule.cmnd === "*" ? "ALL" : commandText(rule.cmnd);
20210
20253
  const cwd = rule.cwd === void 0 ? "" : `CWD=${quoteWord(rule.cwd)} `;
20211
20254
  const stdin = rule.stdin === void 0 ? "" : "STDIN=none ";
20212
- return ` (${rule.target}) ${cwd}${stdin}${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}`;
20255
+ const sums = Object.entries(rule.sha256 ?? {}).map(([path, hex4]) => `SHA256=${quoteWord(path)}:${hex4} `).join("");
20256
+ const flag = writable2 ? " # user-writable command" : "";
20257
+ return ` (${rule.target}) ${cwd}${stdin}${sums}${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}${flag}`;
20213
20258
  }
20214
20259
  var Sudo = class {
20215
20260
  o;
@@ -20400,15 +20445,31 @@ var Sudo = class {
20400
20445
  if (walked.denied !== void 0 || walked.missing) return false;
20401
20446
  return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
20402
20447
  }
20403
- async untrusted(path, cwd, word) {
20404
- const files = this.o.backing ? await this.o.backing(path, cwd) : [];
20405
- if (!files) return failed2("ENOENT", `sudo: ${word}: command not found`);
20406
- for (const file of [path, ...files]) {
20407
- if (!await this.trusted(file)) {
20408
- return failed2("EPERM", `sudo: ${file} must be root's, and writable by root only`);
20409
- }
20448
+ async real(file) {
20449
+ const { fs } = this.o;
20450
+ return await fs.realpath?.(file, true).catch(() => file) ?? file;
20451
+ }
20452
+ async digests(files) {
20453
+ const { fs } = this.o;
20454
+ const found = {};
20455
+ for (const file of new Set(files)) {
20456
+ if (await this.trusted(file)) continue;
20457
+ const real = await this.real(file);
20458
+ found[real] = await fs.readFileBuffer(real).then(sha256, () => "");
20410
20459
  }
20411
- return void 0;
20460
+ return found;
20461
+ }
20462
+ async bound(path, cwd) {
20463
+ const files = this.o.backing ? await this.o.backing(path, cwd) : [];
20464
+ const bound3 = files && await this.digests([path, ...files]);
20465
+ return bound3 && !Object.values(bound3).includes("") ? bound3 : void 0;
20466
+ }
20467
+ async writable(rule) {
20468
+ if (rule.cmnd === "*") return false;
20469
+ const { path } = rule.cmnd;
20470
+ const bound3 = await this.bound(path, "/");
20471
+ if (bound3) return Object.keys(bound3).length > 0;
20472
+ return !await this.trusted(await this.existing(path));
20412
20473
  }
20413
20474
  async locate(word, cwd) {
20414
20475
  const { fs } = this.o;
@@ -20453,7 +20514,7 @@ var Sudo = class {
20453
20514
  const root = cred.euid === 0;
20454
20515
  const rules = root ? [] : policy.rules;
20455
20516
  const mine = rules.filter((rule) => applies(rule.who));
20456
- if (parsed2.list) return this.listed(self, root, mine, settings.matching);
20517
+ if (parsed2.list) return await this.listed(self, root, mine, settings.matching);
20457
20518
  const session = { caller, self, target, owner, policy: policyOf(settings), parsed: parsed2, signal };
20458
20519
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
20459
20520
  return this.command(session, root, mine, call);
@@ -20470,14 +20531,13 @@ var Sudo = class {
20470
20531
  );
20471
20532
  }
20472
20533
  const path = await this.locate(word, cwd);
20473
- if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
20474
- const untrusted2 = await this.untrusted(path, cwd, word);
20475
- if (untrusted2) return untrusted2;
20534
+ const bound3 = path && await this.bound(path, cwd);
20535
+ if (!path || !bound3) return failed2("ENOENT", `sudo: ${word}: command not found`);
20476
20536
  const argv = [path, ...args];
20477
20537
  const matching = mine.filter(
20478
- (rule) => (rule.target === "ALL" || rule.target === target.name) && (rule.cwd === void 0 || rule.cwd === cwd) && stdinMatches(rule, caller.stdin) && commandMatches(rule.cmnd, argv)
20538
+ (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)
20479
20539
  );
20480
- const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
20540
+ const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term, bound3);
20481
20541
  if (root || matching.some((rule) => rule.nopasswd)) return grant();
20482
20542
  const quoted = argv.map(quoteWord).join(" ");
20483
20543
  if (matching.length === 0) {
@@ -20493,14 +20553,15 @@ var Sudo = class {
20493
20553
  }
20494
20554
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
20495
20555
  return this.pendingFor(cred.ruid, async () => {
20496
- const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd });
20556
+ const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd, bound: bound3 });
20497
20557
  if ("error" in answer2) return answer2;
20498
20558
  if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
20499
20559
  return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
20500
20560
  }
20501
- const changed = await this.untrusted(path, cwd, word);
20502
- if (changed) return changed;
20503
- const result = answer2.answer === "once" ? grant() : await this.always(self, target, cwd, argv, recordable(cwd, parsed2.command), grant);
20561
+ const now = await this.bound(path, cwd);
20562
+ if (!now) return failed2("ENOENT", `sudo: ${word}: command not found`);
20563
+ const shown2 = recordable(cwd, [...parsed2.command, ...Object.keys(bound3)]);
20564
+ const result = answer2.answer === "once" ? grant() : await this.always(self, target, { cwd, argv, bound: bound3 }, now, shown2, grant);
20504
20565
  if (!("error" in result)) this.stamped(s, answer2);
20505
20566
  return result;
20506
20567
  });
@@ -20519,7 +20580,7 @@ var Sudo = class {
20519
20580
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
20520
20581
  const cwd = await this.canonical(dir) ?? normalizePath(dir);
20521
20582
  return this.pendingFor(s.caller.cred.ruid, async () => {
20522
- const answer2 = await this.ask(s, { argv: [], path: "", cwd, validate: true });
20583
+ const answer2 = await this.ask(s, { argv: [], path: "", cwd, bound: {}, validate: true });
20523
20584
  if ("error" in answer2) return answer2;
20524
20585
  if (!answer2.person) await s.caller.report?.(NOT_STAMPED);
20525
20586
  this.stamped(s, answer2);
@@ -20547,14 +20608,17 @@ var Sudo = class {
20547
20608
  `sudo: ${self.name} : TTY=${tty} ; PWD=${quoteWord(cwd)} ; USER=${target.name} ; TIMESTAMP=${type} ; COMMAND=${command}`
20548
20609
  );
20549
20610
  }
20550
- async always(self, target, cwd, argv, shown2, grant) {
20611
+ async always(self, target, line, now, shown2, grant) {
20612
+ const { cwd, argv, bound: bound3 } = line;
20551
20613
  if (!shown2 || !recordable(cwd, argv)) return { ...grant(), notice: RAN_ONCE };
20552
- if (!await this.remember(self, target, cwd, argv)) {
20614
+ const changed = changedFile(bound3, now);
20615
+ if (changed !== void 0) return failed2("EPERM", changedText(changed));
20616
+ if (!await this.remember(self, target, line)) {
20553
20617
  return failed2("EIO", "sudo: the rule could not be recorded");
20554
20618
  }
20555
20619
  return grant();
20556
20620
  }
20557
- listed(self, root, rules, defaults2) {
20621
+ async listed(self, root, rules, defaults2) {
20558
20622
  const host = this.o.hostname;
20559
20623
  if (root) {
20560
20624
  return {
@@ -20566,7 +20630,9 @@ var Sudo = class {
20566
20630
  if (rules.length === 0) {
20567
20631
  return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
20568
20632
  }
20569
- const lines2 = rules.map(described).join("\n");
20633
+ const shown2 = [];
20634
+ for (const rule of rules) shown2.push(described(rule, await this.writable(rule)));
20635
+ const lines2 = shown2.join("\n");
20570
20636
  const params = defaults2.flatMap((d) => d.params).join(", ");
20571
20637
  const matching = params === "" ? "" : `Matching Defaults entries for ${self.name} on ${host}:
20572
20638
  ${params}
@@ -20599,9 +20665,10 @@ ${lines2}
20599
20665
  ...caller.tty ? { tty: caller.tty } : {},
20600
20666
  createdAt,
20601
20667
  expiresAt: createdAt + timeout,
20602
- always: asked.validate ? false : recordable(cwd, [path, ...argv]),
20668
+ always: asked.validate ? false : recordable(cwd, [path, ...argv, ...Object.keys(asked.bound)]),
20603
20669
  stdin: caller.stdin,
20604
20670
  rootOnly: asked.validate ? {} : await this.rootOnly(cwd, argv.slice(1)),
20671
+ ...userWritable(asked.bound),
20605
20672
  ...asked.validate ? { validate: true } : {},
20606
20673
  timestamp: true
20607
20674
  };
@@ -20621,7 +20688,7 @@ ${lines2}
20621
20688
  requester.removeEventListener("abort", withdraw);
20622
20689
  }
20623
20690
  }
20624
- async remember(self, target, cwd, argv) {
20691
+ async remember(self, target, { cwd, argv, bound: bound3 }) {
20625
20692
  const { fs } = this.o;
20626
20693
  const path = `${SUDOERS_D}/${self.name}`;
20627
20694
  if (!NAME4.test(self.name)) return false;
@@ -20632,7 +20699,7 @@ ${lines2}
20632
20699
  if (st && !await this.trusted(path)) return false;
20633
20700
  const old = st ? await fs.readFile(path) : "";
20634
20701
  const words2 = recordedCommand(argv);
20635
- const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none Cmnd ${words2}
20702
+ const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words2}
20636
20703
  `;
20637
20704
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
20638
20705
  ${line}`;
@@ -20643,7 +20710,7 @@ ${line}`;
20643
20710
  return false;
20644
20711
  }
20645
20712
  }
20646
- grant(self, target, cred, argv, file, cwd, term) {
20713
+ grant(self, target, cred, argv, file, cwd, term, files) {
20647
20714
  const env = {
20648
20715
  ...this.o.env(),
20649
20716
  ...userEnv(target, this.o.pnpmHome),
@@ -20656,7 +20723,9 @@ ${line}`;
20656
20723
  SUDO_GID: String(cred.rgid),
20657
20724
  SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
20658
20725
  };
20659
- return { grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred } };
20726
+ return {
20727
+ grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred, files }
20728
+ };
20660
20729
  }
20661
20730
  };
20662
20731
 
@@ -28983,14 +29052,20 @@ var WasmProcess = class {
28983
29052
  if (!("grant" in answer2)) return answer2;
28984
29053
  if (this.dying) throw new KernelError("EINTR");
28985
29054
  if (answer2.notice !== void 0) await this.complainLine(answer2.notice);
28986
- const { file, argv, env, cwd, cred, caller } = answer2.grant;
29055
+ const { file, argv, env, cwd, cred, caller, files } = answer2.grant;
28987
29056
  const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
28988
- return {
28989
- pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
28990
- cred,
28991
- caller
28992
- })
28993
- };
29057
+ try {
29058
+ return {
29059
+ pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
29060
+ cred,
29061
+ caller,
29062
+ files
29063
+ })
29064
+ };
29065
+ } catch (err) {
29066
+ if (err instanceof ChangedError) return { error: err.notice, errno: err.code };
29067
+ throw err;
29068
+ }
28994
29069
  }
28995
29070
  stdinKind() {
28996
29071
  if (!this.fds.has(0)) return "none";
@@ -33569,6 +33644,10 @@ function describe(req, tty) {
33569
33644
  };
33570
33645
  return { argv: [req.argv0, ...req.args], tty, started: Date.now(), ...exe };
33571
33646
  }
33647
+ function refuseChanged(approved, now) {
33648
+ const changed = changedFile(approved, now);
33649
+ if (changed !== void 0) throw new ChangedError(changedText(changed));
33650
+ }
33572
33651
  function linksOf2(req) {
33573
33652
  return req.links === void 0 ? {} : { links: req.links };
33574
33653
  }
@@ -34702,6 +34781,27 @@ var Launcher = class {
34702
34781
  }
34703
34782
  return true;
34704
34783
  }
34784
+ async unchanged(planned, file, cwd, grant) {
34785
+ const approved = grant?.files;
34786
+ if (!approved) return {};
34787
+ const files = await this.backingOf(planned, file, cwd, "");
34788
+ refuseChanged(approved, await this.sudoer.digests([file, ...files]));
34789
+ return { pinned: approved };
34790
+ }
34791
+ async pinOf(path, caller) {
34792
+ const { pinned: pinned2 } = caller;
34793
+ if (!pinned2) return {};
34794
+ const real = await this.sudoer.real(path);
34795
+ const hex4 = pinned2[real];
34796
+ if (hex4 !== void 0) return { [real]: hex4 };
34797
+ if (await this.sudoer.trusted(real)) return {};
34798
+ throw new ChangedError(changedText(real));
34799
+ }
34800
+ async pinnedBytes(pin, bytes) {
34801
+ const [real = ""] = Object.keys(pin);
34802
+ refuseChanged(pin, { [real]: await sha256(bytes) });
34803
+ return bytes;
34804
+ }
34705
34805
  async grantedCwd(caller, cwd) {
34706
34806
  const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
34707
34807
  return real === cwd && await this.sudoer.reachable(caller, cwd);
@@ -34790,13 +34890,19 @@ var Launcher = class {
34790
34890
  return found;
34791
34891
  }
34792
34892
  async module(path, caller, asked) {
34793
- const key = await this.cacheKey(path, asked);
34893
+ const pin = await this.pinOf(path, caller);
34894
+ const sum = Object.values(pin)[0];
34895
+ const key = sum === void 0 ? await this.cacheKey(path, asked) : `${path}:sha256:${sum}`;
34794
34896
  return this.cached(this.compiled, key, async () => {
34795
- const { bytes, key: read } = await this.keyed(path, caller, asked);
34897
+ const { bytes: read, key: readKey } = await this.keyed(path, caller, asked);
34898
+ const bytes = sum === void 0 ? read : await this.pinnedBytes(pin, read);
34796
34899
  const memory = importedMemory(bytes);
34797
34900
  const foreign = foreignImports(bytes);
34798
34901
  const compiled = await WebAssembly.compile(bytes);
34799
- return { value: { module: compiled, ...memory ? { memory } : {}, foreign }, key: read };
34902
+ return {
34903
+ value: { module: compiled, ...memory ? { memory } : {}, foreign },
34904
+ key: sum === void 0 ? readKey : key
34905
+ };
34800
34906
  });
34801
34907
  }
34802
34908
  async checkedSources(planned, caller) {
@@ -34821,6 +34927,11 @@ var Launcher = class {
34821
34927
  }
34822
34928
  }
34823
34929
  async text(path, caller, asked) {
34930
+ const pin = await this.pinOf(path, caller);
34931
+ if (Object.keys(pin).length > 0) {
34932
+ const { bytes } = await this.keyed(path, caller, asked);
34933
+ return new TextDecoder().decode(await this.pinnedBytes(pin, bytes));
34934
+ }
34824
34935
  if (!this.integrity) return this.base.readFile(path);
34825
34936
  const key = await this.cacheKey(path, asked);
34826
34937
  return this.cached(this.texts, key, async () => {
@@ -35009,7 +35120,12 @@ var Launcher = class {
35009
35120
  const own = env.HOSTNAME;
35010
35121
  if (asked.target.abi === "wasi") env.HOSTNAME = this.hostname;
35011
35122
  const planned = chosen2(asked, env);
35012
- const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tags?.[0] };
35123
+ const caller = {
35124
+ pid: req.ppid ?? 0,
35125
+ uid: req.cred.euid,
35126
+ tag: req.tags?.[0],
35127
+ pinned: req.pinned
35128
+ };
35013
35129
  if (this.integrity) await this.checkedSources(planned, caller);
35014
35130
  const [program, exeId] = await this.loaded(planned.target, env, caller);
35015
35131
  if (readsEtcHostname(program)) setOrDrop(env, "HOSTNAME", own);
@@ -35524,6 +35640,7 @@ var Launcher = class {
35524
35640
  const scope = grant ? "" : await this.scopeOf(cred.euid);
35525
35641
  const file = booked === void 0 ? req.file : await this.ticketFile(req, fds);
35526
35642
  const planned = await this.checkedPlan({ ...req, file }, { ppid, cred, scope }, fds);
35643
+ const pin = await this.unchanged(planned, file, req.cwd, grant);
35527
35644
  const { recheck, ...redeemed } = await this.redeemed(
35528
35645
  { ...req, file },
35529
35646
  { ppid, cred, scope, booked },
@@ -35535,6 +35652,7 @@ var Launcher = class {
35535
35652
  const replan = () => this.planOrSearch({ ...req, file }, { ppid, cred, scope });
35536
35653
  const again = { ...planned, replan, recheck };
35537
35654
  const handle = await this.launch(again, {
35655
+ ...pin,
35538
35656
  ...deciding ? { decided: deciding.promise } : {},
35539
35657
  cwd: req.cwd,
35540
35658
  cwdFrom: inheritedCwd(req.chdir, this.cwdSlots.get(ppid)),