@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.
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.0" : "0.0.0-dev";
3712
+ var KERNEL_VERSION = true ? "1.178.2" : "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"));
@@ -15304,8 +15311,10 @@ var SUDOERS_D = "/etc/sudoers.d";
15304
15311
  var NAME3 = /^[a-z_][a-z0-9_.-]*$/;
15305
15312
  var WORD = String.raw`(?:'[^']*'|"(?:[^"\\]|\\[\s\S])*"|\\[\s\S]|[^\s'"\\])+`;
15306
15313
  var KERNEL_FORM = new RegExp(
15307
- String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()]*)\)\s+)?(?:Cwd\s+(${WORD})\s+)?(?:Stdin\s+(\S+)\s+)?Cmnd(?:\s+(.*))?$`
15314
+ String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()]*)\)\s+)?(?:Cwd\s+(${WORD})\s+)?(?:Stdin\s+(\S+)\s+)?((?:Sha256\s+${WORD}\s+)*)Cmnd(?:\s+(.*))?$`
15308
15315
  );
15316
+ var SHA256_CLAUSE = new RegExp(String.raw`Sha256\s+(${WORD})`, "g");
15317
+ var HEX = /^[0-9a-f]{64}$/;
15309
15318
  var SUDOERS_FORM = /^(\S+)\s+([^\s=]+)\s*=\s*(.*)$/;
15310
15319
  var RUNAS = /^\(([^()]*)\)\s*/;
15311
15320
  var TAG = /^([A-Z_]+)\s*:\s*/;
@@ -15314,7 +15323,7 @@ var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
15314
15323
  var CONTROL2 = /[\p{Cc}\u2028\u2029]/u;
15315
15324
  var CONTROLS = /[\p{Cc}\u2028\u2029]/gu;
15316
15325
  var SHOWN_MAX = 60;
15317
- var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] Cmnd <command\u2026|*>";
15326
+ var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] [Sha256 <file>=<sha256>\u2026] Cmnd <command\u2026|*>";
15318
15327
  var SUDOERS_SYNTAX = "<user|%group|ALL> ALL=[(<target>[,<target>\u2026])] [NOPASSWD:] <command\u2026|ALL>";
15319
15328
  var EXPECTED = `expected '${KERNEL_SYNTAX}' or '${SUDOERS_SYNTAX}'`;
15320
15329
  var TIMESTAMP_TIMEOUT = 5;
@@ -15481,6 +15490,23 @@ function commandOf2(text, after) {
15481
15490
  if (only && /\s""$/.test(text)) return { path, args: [] };
15482
15491
  return args.length === 0 ? { path } : { path, args };
15483
15492
  }
