@ai-ecoverse/slicc-kernel 1.178.0 → 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.
@@ -7452,6 +7452,9 @@ var KERNEL_SIGINFO = Object.freeze({
7452
7452
  // src/body-idle.ts
7453
7453
  var BODY_IDLE_MS = 5 * 60 * 1e3;
7454
7454
 
7455
+ // src/kernel/net/transport.ts
7456
+ var FORBIDDEN_HEADER = "forbidden-header";
7457
+
7455
7458
  // src/kernel/net/ws-queue.ts
7456
7459
  var INBOUND_LIMIT = 16 * 1024 * 1024;
7457
7460
  var MESSAGE_LIMIT = 64 * 1024 * 1024;
@@ -7586,7 +7589,7 @@ function serveTransport(peer, transport, {
7586
7589
  message: e instanceof Error ? e.message : String(e),
7587
7590
  ...typeof status === "number" ? { status } : {},
7588
7591
  ...typeof code === "string" ? { code } : {},
7589
- ...detail === "transport" ? { detail } : {}
7592
+ ...detail === "transport" || detail === FORBIDDEN_HEADER ? { detail } : {}
7590
7593
  });
7591
7594
  };
7592
7595
  const uploads = uploadsFor(peer);
@@ -8774,7 +8777,7 @@ function satisfies(version, range2) {
8774
8777
  }
8775
8778
 
8776
8779
  // src/version.ts
8777
- var KERNEL_VERSION = true ? "1.178.0" : "0.0.0-dev";
8780
+ var KERNEL_VERSION = true ? "1.178.2" : "0.0.0-dev";
8778
8781
  var OS_RELEASE = "6.0.0-slicc";
8779
8782
  function procVersion() {
8780
8783
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -10056,9 +10059,9 @@ var UnlinkedKeeper = class {
10056
10059
  if (kept.whole) return kept.undoing(async () => void 0);
10057
10060
  await kept.undoing(
10058
10061
  () => replace(target, async (gated, sealed) => {
10059
- const pinned = await keeper.pin(target, gated, sealed);
10060
- if (pinned.moved.has(target)) pinned.commit();
10061
- 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));
10062
10065
  })
10063
10066
  );
10064
10067
  },
@@ -10071,8 +10074,8 @@ var UnlinkedKeeper = class {
10071
10074
  () => keeper.marks.around(
10072
10075
  [source],
10073
10076
  () => replace(target, async (gated, sealed) => {
10074
- const pinned = await keeper.pin(target, gated, sealed);
10075
- 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));
10076
10079
  })
10077
10080
  )
10078
10081
  );
@@ -10107,14 +10110,14 @@ var UnlinkedKeeper = class {
10107
10110
  }
10108
10111
  return false;
10109
10112
  }
