@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.
@@ -1339,8 +1339,8 @@ var RemoteWatch = class {
1339
1339
  const { held = [], pending = [] } = await this.locks.query();
1340
1340
  if (![...held, ...pending].some((lock) => lock.name === name2)) {
1341
1341
  this.mourned.delete(peer);
1342
- const live = this.peers.has(peer) || this.flushes.has(peer);
1343
- if (tries > 1 && live) {
1342
+ const live2 = this.peers.has(peer) || this.flushes.has(peer);
1343
+ if (tries > 1 && live2) {
1344
1344
  setTimeout(() => void this.mourn(peer, tries - 1), MOURN_RETRY_MS);
1345
1345
  } else this.flushedAt(peer);
1346
1346
  return;
@@ -1830,11 +1830,11 @@ async function positioned(handle, op) {
1830
1830
  }
1831
1831
  return;
1832
1832
  }
1833
- const writable2 = await handle.createWritable({ keepExistingData: true });
1833
+ const writable3 = await handle.createWritable({ keepExistingData: true });
1834
1834
  try {
1835
1835
  await op({
1836
1836
  write: async (at, bytes) => {
1837
- await writable2.write({
1837
+ await writable3.write({
1838
1838
  type: "write",
1839
1839
  position: at,
1840
1840
  data: bytes
@@ -1842,15 +1842,15 @@ async function positioned(handle, op) {
1842
1842
  extent.wrote(at + bytes.length);
1843
1843
  },
1844
1844
  truncate: async (n) => {
1845
- await writable2.truncate(n);
1845
+ await writable3.truncate(n);
1846
1846
  extent.cut(n);
1847
1847
  }
1848
1848
  });
1849
1849
  } catch (err) {
1850
- await writable2.abort();
1850
+ await writable3.abort();
1851
1851
  throw err;
1852
1852
  }
1853
- await writable2.close();
1853
+ await writable3.close();
1854
1854
  if (extent.big()) extent.check((await handle.getFile()).size);
1855
1855
  }
1856
1856
  function syncAccessHandles(scope = globalThis) {
@@ -2936,21 +2936,21 @@ var OpfsFs = class {
2936
2936
  }
2937
2937
  async writeSession(path, truncate) {
2938
2938
  const [handle, , target] = await this.fileFor(path);
2939
- const writable2 = await this.retrying(
2939
+ const writable3 = await this.retrying(
2940
2940
  path,
2941
2941
  () => this.io(handle.createWritable({ keepExistingData: !truncate })),
2942
2942
  true
2943
2943
  );
2944
2944
  return {
2945
- write: (at, bytes) => writable2.write({ type: "write", position: at, data: bytes }).catch((err) => {
2945
+ write: (at, bytes) => writable3.write({ type: "write", position: at, data: bytes }).catch((err) => {
2946
2946
  throw translate(err, path, "write", false);
2947
2947
  }),
2948
- truncate: (size) => writable2.truncate(size).catch((err) => {
2948
+ truncate: (size) => writable3.truncate(size).catch((err) => {
2949
2949
  throw translate(err, path, "write", false);
2950
2950
  }),
2951
2951
  close: async () => {
2952
2952
  this.wrote(handle);
2953
- await writable2.close().catch((err) => {
2953
+ await writable3.close().catch((err) => {
2954
2954
  this.wrote(handle);
2955
2955
  throw translate(err, path, "commit", false);
2956
2956
  });
@@ -3002,19 +3002,19 @@ var OpfsFs = class {
3002
3002
  const [file, existed, target] = await this.fileFor(path, owner?.existing !== true);
3003
3003
  if (!existed) await this.stampNew(target, owner, false);
3004
3004
  if (!existed && content.length === 0) return;
3005
- let writable2;
3005
+ let writable3;
3006
3006
  this.wrote(file);
3007
3007
  try {
3008
- writable2 = await this.io(file.createWritable());
3009
- await writable2.write(content);
3010
- await writable2.close();
3008
+ writable3 = await this.io(file.createWritable());
3009
+ await writable3.write(content);
3010
+ await writable3.close();
3011
3011
  if (typeof content !== "string" && content.length >= CHECKED_BYTES) {
3012
3012
  const extent = new Extent();
3013
3013
  extent.cut(content.length);
3014
3014
  extent.check((await file.getFile()).size);
3015
3015
  }
3016
3016
  } catch (err) {
3017
- await writable2?.abort().catch(() => void 0);
3017
+ await writable3?.abort().catch(() => void 0);
3018
3018
  throw translate(err, path, "commit", false);
3019
3019
  } finally {
3020
3020
  this.wrote(file);
@@ -3739,7 +3739,7 @@ var OpfsFs = class {
3739
3739
  created4 = true;
3740
3740
  }
3741
3741
  let target = destination;
3742
- let writable2;
3742
+ let writable3;
3743
3743
  let handle;
3744
3744
  try {
3745
3745
  const kept = created4 ? this.files.get(destination) : void 0;
@@ -3758,11 +3758,11 @@ var OpfsFs = class {
3758
3758
  if (entry?.fifo || entry?.socket) throw fsError("ENXIO", to);
3759
3759
  }
3760
3760
  this.wrote(handle);
3761
- writable2 = await handle.createWritable();
3762
- await writable2.write(file);
3763
- await writable2.close();
3761
+ writable3 = await handle.createWritable();
3762
+ await writable3.write(file);
3763
+ await writable3.close();
3764
3764
  } catch (err) {
3765
- await writable2?.abort().catch(() => void 0);
3765
+ await writable3?.abort().catch(() => void 0);
3766
3766
  if (created4) await this.rm(target, { file: true }).catch(() => void 0);
3767
3767
  throw translate(err, to, "commit", false);
3768
3768
  } finally {
@@ -3851,10 +3851,10 @@ var OpfsFs = class {
3851
3851
  }
3852
3852
  if (handle.kind === "file") {
3853
3853
  const made2 = await this.io(dir.getFileHandle(name2, { create: true }));
3854
- const writable2 = await this.io(made2.createWritable());
3854
+ const writable3 = await this.io(made2.createWritable());
3855
3855
  const file = await this.io(handle.getFile());
3856
- await writable2.write(file);
3857
- await writable2.close();
3856
+ await writable3.write(file);
3857
+ await writable3.close();
3858
3858
  copies?.set(rest2, { before: copies.get(rest2)?.before ?? file.lastModified, made: made2 });
3859
3859
  return;
3860
3860
  }
@@ -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.3" : "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"));
@@ -17082,11 +17089,11 @@ function systemFile(name2, listing, now, memory) {
17082
17089
  listing.processes.reduce((sum2, p) => sum2 + p.memory, 0)
17083
17090
  );
17084
17091
  }
17085
- const live = listing.processes.filter((p) => p.state !== "Z");
17092
+ const live2 = listing.processes.filter((p) => p.state !== "Z");
17086
17093
  if (name2 === "loadavg") {
17087
17094
  const last = Math.max(0, ...listing.processes.map((p) => p.pid));
17088
17095
  const [one, five, fifteen] = (listing.load ?? [0, 0, 0]).map((n) => n.toFixed(2));
17089
- return `${one} ${five} ${fifteen} 1/${live.length} ${last}
17096
+ return `${one} ${five} ${fifteen} 1/${live2.length} ${last}
17090
17097
  `;
17091
17098
  }
17092
17099
  const sum = (key) => listing.cpu?.[key] ?? listing.processes.reduce((n, p) => n + (p[key] ?? 0), 0);
@@ -19575,30 +19582,49 @@ function timestampKey(owner, type) {
19575
19582
  }
19576
19583
  return parent ? JSON.stringify(["ppid", parent.pid, parent.started]) : void 0;
19577
19584
  }
19585
+ function boundKey(bound3) {
19586
+ return JSON.stringify(
19587
+ Object.keys(bound3).sort().map((path) => [path, bound3[path]])
19588
+ );
19589
+ }
19590
+ function writable2(bound3) {
19591
+ return bound3 !== void 0 && Object.keys(bound3).length > 0;
19592
+ }
19593
+ function live(stamp, policy, now) {
19594
+ return stamp.at <= now && (policy.minutes < 0 || now - stamp.at < policy.minutes * MINUTE_MS2);
19595
+ }
19578
19596
  var Timestamps = class {
19579
19597
  users = /* @__PURE__ */ new Map();
19580
19598
  now;
19581
19599
  constructor(now) {
19582
19600
  this.now = now;
19583
19601
  }
19584
- current(owner, policy) {
19602
+ current(owner, policy, bound3) {
19585
19603
  if (policy.minutes === 0) return false;
19586
19604
  const key = timestampKey(owner, policy.type);
19587
- const at = key === void 0 ? void 0 : this.users.get(owner.uid)?.get(key);
19588
- if (at === void 0) return false;
19605
+ const stamp = key === void 0 ? void 0 : this.users.get(owner.uid)?.get(key);
19606
+ if (stamp === void 0) return false;
19589
19607
  const now = this.now();
19590
- return at <= now && (policy.minutes < 0 || now - at < policy.minutes * MINUTE_MS2);
19608
+ if (!live(stamp, policy, now)) {
19609
+ this.drop(owner.uid, key);
19610
+ return false;
19611
+ }
19612
+ if (!writable2(bound3)) return true;
19613
+ return stamp.covered.has(boundKey(bound3));
19591
19614
  }
19592
- stamp(owner, policy) {
19615
+ stamp(owner, policy, bound3) {
19593
19616
  const key = policy.minutes === 0 ? void 0 : timestampKey(owner, policy.type);
19594
19617
  if (key === void 0) return false;
19595
19618
  const now = this.now();
19596
19619
  const records = this.users.get(owner.uid) ?? /* @__PURE__ */ new Map();
19597
19620
  this.users.set(owner.uid, records);
19621
+ const prior = records.get(key);
19622
+ const covered = prior !== void 0 && live(prior, policy, now) ? prior.covered : /* @__PURE__ */ new Set();
19623
+ if (writable2(bound3)) covered.add(boundKey(bound3));
19598
19624
  records.delete(key);
19599
- records.set(key, now);
19600
- for (const [old, at] of records) {
19601
- const stale = policy.minutes > 0 && (at > now || now - at >= policy.minutes * MINUTE_MS2);
19625
+ records.set(key, { at: now, covered });
19626
+ for (const [old, stamp] of records) {
19627
+ const stale = policy.minutes > 0 && !live(stamp, policy, now);
19602
19628
  if (stale || records.size > TIMESTAMPS_MAX) records.delete(old);
19603
19629
  }
19604
19630
  return true;
@@ -19612,6 +19638,11 @@ var Timestamps = class {
19612
19638
  remove(uid) {
19613
19639
  this.users.delete(uid);
19614
19640
  }
19641
+ drop(uid, key) {
19642
+ const records = this.users.get(uid);
19643
+ records?.delete(key);
19644
+ if (records?.size === 0) this.users.delete(uid);
19645
+ }
19615
19646
  };
19616
19647
 
19617
19648
  // src/sudoers.ts
@@ -19620,8 +19651,10 @@ var SUDOERS_D = "/etc/sudoers.d";
19620
19651
  var NAME4 = /^[a-z_][a-z0-9_.-]*$/;
19621
19652
  var WORD = String.raw`(?:'[^']*'|"(?:[^"\\]|\\[\s\S])*"|\\[\s\S]|[^\s'"\\])+`;
19622
19653
  var KERNEL_FORM = new RegExp(
19623
- String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()]*)\)\s+)?(?:Cwd\s+(${WORD})\s+)?(?:Stdin\s+(\S+)\s+)?Cmnd(?:\s+(.*))?$`
19654
+ String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()]*)\)\s+)?(?:Cwd\s+(${WORD})\s+)?(?:Stdin\s+(\S+)\s+)?((?:Sha256\s+${WORD}\s+)*)Cmnd(?:\s+(.*))?$`
19624
19655
  );
19656
+ var SHA256_CLAUSE = new RegExp(String.raw`Sha256\s+(${WORD})`, "g");
19657
+ var HEX = /^[0-9a-f]{64}$/;
19625
19658
  var SUDOERS_FORM = /^(\S+)\s+([^\s=]+)\s*=\s*(.*)$/;
19626
19659
  var RUNAS = /^\(([^()]*)\)\s*/;
19627
19660
  var TAG = /^([A-Z_]+)\s*:\s*/;
@@ -19630,7 +19663,7 @@ var PLAIN2 = /^[A-Za-z0-9_@%+=:,./-]+$/;
19630
19663
  var CONTROL2 = /[\p{Cc}\u2028\u2029]/u;
19631
19664
  var CONTROLS = /[\p{Cc}\u2028\u2029]/gu;
19632
19665
  var SHOWN_MAX = 60;
19633
- var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] Cmnd <command\u2026|*>";
19666
+ var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] [Sha256 <file>=<sha256>\u2026] Cmnd <command\u2026|*>";
19634
19667
  var SUDOERS_SYNTAX = "<user|%group|ALL> ALL=[(<target>[,<target>\u2026])] [NOPASSWD:] <command\u2026|ALL>";
19635
19668
  var EXPECTED = `expected '${KERNEL_SYNTAX}' or '${SUDOERS_SYNTAX}'`;
19636
19669
  var TIMESTAMP_TIMEOUT = 5;
@@ -19797,6 +19830,23 @@ function commandOf2(text, after) {
19797
19830
  if (only && /\s""$/.test(text)) return { path, args: [] };
19798
19831
  return args.length === 0 ? { path } : { path, args };
19799
19832
  }
19833
+ function sha256Of(clauses) {
19834
+ const files = {};
19835
+ for (const [, word = ""] of clauses.matchAll(SHA256_CLAUSE)) {
19836
+ const [text = ""] = splitWords(word) ?? [];
19837
+ const at = text.lastIndexOf("=");
19838
+ const path = text.slice(0, at);
19839
+ const hex4 = text.slice(at + 1).toLowerCase();
19840
+ if (!path.startsWith("/") || !HEX.test(hex4)) {
19841
+ return `Sha256 expects <absolute file>=<sha256 in hex>, not ${shown(word)}`;
19842
+ }
19843
+ files[normalizePath(path)] = hex4;
19844
+ }
19845
+ return files;
19846
+ }
19847
+ function sha256Text(files) {
19848
+ return Object.entries(files).map(([path, hex4]) => `Sha256 ${quoteWord(`${path}=${hex4}`)} `).join("");
19849
+ }
19800
19850
  function targetError(list) {
19801
19851
  if (list.includes(",")) {
19802
19852
  return `(${list}) names one user in this form: write one line per user, or use '${SUDOERS_SYNTAX}'`;
@@ -19804,19 +19854,22 @@ function targetError(list) {
19804
19854
  return `expected one user name or ALL in (\u2026), not ${shown(`(${list})`)}`;
19805
19855
  }
19806
19856
  function kernelRules(m) {
19807
- const [, nopasswd, who = "", target = "root", dir, stdin, rest2 = ""] = m;
19857
+ const [, nopasswd, who = "", target = "root", dir, stdin, sums = "", rest2 = ""] = m;
19808
19858
  const bad = whoError(who);
19809
19859
  if (bad !== void 0) return bad;
19810
19860
  if (target !== "ALL" && !NAME4.test(target)) return targetError(target);
19811
19861
  const cwd = dir === void 0 ? void 0 : cwdOf(dir);
19812
19862
  if (cwd === "") return `Cwd expects one absolute directory, not ${shown(dir)}`;
19813
19863
  if (stdin !== void 0 && stdin !== "none") return `Stdin takes none, not ${shown(stdin)}`;
19864
+ const sha2564 = sha256Of(sums);
19865
+ if (typeof sha2564 === "string") return sha2564;
19814
19866
  const rule = {
19815
19867
  nopasswd: !!nopasswd,
19816
19868
  who,
19817
19869
  target,
19818
19870
  ...cwd !== void 0 ? { cwd } : {},
19819
- ...stdin === "none" ? { stdin: "none" } : {}
19871
+ ...stdin === "none" ? { stdin: "none" } : {},
19872
+ ...sums === "" ? {} : { sha256: sha2564 }
19820
19873
  };
19821
19874
  const text = rest2.trim();
19822
19875
  if (text === "*") return [{ ...rule, cmnd: "*" }];
@@ -20069,12 +20122,26 @@ var RAN_ONCE = "sudo: ran once; this command line can't be recorded as always";
20069
20122
  var ROOT_ONLY_MAX = 64;
20070
20123
  var OPERANDS_MAX = 256;
20071
20124
  var PATH_MAX = 4096;
20125
+ function userWritable(bound3) {
20126
+ const files = Object.entries(bound3).map(([path, hex4]) => ({ path, sha256: hex4 }));
20127
+ return files.length > 0 ? { userWritable: files } : {};
20128
+ }
20072
20129
  function stdinMatches(rule, stdin) {
20073
20130
  return rule.stdin === void 0 || stdin === "tty" || stdin === "none";
20074
20131
  }
20075
20132
  function recordable(cwd, argv) {
20076
20133
  return !hasControl(cwd) && !argv.some(hasControl);
20077
20134
  }
20135
+ function changedText(file) {
20136
+ return `sudo: ${file} changed since it was approved`;
20137
+ }
20138
+ function changedFile(approved, now) {
20139
+ const files = /* @__PURE__ */ new Set([...Object.keys(approved), ...Object.keys(now)]);
20140
+ return [...files].find((file) => approved[file] !== now[file]);
20141
+ }
20142
+ function pinned(rule, bound3) {
20143
+ return rule.sha256 === void 0 || changedFile(rule.sha256, bound3) === void 0;
20144
+ }
20078
20145
  var VISUDO_FILES_MAX = 64;
20079
20146
  var VISUDO_TEXT_MAX = 1024 * 1024;
20080
20147
  function visudoCall(req) {
@@ -20205,11 +20272,13 @@ function usable(parsed2) {
20205
20272
  function policyOf(settings) {
20206
20273
  return { type: settings.timestampType, minutes: settings.timestampTimeout };
20207
20274
  }
20208
- function described(rule) {
20275
+ function described(rule, writable3) {
20209
20276
  const cmnd = rule.cmnd === "*" ? "ALL" : commandText(rule.cmnd);
20210
20277
  const cwd = rule.cwd === void 0 ? "" : `CWD=${quoteWord(rule.cwd)} `;
20211
20278
  const stdin = rule.stdin === void 0 ? "" : "STDIN=none ";
20212
- return ` (${rule.target}) ${cwd}${stdin}${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}`;
20279
+ const sums = Object.entries(rule.sha256 ?? {}).map(([path, hex4]) => `SHA256=${quoteWord(path)}:${hex4} `).join("");
20280
+ const flag = writable3 ? " # user-writable command" : "";
20281
+ return ` (${rule.target}) ${cwd}${stdin}${sums}${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}${flag}`;
20213
20282
  }
20214
20283
  var Sudo = class {
20215
20284
  o;
@@ -20400,15 +20469,31 @@ var Sudo = class {
20400
20469
  if (walked.denied !== void 0 || walked.missing) return false;
20401
20470
  return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
20402
20471
  }
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
- }
20472
+ async real(file) {
20473
+ const { fs } = this.o;
20474
+ return await fs.realpath?.(file, true).catch(() => file) ?? file;
20475
+ }
20476
+ async digests(files) {
20477
+ const { fs } = this.o;
20478
+ const found = {};
20479
+ for (const file of new Set(files)) {
20480
+ if (await this.trusted(file)) continue;
20481
+ const real = await this.real(file);
20482
+ found[real] = await fs.readFileBuffer(real).then(sha256, () => "");
20410
20483
  }
20411
- return void 0;
20484
+ return found;
20485
+ }
20486
+ async bound(path, cwd) {
20487
+ const files = this.o.backing ? await this.o.backing(path, cwd) : [];
20488
+ const bound3 = files && await this.digests([path, ...files]);
20489
+ return bound3 && !Object.values(bound3).includes("") ? bound3 : void 0;
20490
+ }
20491
+ async writable(rule) {
20492
+ if (rule.cmnd === "*") return false;
20493
+ const { path } = rule.cmnd;
20494
+ const bound3 = await this.bound(path, "/");
20495
+ if (bound3) return Object.keys(bound3).length > 0;
20496
+ return !await this.trusted(await this.existing(path));
20412
20497
  }
20413
20498
  async locate(word, cwd) {
20414
20499
  const { fs } = this.o;
@@ -20453,7 +20538,7 @@ var Sudo = class {
20453
20538
  const root = cred.euid === 0;
20454
20539
  const rules = root ? [] : policy.rules;
20455
20540
  const mine = rules.filter((rule) => applies(rule.who));
20456
- if (parsed2.list) return this.listed(self, root, mine, settings.matching);
20541
+ if (parsed2.list) return await this.listed(self, root, mine, settings.matching);
20457
20542
  const session = { caller, self, target, owner, policy: policyOf(settings), parsed: parsed2, signal };
20458
20543
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
20459
20544
  return this.command(session, root, mine, call);
@@ -20470,14 +20555,13 @@ var Sudo = class {
20470
20555
  );
20471
20556
  }
20472
20557
  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;
20558
+ const bound3 = path && await this.bound(path, cwd);
20559
+ if (!path || !bound3) return failed2("ENOENT", `sudo: ${word}: command not found`);
20476
20560
  const argv = [path, ...args];
20477
20561
  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)
20562
+ (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
20563
  );
20480
- const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
20564
+ const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term, bound3);
20481
20565
  if (root || matching.some((rule) => rule.nopasswd)) return grant();
20482
20566
  const quoted = argv.map(quoteWord).join(" ");
20483
20567
  if (matching.length === 0) {
@@ -20486,22 +20570,23 @@ var Sudo = class {
20486
20570
  `sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
20487
20571
  );
20488
20572
  }
20489
- if (!parsed2.reset && this.timestamps.current(s.owner, s.policy)) {
20490
- this.timestamps.stamp(s.owner, s.policy);
20573
+ if (!parsed2.reset && this.timestamps.current(s.owner, s.policy, bound3)) {
20574
+ this.timestamps.stamp(s.owner, s.policy, bound3);
20491
20575
  this.logged(s, cwd, argv);
20492
20576
  return grant();
20493
20577
  }
20494
20578
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
20495
20579
  return this.pendingFor(cred.ruid, async () => {
20496
- const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd });
20580
+ const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd, bound: bound3 });
20497
20581
  if ("error" in answer2) return answer2;
20498
20582
  if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
20499
20583
  return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
20500
20584
  }
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);
20504
- if (!("error" in result)) this.stamped(s, answer2);
20585
+ const now = await this.bound(path, cwd);
20586
+ if (!now) return failed2("ENOENT", `sudo: ${word}: command not found`);
20587
+ const shown2 = recordable(cwd, [...parsed2.command, ...Object.keys(bound3)]);
20588
+ const result = answer2.answer === "once" ? grant() : await this.always(self, target, { cwd, argv, bound: bound3 }, now, shown2, grant);
20589
+ if (!("error" in result)) this.stamped(s, answer2, bound3);
20505
20590
  return result;
20506
20591
  });
20507
20592
  }
@@ -20519,7 +20604,7 @@ var Sudo = class {
20519
20604
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
20520
20605
  const cwd = await this.canonical(dir) ?? normalizePath(dir);
20521
20606
  return this.pendingFor(s.caller.cred.ruid, async () => {
20522
- const answer2 = await this.ask(s, { argv: [], path: "", cwd, validate: true });
20607
+ const answer2 = await this.ask(s, { argv: [], path: "", cwd, bound: {}, validate: true });
20523
20608
  if ("error" in answer2) return answer2;
20524
20609
  if (!answer2.person) await s.caller.report?.(NOT_STAMPED);
20525
20610
  this.stamped(s, answer2);
@@ -20535,8 +20620,8 @@ var Sudo = class {
20535
20620
  this.pending.delete(uid);
20536
20621
  }
20537
20622
  }
20538
- stamped(s, answer2) {
20539
- if (answer2.person && !s.parsed.reset) this.timestamps.stamp(s.owner, s.policy);
20623
+ stamped(s, answer2, bound3 = {}) {
20624
+ if (answer2.person && !s.parsed.reset) this.timestamps.stamp(s.owner, s.policy, bound3);
20540
20625
  }
20541
20626
  logged(s, cwd, argv) {
20542
20627
  const { caller, self, target, owner, policy } = s;
@@ -20547,14 +20632,17 @@ var Sudo = class {
20547
20632
  `sudo: ${self.name} : TTY=${tty} ; PWD=${quoteWord(cwd)} ; USER=${target.name} ; TIMESTAMP=${type} ; COMMAND=${command}`
20548
20633
  );
20549
20634
  }
20550
- async always(self, target, cwd, argv, shown2, grant) {
20635
+ async always(self, target, line, now, shown2, grant) {
20636
+ const { cwd, argv, bound: bound3 } = line;
20551
20637
  if (!shown2 || !recordable(cwd, argv)) return { ...grant(), notice: RAN_ONCE };
20552
- if (!await this.remember(self, target, cwd, argv)) {
20638
+ const changed = changedFile(bound3, now);
20639
+ if (changed !== void 0) return failed2("EPERM", changedText(changed));
20640
+ if (!await this.remember(self, target, line)) {
20553
20641
  return failed2("EIO", "sudo: the rule could not be recorded");
20554
20642
  }
20555
20643
  return grant();
20556
20644
  }
20557
- listed(self, root, rules, defaults2) {
20645
+ async listed(self, root, rules, defaults2) {
20558
20646
  const host = this.o.hostname;
20559
20647
  if (root) {
20560
20648
  return {
@@ -20566,7 +20654,9 @@ var Sudo = class {
20566
20654
  if (rules.length === 0) {
20567
20655
  return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
20568
20656
  }
20569
- const lines2 = rules.map(described).join("\n");
20657
+ const shown2 = [];
20658
+ for (const rule of rules) shown2.push(described(rule, await this.writable(rule)));
20659
+ const lines2 = shown2.join("\n");
20570
20660
  const params = defaults2.flatMap((d) => d.params).join(", ");
20571
20661
  const matching = params === "" ? "" : `Matching Defaults entries for ${self.name} on ${host}:
20572
20662
  ${params}
@@ -20599,9 +20689,10 @@ ${lines2}
20599
20689
  ...caller.tty ? { tty: caller.tty } : {},
20600
20690
  createdAt,
20601
20691
  expiresAt: createdAt + timeout,
20602
- always: asked.validate ? false : recordable(cwd, [path, ...argv]),
20692
+ always: asked.validate ? false : recordable(cwd, [path, ...argv, ...Object.keys(asked.bound)]),
20603
20693
  stdin: caller.stdin,
20604
20694
  rootOnly: asked.validate ? {} : await this.rootOnly(cwd, argv.slice(1)),
20695
+ ...userWritable(asked.bound),
20605
20696
  ...asked.validate ? { validate: true } : {},
20606
20697
  timestamp: true
20607
20698
  };
@@ -20621,7 +20712,7 @@ ${lines2}
20621
20712
  requester.removeEventListener("abort", withdraw);
20622
20713
  }
20623
20714
  }
20624
- async remember(self, target, cwd, argv) {
20715
+ async remember(self, target, { cwd, argv, bound: bound3 }) {
20625
20716
  const { fs } = this.o;
20626
20717
  const path = `${SUDOERS_D}/${self.name}`;
20627
20718
  if (!NAME4.test(self.name)) return false;
@@ -20632,7 +20723,7 @@ ${lines2}
20632
20723
  if (st && !await this.trusted(path)) return false;
20633
20724
  const old = st ? await fs.readFile(path) : "";
20634
20725
  const words2 = recordedCommand(argv);
20635
- const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none Cmnd ${words2}
20726
+ const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words2}
20636
20727
  `;
20637
20728
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
20638
20729
  ${line}`;
@@ -20643,7 +20734,7 @@ ${line}`;
20643
20734
  return false;
20644
20735
  }
20645
20736
  }
20646
- grant(self, target, cred, argv, file, cwd, term) {
20737
+ grant(self, target, cred, argv, file, cwd, term, files) {
20647
20738
  const env = {
20648
20739
  ...this.o.env(),
20649
20740
  ...userEnv(target, this.o.pnpmHome),
@@ -20656,7 +20747,9 @@ ${line}`;
20656
20747
  SUDO_GID: String(cred.rgid),
20657
20748
  SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
20658
20749
  };
20659
- return { grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred } };
20750
+ return {
20751
+ grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred, files }
20752
+ };
20660
20753
  }
20661
20754
  };
20662
20755
 
@@ -26885,18 +26978,18 @@ var VfsNodes = class {
26885
26978
  if (id2 !== void 0) this.know(node, id2);
26886
26979
  return node;
26887
26980
  }
26888
- open(path, pin, writable2 = false, id2, ino) {
26981
+ open(path, pin, writable3 = false, id2, ino) {
26889
26982
  const found = this.named(path, id2);
26890
26983
  const same3 = !found || ino === void 0 || found.ino === void 0 || found.ino === ino;
26891
26984
  if (found && !same3) this.orphan(found);
26892
26985
  const node = (same3 ? found : void 0) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
26893
26986
  node.ino ??= ino;
26894
26987
  node.opens++;
26895
- if (writable2) node.writers++;
26988
+ if (writable3) node.writers++;
26896
26989
  if (node.opens === 1) this.opened?.(node);
26897
26990
  return node;
26898
26991
  }
26899
- adopt(path, contents2, dirty, writable2 = false, id2) {
26992
+ adopt(path, contents2, dirty, writable3 = false, id2) {
26900
26993
  let node = this.named(path, id2);
26901
26994
  if (!node) {
26902
26995
  node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
@@ -26904,7 +26997,7 @@ var VfsNodes = class {
26904
26997
  node.replace(contents2);
26905
26998
  }
26906
26999
  node.opens++;
26907
- if (writable2) node.writers++;
27000
+ if (writable3) node.writers++;
26908
27001
  if (node.opens === 1) this.opened?.(node);
26909
27002
  return node;
26910
27003
  }
@@ -26917,11 +27010,11 @@ var VfsNodes = class {
26917
27010
  if (node.writers > 0 && within9(path, prefix)) return true;
26918
27011
  return false;
26919
27012
  }
26920
- closed(node, writable2 = false) {
27013
+ closed(node, writable3 = false) {
26921
27014
  node.opens--;
26922
- if (writable2) node.writers--;
27015
+ if (writable3) node.writers--;
26923
27016
  if (node.opens > 0) {
26924
- if (writable2 && node.writers === 0) this.onClosed?.(node.path);
27017
+ if (writable3 && node.writers === 0) this.onClosed?.(node.path);
26925
27018
  return;
26926
27019
  }
26927
27020
  this.forget(node);
@@ -27240,8 +27333,8 @@ function keepingOpen(fs, nodes, changed = () => {
27240
27333
  function vfsFile(fs, opts, nodes) {
27241
27334
  const access2 = opts.flags & O_ACCMODE;
27242
27335
  const readable2 = access2 !== O_WRONLY;
27243
- const writable2 = access2 === O_WRONLY || access2 === O_RDWR;
27244
- 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);
27336
+ const writable3 = access2 === O_WRONLY || access2 === O_RDWR;
27337
+ 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);
27245
27338
  let openError;
27246
27339
  const atOpen = (op) => {
27247
27340
  void node.serial(op).catch((err) => {
@@ -27268,7 +27361,7 @@ function vfsFile(fs, opts, nodes) {
27268
27361
  offset += out.length;
27269
27362
  return out;
27270
27363
  }) : void 0,
27271
- write: writable2 ? (bytes) => serial(async () => {
27364
+ write: writable3 ? (bytes) => serial(async () => {
27272
27365
  if (opts.flags & O_APPEND) offset = await node.append(bytes);
27273
27366
  else offset += await node.pwrite(bytes, offset);
27274
27367
  return bytes.length;
@@ -27287,11 +27380,11 @@ function vfsFile(fs, opts, nodes) {
27287
27380
  return node.pread(max, at);
27288
27381
  }),
27289
27382
  pwrite: (bytes, at) => serial(() => {
27290
- if (!writable2) throw new KernelError("EBADF");
27383
+ if (!writable3) throw new KernelError("EBADF");
27291
27384
  return node.pwrite(bytes, at);
27292
27385
  }),
27293
27386
  resize: (size) => serial(() => {
27294
- if (!writable2) throw new KernelError("EBADF");
27387
+ if (!writable3) throw new KernelError("EBADF");
27295
27388
  return node.truncate(size);
27296
27389
  }),
27297
27390
  stat: () => serial(async () => ({
@@ -27307,7 +27400,7 @@ function vfsFile(fs, opts, nodes) {
27307
27400
  takeOpenError();
27308
27401
  await node.flush();
27309
27402
  } finally {
27310
- nodes?.closed(node, writable2);
27403
+ nodes?.closed(node, writable3);
27311
27404
  }
27312
27405
  })
27313
27406
  });
@@ -28983,14 +29076,20 @@ var WasmProcess = class {
28983
29076
  if (!("grant" in answer2)) return answer2;
28984
29077
  if (this.dying) throw new KernelError("EINTR");
28985
29078
  if (answer2.notice !== void 0) await this.complainLine(answer2.notice);
28986
- const { file, argv, env, cwd, cred, caller } = answer2.grant;
29079
+ const { file, argv, env, cwd, cred, caller, files } = answer2.grant;
28987
29080
  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
- };
29081
+ try {
29082
+ return {
29083
+ pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
29084
+ cred,
29085
+ caller,
29086
+ files
29087
+ })
29088
+ };
29089
+ } catch (err) {
29090
+ if (err instanceof ChangedError) return { error: err.notice, errno: err.code };
29091
+ throw err;
29092
+ }
28994
29093
  }
28995
29094
  stdinKind() {
28996
29095
  if (!this.fds.has(0)) return "none";
@@ -29030,8 +29129,8 @@ var WasmProcess = class {
29030
29129
  };
29031
29130
  withDl(state) {
29032
29131
  if (this.dlLog.length === 0) return state;
29033
- const { next: next3, handle, free, live } = this.closures;
29034
- const dl = { log: [...this.dlLog], next: next3, handle, free: [...free], live: [...live] };
29132
+ const { next: next3, handle, free, live: live2 } = this.closures;
29133
+ const dl = { log: [...this.dlLog], next: next3, handle, free: [...free], live: [...live2] };
29035
29134
  return { ...state, dl };
29036
29135
  }
29037
29136
  seedDl(dl) {
@@ -32255,7 +32354,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32255
32354
  if (remote.client.token === remote.token && !remote.client.gone) return remote;
32256
32355
  throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
32257
32356
  };
32258
- const live = (fh) => {
32357
+ const live2 = (fh) => {
32259
32358
  const remote = handles.get(fh);
32260
32359
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
32261
32360
  return fresh3(remote);
@@ -32274,7 +32373,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32274
32373
  }, againMs);
32275
32374
  timer.unref?.();
32276
32375
  });
32277
- async function writable2(client, path, flags2) {
32376
+ async function writable3(client, path, flags2) {
32278
32377
  const { ifMatch: _, ...asked } = flags2;
32279
32378
  const opening2 = () => client.call("open", { path: rel(path), ...asked });
32280
32379
  try {
@@ -32305,7 +32404,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32305
32404
  async open(path, flags2) {
32306
32405
  const client = current();
32307
32406
  const token = client.token;
32308
- const { opened: opened2, restore } = await writable2(client, path, flags2);
32407
+ const { opened: opened2, restore } = await writable3(client, path, flags2);
32309
32408
  if (flags2.ifMatch !== void 0 && opened2.attr?.etag !== flags2.ifMatch) {
32310
32409
  await client.call("release", { fh: opened2.fh }, true);
32311
32410
  throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
@@ -32320,7 +32419,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32320
32419
  return nextFh;
32321
32420
  },
32322
32421
  async read(fh, offset, size) {
32323
- const remote = live(fh);
32422
+ const remote = live2(fh);
32324
32423
  return remote.client.read(
32325
32424
  remote.fh,
32326
32425
  offset,
@@ -32329,7 +32428,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32329
32428
  );
32330
32429
  },
32331
32430
  async write(fh, offset, bytes) {
32332
- const remote = live(fh);
32431
+ const remote = live2(fh);
32333
32432
  const most = remote.client.maxIo;
32334
32433
  for (let at = 0; at < bytes.length; at += most) {
32335
32434
  await remote.client.write(remote.fh, offset + at, bytes.subarray(at, at + most));
@@ -33569,6 +33668,10 @@ function describe(req, tty) {
33569
33668
  };
33570
33669
  return { argv: [req.argv0, ...req.args], tty, started: Date.now(), ...exe };
33571
33670
  }
33671
+ function refuseChanged(approved, now) {
33672
+ const changed = changedFile(approved, now);
33673
+ if (changed !== void 0) throw new ChangedError(changedText(changed));
33674
+ }
33572
33675
  function linksOf2(req) {
33573
33676
  return req.links === void 0 ? {} : { links: req.links };
33574
33677
  }
@@ -34702,6 +34805,27 @@ var Launcher = class {
34702
34805
  }
34703
34806
  return true;
34704
34807
  }
34808
+ async unchanged(planned, file, cwd, grant) {
34809
+ const approved = grant?.files;
34810
+ if (!approved) return {};
34811
+ const files = await this.backingOf(planned, file, cwd, "");
34812
+ refuseChanged(approved, await this.sudoer.digests([file, ...files]));
34813
+ return { pinned: approved };
34814
+ }
34815
+ async pinOf(path, caller) {
34816
+ const { pinned: pinned2 } = caller;
34817
+ if (!pinned2) return {};
34818
+ const real = await this.sudoer.real(path);
34819
+ const hex4 = pinned2[real];
34820
+ if (hex4 !== void 0) return { [real]: hex4 };
34821
+ if (await this.sudoer.trusted(real)) return {};
34822
+ throw new ChangedError(changedText(real));
34823
+ }
34824
+ async pinnedBytes(pin, bytes) {
34825
+ const [real = ""] = Object.keys(pin);
34826
+ refuseChanged(pin, { [real]: await sha256(bytes) });
34827
+ return bytes;
34828
+ }
34705
34829
  async grantedCwd(caller, cwd) {
34706
34830
  const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
34707
34831
  return real === cwd && await this.sudoer.reachable(caller, cwd);
@@ -34790,13 +34914,19 @@ var Launcher = class {
34790
34914
  return found;
34791
34915
  }
34792
34916
  async module(path, caller, asked) {
34793
- const key = await this.cacheKey(path, asked);
34917
+ const pin = await this.pinOf(path, caller);
34918
+ const sum = Object.values(pin)[0];
34919
+ const key = sum === void 0 ? await this.cacheKey(path, asked) : `${path}:sha256:${sum}`;
34794
34920
  return this.cached(this.compiled, key, async () => {
34795
- const { bytes, key: read } = await this.keyed(path, caller, asked);
34921
+ const { bytes: read, key: readKey } = await this.keyed(path, caller, asked);
34922
+ const bytes = sum === void 0 ? read : await this.pinnedBytes(pin, read);
34796
34923
  const memory = importedMemory(bytes);
34797
34924
  const foreign = foreignImports(bytes);
34798
34925
  const compiled = await WebAssembly.compile(bytes);
34799
- return { value: { module: compiled, ...memory ? { memory } : {}, foreign }, key: read };
34926
+ return {
34927
+ value: { module: compiled, ...memory ? { memory } : {}, foreign },
34928
+ key: sum === void 0 ? readKey : key
34929
+ };
34800
34930
  });
34801
34931
  }
34802
34932
  async checkedSources(planned, caller) {
@@ -34821,6 +34951,11 @@ var Launcher = class {
34821
34951
  }
34822
34952
  }
34823
34953
  async text(path, caller, asked) {
34954
+ const pin = await this.pinOf(path, caller);
34955
+ if (Object.keys(pin).length > 0) {
34956
+ const { bytes } = await this.keyed(path, caller, asked);
34957
+ return new TextDecoder().decode(await this.pinnedBytes(pin, bytes));
34958
+ }
34824
34959
  if (!this.integrity) return this.base.readFile(path);
34825
34960
  const key = await this.cacheKey(path, asked);
34826
34961
  return this.cached(this.texts, key, async () => {
@@ -35009,7 +35144,12 @@ var Launcher = class {
35009
35144
  const own = env.HOSTNAME;
35010
35145
  if (asked.target.abi === "wasi") env.HOSTNAME = this.hostname;
35011
35146
  const planned = chosen2(asked, env);
35012
- const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tags?.[0] };
35147
+ const caller = {
35148
+ pid: req.ppid ?? 0,
35149
+ uid: req.cred.euid,
35150
+ tag: req.tags?.[0],
35151
+ pinned: req.pinned
35152
+ };
35013
35153
  if (this.integrity) await this.checkedSources(planned, caller);
35014
35154
  const [program, exeId] = await this.loaded(planned.target, env, caller);
35015
35155
  if (readsEtcHostname(program)) setOrDrop(env, "HOSTNAME", own);
@@ -35524,6 +35664,7 @@ var Launcher = class {
35524
35664
  const scope = grant ? "" : await this.scopeOf(cred.euid);
35525
35665
  const file = booked === void 0 ? req.file : await this.ticketFile(req, fds);
35526
35666
  const planned = await this.checkedPlan({ ...req, file }, { ppid, cred, scope }, fds);
35667
+ const pin = await this.unchanged(planned, file, req.cwd, grant);
35527
35668
  const { recheck, ...redeemed } = await this.redeemed(
35528
35669
  { ...req, file },
35529
35670
  { ppid, cred, scope, booked },
@@ -35535,6 +35676,7 @@ var Launcher = class {
35535
35676
  const replan = () => this.planOrSearch({ ...req, file }, { ppid, cred, scope });
35536
35677
  const again = { ...planned, replan, recheck };
35537
35678
  const handle = await this.launch(again, {
35679
+ ...pin,
35538
35680
  ...deciding ? { decided: deciding.promise } : {},
35539
35681
  cwd: req.cwd,
35540
35682
  cwdFrom: inheritedCwd(req.chdir, this.cwdSlots.get(ppid)),