15493
+ function sha256Of(clauses) {
15494
+ const files = {};
15495
+ for (const [, word = ""] of clauses.matchAll(SHA256_CLAUSE)) {
15496
+ const [text = ""] = splitWords(word) ?? [];
15497
+ const at = text.lastIndexOf("=");
15498
+ const path = text.slice(0, at);
15499
+ const hex4 = text.slice(at + 1).toLowerCase();
15500
+ if (!path.startsWith("/") || !HEX.test(hex4)) {
15501
+ return `Sha256 expects <absolute file>=<sha256 in hex>, not ${shown(word)}`;
15502
+ }
15503
+ files[normalizePath(path)] = hex4;
15504
+ }
15505
+ return files;
15506
+ }
15507
+ function sha256Text(files) {
15508
+ return Object.entries(files).map(([path, hex4]) => `Sha256 ${quoteWord(`${path}=${hex4}`)} `).join("");
15509
+ }
15484
15510
  function targetError(list) {
15485
15511
  if (list.includes(",")) {
15486
15512
  return `(${list}) names one user in this form: write one line per user, or use '${SUDOERS_SYNTAX}'`;
@@ -15488,19 +15514,22 @@ function targetError(list) {
15488
15514
  return `expected one user name or ALL in (\u2026), not ${shown(`(${list})`)}`;
15489
15515
  }
15490
15516
  function kernelRules(m) {
15491
- const [, nopasswd, who = "", target = "root", dir, stdin, rest2 = ""] = m;
15517
+ const [, nopasswd, who = "", target = "root", dir, stdin, sums = "", rest2 = ""] = m;
15492
15518
  const bad = whoError(who);
15493
15519
  if (bad !== void 0) return bad;
15494
15520
  if (target !== "ALL" && !NAME3.test(target)) return targetError(target);
15495
15521
  const cwd = dir === void 0 ? void 0 : cwdOf(dir);
15496
15522
  if (cwd === "") return `Cwd expects one absolute directory, not ${shown(dir)}`;
15497
15523
  if (stdin !== void 0 && stdin !== "none") return `Stdin takes none, not ${shown(stdin)}`;
15524
+ const sha2564 = sha256Of(sums);
15525
+ if (typeof sha2564 === "string") return sha2564;
15498
15526
  const rule = {
15499
15527
  nopasswd: !!nopasswd,
15500
15528
  who,
15501
15529
  target,
15502
15530
  ...cwd !== void 0 ? { cwd } : {},
15503
- ...stdin === "none" ? { stdin: "none" } : {}
15531
+ ...stdin === "none" ? { stdin: "none" } : {},
15532
+ ...sums === "" ? {} : { sha256: sha2564 }
15504
15533
  };
15505
15534
  const text = rest2.trim();
15506
15535
  if (text === "*") return [{ ...rule, cmnd: "*" }];
@@ -16130,12 +16159,26 @@ var RAN_ONCE = "sudo: ran once; this command line can't be recorded as always";
16130
16159
  var ROOT_ONLY_MAX = 64;
16131
16160
  var OPERANDS_MAX = 256;
16132
16161
  var PATH_MAX = 4096;
16162
+ function userWritable(bound3) {
16163
+ const files = Object.entries(bound3).map(([path, hex4]) => ({ path, sha256: hex4 }));
16164
+ return files.length > 0 ? { userWritable: files } : {};
16165
+ }
16133
16166
  function stdinMatches(rule, stdin) {
16134
16167
  return rule.stdin === void 0 || stdin === "tty" || stdin === "none";
16135
16168
  }
16136
16169
  function recordable(cwd, argv) {
16137
16170
  return !hasControl(cwd) && !argv.some(hasControl);
16138
16171
  }
16172
+ function changedText(file) {
16173
+ return `sudo: ${file} changed since it was approved`;
16174
+ }
16175
+ function changedFile(approved, now) {
16176
+ const files = /* @__PURE__ */ new Set([...Object.keys(approved), ...Object.keys(now)]);
16177
+ return [...files].find((file) => approved[file] !== now[file]);
16178
+ }
16179
+ function pinned(rule, bound3) {
16180
+ return rule.sha256 === void 0 || changedFile(rule.sha256, bound3) === void 0;
16181
+ }
16139
16182
  var VISUDO_FILES_MAX = 64;
16140
16183
  var VISUDO_TEXT_MAX = 1024 * 1024;
16141
16184
  function visudoCall(req) {
@@ -16266,11 +16309,13 @@ function usable(parsed2) {
16266
16309
  function policyOf(settings) {
16267
16310
  return { type: settings.timestampType, minutes: settings.timestampTimeout };
16268
16311
  }
16269
- function described(rule) {
16312
+ function described(rule, writable2) {
16270
16313
  const cmnd = rule.cmnd === "*" ? "ALL" : commandText(rule.cmnd);
16271
16314
  const cwd = rule.cwd === void 0 ? "" : `CWD=${quoteWord(rule.cwd)} `;
16272
16315
  const stdin = rule.stdin === void 0 ? "" : "STDIN=none ";
16273
- return ` (${rule.target}) ${cwd}${stdin}${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}`;
16316
+ const sums = Object.entries(rule.sha256 ?? {}).map(([path, hex4]) => `SHA256=${quoteWord(path)}:${hex4} `).join("");
16317
+ const flag = writable2 ? " # user-writable command" : "";
16318
+ return ` (${rule.target}) ${cwd}${stdin}${sums}${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}${flag}`;
16274
16319
  }
16275
16320
  var Sudo = class {
16276
16321
  o;
@@ -16461,15 +16506,31 @@ var Sudo = class {
16461
16506
  if (walked.denied !== void 0 || walked.missing) return false;
16462
16507
  return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
16463
16508
  }
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
- }
16509
+ async real(file) {
16510
+ const { fs } = this.o;
16511
+ return await fs.realpath?.(file, true).catch(() => file) ?? file;
16512
+ }
16513
+ async digests(files) {
16514
+ const { fs } = this.o;
16515
+ const found = {};
16516
+ for (const file of new Set(files)) {
16517
+ if (await this.trusted(file)) continue;
16518
+ const real = await this.real(file);
16519
+ found[real] = await fs.readFileBuffer(real).then(sha2562, () => "");
16471
16520
  }
16472
- return void 0;
16521
+ return found;
16522
+ }
16523
+ async bound(path, cwd) {
16524
+ const files = this.o.backing ? await this.o.backing(path, cwd) : [];
16525
+ const bound3 = files && await this.digests([path, ...files]);
16526
+ return bound3 && !Object.values(bound3).includes("") ? bound3 : void 0;
16527
+ }
16528
+ async writable(rule) {
16529
+ if (rule.cmnd === "*") return false;
16530
+ const { path } = rule.cmnd;
16531
+ const bound3 = await this.bound(path, "/");
16532
+ if (bound3) return Object.keys(bound3).length > 0;
16533
+ return !await this.trusted(await this.existing(path));
16473
16534
  }
16474
16535
  async locate(word, cwd) {
16475
16536
  const { fs } = this.o;
@@ -16514,7 +16575,7 @@ var Sudo = class {
16514
16575
  const root = cred.euid === 0;
16515
16576
  const rules = root ? [] : policy.rules;
16516
16577
  const mine = rules.filter((rule) => applies(rule.who));
16517
- if (parsed2.list) return this.listed(self, root, mine, settings.matching);
16578
+ if (parsed2.list) return await this.listed(self, root, mine, settings.matching);
16518
16579
  const session = { caller, self, target, owner, policy: policyOf(settings), parsed: parsed2, signal };
16519
16580
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
16520
16581
  return this.command(session, root, mine, call);
@@ -16531,14 +16592,13 @@ var Sudo = class {
16531
16592
  );
16532
16593
  }
16533
16594
  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;
16595
+ const bound3 = path && await this.bound(path, cwd);
16596
+ if (!path || !bound3) return failed2("ENOENT", `sudo: ${word}: command not found`);
16537
16597
  const argv = [path, ...args];
16538
16598
  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)
16599
+ (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
16600
  );
16541
- const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
16601
+ const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term, bound3);
16542
16602
  if (root || matching.some((rule) => rule.nopasswd)) return grant();
16543
16603
  const quoted = argv.map(quoteWord).join(" ");
16544
16604
  if (matching.length === 0) {
@@ -16554,14 +16614,15 @@ var Sudo = class {
16554
16614
  }
16555
16615
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
16556
16616
  return this.pendingFor(cred.ruid, async () => {
16557
- const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd });
16617
+ const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd, bound: bound3 });
16558
16618
  if ("error" in answer2) return answer2;
16559
16619
  if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
16560
16620
  return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
16561
16621
  }
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);
16622
+ const now = await this.bound(path, cwd);
16623
+ if (!now) return failed2("ENOENT", `sudo: ${word}: command not found`);
16624
+ const shown2 = recordable(cwd, [...parsed2.command, ...Object.keys(bound3)]);
16625
+ const result = answer2.answer === "once" ? grant() : await this.always(self, target, { cwd, argv, bound: bound3 }, now, shown2, grant);
16565
16626
  if (!("error" in result)) this.stamped(s, answer2);
16566
16627
  return result;
16567
16628
  });