10110
- async settle(pinned, op) {
10113
+ async settle(pinned2, op) {
10111
10114
  try {
10112
10115
  await op();
10113
10116
  } catch (err) {
10114
- await pinned.rollback();
10117
+ await pinned2.rollback();
10115
10118
  throw err;
10116
10119
  }
10117
- pinned.commit();
10120
+ pinned2.commit();
10118
10121
  }
10119
10122
  heldWithin(target) {
10120
10123
  const held = /* @__PURE__ */ new Map();
@@ -12638,6 +12641,13 @@ var SpawnError = class extends Error {
12638
12641
  this.code = code;
12639
12642
  }
12640
12643
  };
12644
+ var ChangedError = class extends SpawnError {
12645
+ notice;
12646
+ constructor(notice) {
12647
+ super("EPERM");
12648
+ this.notice = notice;
12649
+ }
12650
+ };
12641
12651
  function interrupted(signal) {
12642
12652
  const { promise, reject } = Promise.withResolvers();
12643
12653
  const fail2 = () => reject(new KernelError("EINTR"));
@@ -19617,8 +19627,10 @@ var SUDOERS_D = "/etc/sudoers.d";
19617
19627
  var NAME4 = /^[a-z_][a-z0-9_.-]*$/;
19618
19628
  var WORD = String.raw`(?:'[^']*'|"(?:[^"\\]|\\[\s\S])*"|\\[\s\S]|[^\s'"\\])+`;
19619
19629
  var KERNEL_FORM = new RegExp(
19620
- 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+(.*))?$`
19621
19631
  );
19632
+ var SHA256_CLAUSE = new RegExp(String.raw`Sha256\s+(${WORD})`, "g");
19633
+ var HEX = /^[0-9a-f]{64}$/;
19622
19634
  var SUDOERS_FORM = /^(\S+)\s+([^\s=]+)\s*=\s*(.*)$/;
19623
19635
  var RUNAS = /^\(([^()]*)\)\s*/;
19624
19636
  var TAG = /^([A-Z_]+)\s*:\s*/;
@@ -19627,7 +19639,7 @@ var PLAIN2 = /^[A-Za-z0-9_@%+=:,./-]+$/;
19627
19639
  var CONTROL2 = /[\p{Cc}\u2028\u2029]/u;
19628
19640
  var CONTROLS = /[\p{Cc}\u2028\u2029]/gu;
19629
19641
  var SHOWN_MAX = 60;
19630
- 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|*>";
19631
19643
  var SUDOERS_SYNTAX = "<user|%group|ALL> ALL=[(<target>[,<target>\u2026])] [NOPASSWD:] <command\u2026|ALL>";
19632
19644
  var EXPECTED = `expected '${KERNEL_SYNTAX}' or '${SUDOERS_SYNTAX}'`;
19633
19645
  var TIMESTAMP_TIMEOUT = 5;
@@ -19794,6 +19806,23 @@ function commandOf2(text, after) {
19794
19806
  if (only && /\s""$/.test(text)) return { path, args: [] };
19795
19807
  return args.length === 0 ? { path } : { path, args };
19796
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
+ }
19797
19826
  function targetError(list) {
19798
19827
  if (list.includes(",")) {
19799
19828
  return `(${list}) names one user in this form: write one line per user, or use '${SUDOERS_SYNTAX}'`;
@@ -19801,19 +19830,22 @@ function targetError(list) {
19801
19830
  return `expected one user name or ALL in (\u2026), not ${shown(`(${list})`)}`;
19802
19831
  }
19803
19832
  function kernelRules(m) {
19804
- const [, nopasswd, who = "", target = "root", dir, stdin, rest2 = ""] = m;
19833
+ const [, nopasswd, who = "", target = "root", dir, stdin, sums = "", rest2 = ""] = m;
19805
19834
  const bad = whoError(who);
19806
19835
  if (bad !== void 0) return bad;
19807
19836
  if (target !== "ALL" && !NAME4.test(target)) return targetError(target);
19808
19837
  const cwd = dir === void 0 ? void 0 : cwdOf(dir);
19809
19838
  if (cwd === "") return `Cwd expects one absolute directory, not ${shown(dir)}`;
19810
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;
19811
19842
  const rule = {
19812
19843
  nopasswd: !!nopasswd,
19813
19844
  who,
19814
19845
  target,
19815
19846
  ...cwd !== void 0 ? { cwd } : {},
19816
- ...stdin === "none" ? { stdin: "none" } : {}
19847
+ ...stdin === "none" ? { stdin: "none" } : {},
19848
+ ...sums === "" ? {} : { sha256: sha2564 }
19817
19849
  };
19818
19850
  const text = rest2.trim();
19819
19851
  if (text === "*") return [{ ...rule, cmnd: "*" }];
@@ -20066,12 +20098,26 @@ var RAN_ONCE = "sudo: ran once; this command line can't be recorded as always";
20066
20098
  var ROOT_ONLY_MAX = 64;
20067
20099
  var OPERANDS_MAX = 256;
20068
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
+ }
20069
20105
  function stdinMatches(rule, stdin) {
20070
20106
  return rule.stdin === void 0 || stdin === "tty" || stdin === "none";
20071
20107
  }
20072
20108
  function recordable(cwd, argv) {
20073
20109
  return !hasControl(cwd) && !argv.some(hasControl);
20074
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
+ }
20075
20121
  var VISUDO_FILES_MAX = 64;
20076
20122
  var VISUDO_TEXT_MAX = 1024 * 1024;
20077
20123
  function visudoCall(req) {
@@ -20202,11 +20248,13 @@ function usable(parsed2) {
20202
20248
  function policyOf(settings) {
20203
20249
  return { type: settings.timestampType, minutes: settings.timestampTimeout };
20204
20250
  }
20205
- function described(rule) {
20251
+ function described(rule, writable2) {
20206
20252
  const cmnd = rule.cmnd === "*" ? "ALL" : commandText(rule.cmnd);
20207
20253
  const cwd = rule.cwd === void 0 ? "" : `CWD=${quoteWord(rule.cwd)} `;
20208
20254
  const stdin = rule.stdin === void 0 ? "" : "STDIN=none ";
20209
- 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}`;
20210
20258
  }
20211
20259
  var Sudo = class {
20212
20260
  o;
@@ -20397,15 +20445,31 @@ var Sudo = class {
20397
20445
  if (walked.denied !== void 0 || walked.missing) return false;
20398
20446
  return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
20399
20447
  }
20400
- async untrusted(path, cwd, word) {
20401
- const files = this.o.backing ? await this.o.backing(path, cwd) : [];
20402
- if (!files) return failed2("ENOENT", `sudo: ${word}: command not found`);
20403
- for (const file of [path, ...files]) {
20404
- if (!await this.trusted(file)) {
20405
- return failed2("EPERM", `sudo: ${file} must be root's, and writable by root only`);
20406
- }
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, () => "");
20407
20459
  }
20408
- 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));
20409
20473
  }
20410
20474
  async locate(word, cwd) {
20411
20475
  const { fs } = this.o;
@@ -20450,7 +20514,7 @@ var Sudo = class {
20450
20514
  const root = cred.euid === 0;
20451
20515
  const rules = root ? [] : policy.rules;
20452
20516
  const mine = rules.filter((rule) => applies(rule.who));
20453
- if (parsed2.list) return this.listed(self, root, mine, settings.matching);
20517
+ if (parsed2.list) return await this.listed(self, root, mine, settings.matching);
20454
20518
  const session = { caller, self, target, owner, policy: policyOf(settings), parsed: parsed2, signal };
20455
20519
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
20456
20520
  return this.command(session, root, mine, call);
@@ -20467,14 +20531,13 @@ var Sudo = class {
20467
20531
  );
20468
20532
  }
20469
20533
  const path = await this.locate(word, cwd);
20470
- if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
20471
- const untrusted2 = await this.untrusted(path, cwd, word);
20472
- 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`);
20473
20536
  const argv = [path, ...args];
20474
20537
  const matching = mine.filter(
20475
- (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)
20476
20539
  );
20477
- 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);
20478
20541
  if (root || matching.some((rule) => rule.nopasswd)) return grant();
20479
20542
  const quoted = argv.map(quoteWord).join(" ");
20480
20543
  if (matching.length === 0) {
@@ -20490,14 +20553,15 @@ var Sudo = class {
20490
20553
  }
20491
20554
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
20492
20555
  return this.pendingFor(cred.ruid, async () => {
20493
- 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 });
20494
20557
  if ("error" in answer2) return answer2;
20495
20558
  if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
20496
20559
  return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
20497
20560
  }
20498
- const changed = await this.untrusted(path, cwd, word);
20499
- if (changed) return changed;
20500
- 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);
20501
20565
  if (!("error" in result)) this.stamped(s, answer2);
20502
20566
  return result;
20503
20567
  });
@@ -20516,7 +20580,7 @@ var Sudo = class {
20516
20580
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
20517
20581
  const cwd = await this.canonical(dir) ?? normalizePath(dir);
20518
20582
  return this.pendingFor(s.caller.cred.ruid, async () => {
20519
- const answer2 = await this.ask(s, { argv: [], path: "", cwd, validate: true });
20583
+ const answer2 = await this.ask(s, { argv: [], path: "", cwd, bound: {}, validate: true });
20520
20584
  if ("error" in answer2) return answer2;
20521
20585
  if (!answer2.person) await s.caller.report?.(NOT_STAMPED);
20522
20586
  this.stamped(s, answer2);
@@ -20544,14 +20608,17 @@ var Sudo = class {
20544
20608
  `sudo: ${self.name} : TTY=${tty} ; PWD=${quoteWord(cwd)} ; USER=${target.name} ; TIMESTAMP=${type} ; COMMAND=${command}`
20545
20609
  );
20546
20610
  }
20547
- async always(self, target, cwd, argv, shown2, grant) {
20611
+ async always(self, target, line, now, shown2, grant) {
20612
+ const { cwd, argv, bound: bound3 } = line;
20548
20613
  if (!shown2 || !recordable(cwd, argv)) return { ...grant(), notice: RAN_ONCE };
20549
- 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)) {
20550
20617
  return failed2("EIO", "sudo: the rule could not be recorded");
20551
20618
  }
20552
20619
  return grant();
20553
20620
  }
20554
- listed(self, root, rules, defaults2) {
20621
+ async listed(self, root, rules, defaults2) {
20555
20622
  const host = this.o.hostname;
20556
20623
  if (root) {
20557
20624
  return {
@@ -20563,7 +20630,9 @@ var Sudo = class {
20563
20630
  if (rules.length === 0) {
20564
20631
  return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
20565
20632
  }
20566
- 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");
20567
20636
  const params = defaults2.flatMap((d) => d.params).join(", ");
20568
20637
  const matching = params === "" ? "" : `Matching Defaults entries for ${self.name} on ${host}:
20569
20638
  ${params}
@@ -20596,9 +20665,10 @@ ${lines2}
20596
20665
  ...caller.tty ? { tty: caller.tty } : {},
20597
20666
  createdAt,
20598
20667
  expiresAt: createdAt + timeout,
20599
- always: asked.validate ? false : recordable(cwd, [path, ...argv]),
20668
+ always: asked.validate ? false : recordable(cwd, [path, ...argv, ...Object.keys(asked.bound)]),
20600
20669
  stdin: caller.stdin,
20601
20670
  rootOnly: asked.validate ? {} : await this.rootOnly(cwd, argv.slice(1)),
20671
+ ...userWritable(asked.bound),
20602
20672
  ...asked.validate ? { validate: true } : {},
20603
20673
  timestamp: true
20604
20674
  };