@@ -16580,7 +16641,7 @@ var Sudo = class {
16580
16641
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
16581
16642
  const cwd = await this.canonical(dir) ?? normalizePath(dir);
16582
16643
  return this.pendingFor(s.caller.cred.ruid, async () => {
16583
- const answer2 = await this.ask(s, { argv: [], path: "", cwd, validate: true });
16644
+ const answer2 = await this.ask(s, { argv: [], path: "", cwd, bound: {}, validate: true });
16584
16645
  if ("error" in answer2) return answer2;
16585
16646
  if (!answer2.person) await s.caller.report?.(NOT_STAMPED);
16586
16647
  this.stamped(s, answer2);
@@ -16608,14 +16669,17 @@ var Sudo = class {
16608
16669
  `sudo: ${self.name} : TTY=${tty} ; PWD=${quoteWord(cwd)} ; USER=${target.name} ; TIMESTAMP=${type} ; COMMAND=${command}`
16609
16670
  );
16610
16671
  }
16611
- async always(self, target, cwd, argv, shown2, grant) {
16672
+ async always(self, target, line, now, shown2, grant) {
16673
+ const { cwd, argv, bound: bound3 } = line;
16612
16674
  if (!shown2 || !recordable(cwd, argv)) return { ...grant(), notice: RAN_ONCE };
16613
- if (!await this.remember(self, target, cwd, argv)) {
16675
+ const changed = changedFile(bound3, now);
16676
+ if (changed !== void 0) return failed2("EPERM", changedText(changed));
16677
+ if (!await this.remember(self, target, line)) {
16614
16678
  return failed2("EIO", "sudo: the rule could not be recorded");
16615
16679
  }
16616
16680
  return grant();
16617
16681
  }
16618
- listed(self, root, rules, defaults2) {
16682
+ async listed(self, root, rules, defaults2) {
16619
16683
  const host = this.o.hostname;
16620
16684
  if (root) {
16621
16685
  return {
@@ -16627,7 +16691,9 @@ var Sudo = class {
16627
16691
  if (rules.length === 0) {
16628
16692
  return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
16629
16693
  }
16630
- const lines2 = rules.map(described).join("\n");
16694
+ const shown2 = [];
16695
+ for (const rule of rules) shown2.push(described(rule, await this.writable(rule)));
16696
+ const lines2 = shown2.join("\n");
16631
16697
  const params = defaults2.flatMap((d) => d.params).join(", ");
16632
16698
  const matching = params === "" ? "" : `Matching Defaults entries for ${self.name} on ${host}:
16633
16699
  ${params}
@@ -16660,9 +16726,10 @@ ${lines2}
16660
16726
  ...caller.tty ? { tty: caller.tty } : {},
16661
16727
  createdAt,
16662
16728
  expiresAt: createdAt + timeout,
16663
- always: asked.validate ? false : recordable(cwd, [path, ...argv]),
16729
+ always: asked.validate ? false : recordable(cwd, [path, ...argv, ...Object.keys(asked.bound)]),
16664
16730
  stdin: caller.stdin,
16665
16731
  rootOnly: asked.validate ? {} : await this.rootOnly(cwd, argv.slice(1)),
16732
+ ...userWritable(asked.bound),
16666
16733
  ...asked.validate ? { validate: true } : {},
16667
16734
  timestamp: true
16668
16735
  };
@@ -16682,7 +16749,7 @@ ${lines2}
16682
16749
  requester.removeEventListener("abort", withdraw);
16683
16750
  }
16684
16751
  }
16685
- async remember(self, target, cwd, argv) {
16752
+ async remember(self, target, { cwd, argv, bound: bound3 }) {
16686
16753
  const { fs } = this.o;
16687
16754
  const path = `${SUDOERS_D}/${self.name}`;
16688
16755
  if (!NAME3.test(self.name)) return false;
@@ -16693,7 +16760,7 @@ ${lines2}
16693
16760
  if (st && !await this.trusted(path)) return false;
16694
16761
  const old = st ? await fs.readFile(path) : "";
16695
16762
  const words2 = recordedCommand(argv);
16696
- const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none Cmnd ${words2}
16763
+ const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words2}
16697
16764
  `;
16698
16765
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
16699
16766
  ${line}`;
@@ -16704,7 +16771,7 @@ ${line}`;
16704
16771
  return false;
16705
16772
  }
16706
16773
  }
16707
- grant(self, target, cred, argv, file, cwd, term) {
16774
+ grant(self, target, cred, argv, file, cwd, term, files) {
16708
16775
  const env = {
16709
16776
  ...this.o.env(),
16710
16777
  ...userEnv(target, this.o.pnpmHome),
@@ -16717,7 +16784,9 @@ ${line}`;
16717
16784
  SUDO_GID: String(cred.rgid),
16718
16785
  SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
16719
16786
  };
16720
- return { grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred } };
16787
+ return {
16788
+ grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred, files }
16789
+ };
16721
16790
  }
16722
16791
  };
16723
16792
 
@@ -18407,6 +18476,9 @@ function enableNetwork(net, options) {
18407
18476
  return () => proxy;
18408
18477
  }
18409
18478
 
18479
+ // src/kernel/net/transport.ts
18480
+ var FORBIDDEN_HEADER = "forbidden-header";
18481
+
18410
18482
  // src/kernel/net/ws-syscalls.ts
18411
18483
  var WS_OPS = [
18412
18484
  "net-ws-open",
@@ -18706,6 +18778,9 @@ var HttpHandles = class {
18706
18778
  if (e instanceof PlaceholderRefused) {
18707
18779
  return { ok: false, errno: e.code, message: text, detail: "placeholder" };
18708
18780
  }
18781
+ if (e.detail === FORBIDDEN_HEADER) {
18782
+ return { ok: false, errno: "ENOTSUP", message: text, detail: FORBIDDEN_HEADER };
18783
+ }
18709
18784
  const status = e.status;
18710
18785
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
18711
18786
  const code = codeOf2(e);
@@ -24881,14 +24956,20 @@ var WasmProcess = class {
24881
24956
  if (!("grant" in answer2)) return answer2;
24882
24957
  if (this.dying) throw new KernelError("EINTR");
24883
24958
  if (answer2.notice !== void 0) await this.complainLine(answer2.notice);
24884
- const { file, argv, env, cwd, cred, caller } = answer2.grant;
24959
+ const { file, argv, env, cwd, cred, caller, files } = answer2.grant;
24885
24960
  const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
24886
- return {
24887
- pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
24888
- cred,
24889
- caller
24890
- })
24891
- };
24961
+ try {
24962
+ return {
24963
+ pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
24964
+ cred,
24965
+ caller,
24966
+ files
24967
+ })
24968
+ };
24969
+ } catch (err) {
24970
+ if (err instanceof ChangedError) return { error: err.notice, errno: err.code };
24971
+ throw err;
24972
+ }
24892
24973
  }
24893
24974
  stdinKind() {
24894
24975
  if (!this.fds.has(0)) return "none";
@@ -27179,7 +27260,7 @@ var TransportError = class extends Error {
27179
27260
  super(message3);
27180
27261
  this.status = status;
27181
27262
  this.code = code;
27182
- this.detail = detail === "transport" ? detail : void 0;
27263
+ this.detail = detail === "transport" || detail === FORBIDDEN_HEADER ? detail : void 0;
27183
27264
  }
27184
27265
  };
27185
27266
  function unfollowed2(redirect) {
@@ -32460,6 +32541,10 @@ function describe(req, tty) {
32460
32541
  };
32461
32542
  return { argv: [req.argv0, ...req.args], tty, started: Date.now(), ...exe };
32462
32543
  }
32544
+ function refuseChanged(approved, now) {
32545
+ const changed = changedFile(approved, now);
32546
+ if (changed !== void 0) throw new ChangedError(changedText(changed));
32547
+ }
32463
32548
  function linksOf2(req) {
32464
32549
  return req.links === void 0 ? {} : { links: req.links };
32465
32550
  }
@@ -33593,6 +33678,27 @@ var Launcher = class {
33593
33678
  }
33594
33679
  return true;
33595
33680
  }
33681
+ async unchanged(planned, file, cwd, grant) {
33682
+ const approved = grant?.files;
33683
+ if (!approved) return {};
33684
+ const files = await this.backingOf(planned, file, cwd, "");
33685
+ refuseChanged(approved, await this.sudoer.digests([file, ...files]));
33686
+ return { pinned: approved };
33687
+ }
33688
+ async pinOf(path, caller) {
33689
+ const { pinned: pinned2 } = caller;
33690
+ if (!pinned2) return {};
33691
+ const real = await this.sudoer.real(path);
33692
+ const hex4 = pinned2[real];
33693
+ if (hex4 !== void 0) return { [real]: hex4 };
33694
+ if (await this.sudoer.trusted(real)) return {};
33695
+ throw new ChangedError(changedText(real));
33696
+ }
33697
+ async pinnedBytes(pin, bytes) {
33698
+ const [real = ""] = Object.keys(pin);
33699
+ refuseChanged(pin, { [real]: await sha2562(bytes) });
33700
+ return bytes;
33701
+ }
33596
33702
  async grantedCwd(caller, cwd) {
33597
33703
  const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
33598
33704
  return real === cwd && await this.sudoer.reachable(caller, cwd);
@@ -33681,13 +33787,19 @@ var Launcher = class {
33681
33787
  return found;
33682
33788
  }
33683
33789
  async module(path, caller, asked) {
33684
- const key = await this.cacheKey(path, asked);
33790
+ const pin = await this.pinOf(path, caller);
33791
+ const sum = Object.values(pin)[0];
33792
+ const key = sum === void 0 ? await this.cacheKey(path, asked) : `${path}:sha256:${sum}`;
33685
33793
  return this.cached(this.compiled, key, async () => {
33686
- const { bytes, key: read } = await this.keyed(path, caller, asked);
33794
+ const { bytes: read, key: readKey } = await this.keyed(path, caller, asked);
33795
+ const bytes = sum === void 0 ? read : await this.pinnedBytes(pin, read);
33687
33796
  const memory = importedMemory(bytes);
33688
33797
  const foreign = foreignImports(bytes);
33689
33798
  const compiled = await WebAssembly.compile(bytes);
33690
- return { value: { module: compiled, ...memory ? { memory } : {}, foreign }, key: read };
33799
+ return {
33800
+ value: { module: compiled, ...memory ? { memory } : {}, foreign },
33801
+ key: sum === void 0 ? readKey : key
33802
+ };
33691
33803
  });
33692
33804
  }
33693
33805
  async checkedSources(planned, caller) {
@@ -33712,6 +33824,11 @@ var Launcher = class {
33712
33824
  }
33713
33825
  }
33714
33826
  async text(path, caller, asked) {
33827
+ const pin = await this.pinOf(path, caller);
33828
+ if (Object.keys(pin).length > 0) {
33829
+ const { bytes } = await this.keyed(path, caller, asked);
33830
+ return new TextDecoder().decode(await this.pinnedBytes(pin, bytes));
33831
+ }
33715
33832
  if (!this.integrity) return this.base.readFile(path);
33716
33833
  const key = await this.cacheKey(path, asked);
33717
33834
  return this.cached(this.texts, key, async () => {
@@ -33900,7 +34017,12 @@ var Launcher = class {
33900
34017
  const own = env.HOSTNAME;
33901
34018
  if (asked.target.abi === "wasi") env.HOSTNAME = this.hostname;
33902
34019
  const planned = chosen2(asked, env);
33903
- const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tags?.[0] };
34020
+ const caller = {
34021
+ pid: req.ppid ?? 0,
34022
+ uid: req.cred.euid,
34023
+ tag: req.tags?.[0],
34024
+ pinned: req.pinned
34025
+ };
33904
34026
  if (this.integrity) await this.checkedSources(planned, caller);
33905
34027
  const [program, exeId] = await this.loaded(planned.target, env, caller);
33906
34028
  if (readsEtcHostname(program)) setOrDrop(env, "HOSTNAME", own);
@@ -34415,6 +34537,7 @@ var Launcher = class {
34415
34537
  const scope = grant ? "" : await this.scopeOf(cred.euid);
34416
34538
  const file = booked === void 0 ? req.file : await this.ticketFile(req, fds);
34417
34539
  const planned = await this.checkedPlan({ ...req, file }, { ppid, cred, scope }, fds);
34540
+ const pin = await this.unchanged(planned, file, req.cwd, grant);
34418
34541
  const { recheck, ...redeemed } = await this.redeemed(
34419
34542
  { ...req, file },
34420
34543
  { ppid, cred, scope, booked },
@@ -34426,6 +34549,7 @@ var Launcher = class {
34426
34549
  const replan = () => this.planOrSearch({ ...req, file }, { ppid, cred, scope });
34427
34550
  const again = { ...planned, replan, recheck };
34428
34551
  const handle = await this.launch(again, {
34552
+ ...pin,
34429
34553
  ...deciding ? { decided: deciding.promise } : {},
34430
34554
  cwd: req.cwd,
34431
34555
  cwdFrom: inheritedCwd(req.chdir, this.cwdSlots.get(ppid)),
@@ -35600,6 +35724,44 @@ function sentHeaders(headers, inPage) {
35600
35724
  if (inPage) out.delete("user-agent");
35601
35725
  return out;
35602
35726
  }
35727
+ var TRANSPORT_OWN = /* @__PURE__ */ new Set([
35728
+ "accept-encoding",
35729
+ "connection",
35730
+ "content-length",
35731
+ "expect",
35732
+ "host",
35733
+ "keep-alive",
35734
+ "te",
35735
+ "trailer",
35736
+ "transfer-encoding",
35737
+ "upgrade"
35738
+ ]);
35739
+ var PROBE = "http://forbidden-headers.invalid/";
35740
+ var filters;
35741
+ function platformFilters() {
35742
+ filters ??= !new Request(PROBE, { headers: [["cookie", "probe=1"]] }).headers.has("cookie");
35743
+ return filters;
35744
+ }
35745
+ function unsendableHeaders(url, names2, hint) {
35746
+ const which = names2.length > 1 ? "headers" : "header";
35747
+ return Object.assign(
35748
+ new Error(
35749
+ `fetch ${url} refused: the page's fetch can't send the request ${which} ${names2.join(", ")}, so it was not sent${hint ? `: ${hint}` : ""}`
35750
+ ),
35751
+ { status: 502, code: "ENOTSUP", detail: FORBIDDEN_HEADER, headers: [...names2] }
35752
+ );
35753
+ }
35754
+ function refuseUnsendable(url, out, hint) {
35755
+ if (!platformFilters()) return;
35756
+ const kept = new Request(PROBE, { headers: out }).headers;
35757
+ const refused4 = [];
35758
+ for (const name2 of new Set(out.keys())) {
35759
+ if (kept.has(name2)) continue;
35760
+ if (TRANSPORT_OWN.has(name2)) out.delete(name2);
35761
+ else refused4.push(name2);
35762
+ }
35763
+ if (refused4.length > 0) throw unsendableHeaders(url, refused4, hint);
35764
+ }
35603
35765
  function streamOf(body) {
35604
35766
  const it = body[Symbol.asyncIterator]();
35605
35767
  return new ReadableStream(
@@ -35628,6 +35790,7 @@ function fetchTransport(options = {}) {
35628
35790
  const headers = options.webSocketHeaders === true;
35629
35791
  const instance = options.redirect ?? "follow";
35630
35792
  const inPage = !options.fetch && "location" in globalThis;
35793
+ const refuse2 = (options.forbiddenHeaders ?? (options.fetch ? "send" : "refuse")) === "refuse";
35631
35794
  return {
35632
35795
  traits: {
35633
35796
  manualRedirects: instance === "manual",
@@ -35640,11 +35803,13 @@ function fetchTransport(options = {}) {
35640
35803
  },
35641
35804
  ...Socket ? { websocket: (request) => openWebSocket(Socket, request, headers) } : {},
35642
35805
  async fetch(request) {
35806
+ const sent = sentHeaders(request.headers, inPage);
35807
+ if (refuse2) refuseUnsendable(request.url, sent, options.hint);
35643
35808
  let response;
35644
35809
  try {
35645
35810
  response = await send2(request.url, {
35646
35811
  method: request.method,
35647
- headers: sentHeaders(request.headers, inPage),
35812
+ headers: sent,
35648
35813
  ...requestBody(request),
35649
35814
  signal: request.signal,
35650
35815
  redirect: request.redirect ?? instance,
@@ -35745,7 +35910,7 @@ function serveTransport(peer, transport, {
35745
35910
  message: e instanceof Error ? e.message : String(e),
35746
35911
  ...typeof status === "number" ? { status } : {},
35747
35912
  ...typeof code === "string" ? { code } : {},
35748
- ...detail === "transport" ? { detail } : {}
35913
+ ...detail === "transport" || detail === FORBIDDEN_HEADER ? { detail } : {}
35749
35914
  });
35750
35915
  };
35751
35916
  const uploads = uploadsFor(peer);