@@ -20618,7 +20688,7 @@ ${lines2}
20618
20688
  requester.removeEventListener("abort", withdraw);
20619
20689
  }
20620
20690
  }
20621
- async remember(self, target, cwd, argv) {
20691
+ async remember(self, target, { cwd, argv, bound: bound3 }) {
20622
20692
  const { fs } = this.o;
20623
20693
  const path = `${SUDOERS_D}/${self.name}`;
20624
20694
  if (!NAME4.test(self.name)) return false;
@@ -20629,7 +20699,7 @@ ${lines2}
20629
20699
  if (st && !await this.trusted(path)) return false;
20630
20700
  const old = st ? await fs.readFile(path) : "";
20631
20701
  const words2 = recordedCommand(argv);
20632
- 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}
20633
20703
  `;
20634
20704
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
20635
20705
  ${line}`;
@@ -20640,7 +20710,7 @@ ${line}`;
20640
20710
  return false;
20641
20711
  }
20642
20712
  }
20643
- grant(self, target, cred, argv, file, cwd, term) {
20713
+ grant(self, target, cred, argv, file, cwd, term, files) {
20644
20714
  const env = {
20645
20715
  ...this.o.env(),
20646
20716
  ...userEnv(target, this.o.pnpmHome),
@@ -20653,7 +20723,9 @@ ${line}`;
20653
20723
  SUDO_GID: String(cred.rgid),
20654
20724
  SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
20655
20725
  };
20656
- 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
+ };
20657
20729
  }
20658
20730
  };
20659
20731
 
@@ -22690,6 +22762,9 @@ var HttpHandles = class {
22690
22762
  if (e instanceof PlaceholderRefused) {
22691
22763
  return { ok: false, errno: e.code, message: text, detail: "placeholder" };
22692
22764
  }
22765
+ if (e.detail === FORBIDDEN_HEADER) {
22766
+ return { ok: false, errno: "ENOTSUP", message: text, detail: FORBIDDEN_HEADER };
22767
+ }
22693
22768
  const status = e.status;
22694
22769
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
22695
22770
  const code = codeOf2(e);
@@ -28977,14 +29052,20 @@ var WasmProcess = class {
28977
29052
  if (!("grant" in answer2)) return answer2;
28978
29053
  if (this.dying) throw new KernelError("EINTR");
28979
29054
  if (answer2.notice !== void 0) await this.complainLine(answer2.notice);
28980
- const { file, argv, env, cwd, cred, caller } = answer2.grant;
29055
+ const { file, argv, env, cwd, cred, caller, files } = answer2.grant;
28981
29056
  const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
28982
- return {
28983
- pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
28984
- cred,
28985
- caller
28986
- })
28987
- };
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
+ }
28988
29069
  }
28989
29070
  stdinKind() {
28990
29071
  if (!this.fds.has(0)) return "none";
@@ -30840,7 +30921,7 @@ var TransportError = class extends Error {
30840
30921
  super(message3);
30841
30922
  this.status = status;
30842
30923
  this.code = code;
30843
- this.detail = detail === "transport" ? detail : void 0;
30924
+ this.detail = detail === "transport" || detail === FORBIDDEN_HEADER ? detail : void 0;
30844
30925
  }
30845
30926
  };
30846
30927
  function unfollowed2(redirect) {
@@ -33563,6 +33644,10 @@ function describe(req, tty) {
33563
33644
  };
33564
33645
  return { argv: [req.argv0, ...req.args], tty, started: Date.now(), ...exe };
33565
33646
  }
33647
+ function refuseChanged(approved, now) {
33648
+ const changed = changedFile(approved, now);
33649
+ if (changed !== void 0) throw new ChangedError(changedText(changed));
33650
+ }
33566
33651
  function linksOf2(req) {
33567
33652
  return req.links === void 0 ? {} : { links: req.links };
33568
33653
  }
@@ -34696,6 +34781,27 @@ var Launcher = class {
34696
34781
  }
34697
34782
  return true;
34698
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
+ }
34699
34805
  async grantedCwd(caller, cwd) {
34700
34806
  const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
34701
34807
  return real === cwd && await this.sudoer.reachable(caller, cwd);
@@ -34784,13 +34890,19 @@ var Launcher = class {
34784
34890
  return found;
34785
34891
  }
34786
34892
  async module(path, caller, asked) {
34787
- 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}`;
34788
34896
  return this.cached(this.compiled, key, async () => {
34789
- 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);
34790
34899
  const memory = importedMemory(bytes);
34791
34900
  const foreign = foreignImports(bytes);
34792
34901
  const compiled = await WebAssembly.compile(bytes);
34793
- 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
+ };
34794
34906
  });
34795
34907
  }
34796
34908
  async checkedSources(planned, caller) {
@@ -34815,6 +34927,11 @@ var Launcher = class {
34815
34927
  }
34816
34928
  }
34817
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
+ }
34818
34935
  if (!this.integrity) return this.base.readFile(path);
34819
34936
  const key = await this.cacheKey(path, asked);
34820
34937
  return this.cached(this.texts, key, async () => {
@@ -35003,7 +35120,12 @@ var Launcher = class {
35003
35120
  const own = env.HOSTNAME;
35004
35121
  if (asked.target.abi === "wasi") env.HOSTNAME = this.hostname;
35005
35122
  const planned = chosen2(asked, env);
35006
- 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
+ };
35007
35129
  if (this.integrity) await this.checkedSources(planned, caller);
35008
35130
  const [program, exeId] = await this.loaded(planned.target, env, caller);
35009
35131
  if (readsEtcHostname(program)) setOrDrop(env, "HOSTNAME", own);
@@ -35518,6 +35640,7 @@ var Launcher = class {
35518
35640
  const scope = grant ? "" : await this.scopeOf(cred.euid);
35519
35641
  const file = booked === void 0 ? req.file : await this.ticketFile(req, fds);
35520
35642
  const planned = await this.checkedPlan({ ...req, file }, { ppid, cred, scope }, fds);
35643
+ const pin = await this.unchanged(planned, file, req.cwd, grant);
35521
35644
  const { recheck, ...redeemed } = await this.redeemed(
35522
35645
  { ...req, file },
35523
35646
  { ppid, cred, scope, booked },
@@ -35529,6 +35652,7 @@ var Launcher = class {
35529
35652
  const replan = () => this.planOrSearch({ ...req, file }, { ppid, cred, scope });
35530
35653
  const again = { ...planned, replan, recheck };
35531
35654
  const handle = await this.launch(again, {
35655
+ ...pin,
35532
35656
  ...deciding ? { decided: deciding.promise } : {},
35533
35657
  cwd: req.cwd,
35534
35658
  cwdFrom: inheritedCwd(req.chdir, this.cwdSlots.get(ppid)),