@ai-ecoverse/slicc-kernel 1.176.1 → 1.177.0

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
@@ -876,14 +876,21 @@ async function ownerOnly(g, op, path, follow2) {
876
876
  return walked;
877
877
  }
878
878
  async function chmodMode(g, path, mode) {
879
- const perm = (await ownerOnly(g, "chmod", path, true)).perm;
880
- if (!g.dac.enforcing || !perm || g.cred.euid === 0 || inGroup(g.cred, perm.gid)) return mode;
879
+ const walked = await need(g, "chmod", path, 0, { follow: true });
880
+ const perm = walked.perm;
881
+ const st = await g.fs.stat(walked.path).catch(() => void 0);
882
+ if (!st?.fat && perm && g.cred.euid !== 0 && perm.uid !== g.cred.euid) {
883
+ if (await present(g.fs, walked.path, false)) deny(g, "chmod", path, "EPERM");
884
+ }
885
+ if (!g.dac.enforcing || !perm || g.cred.euid === 0 || st?.fat || inGroup(g.cred, perm.gid)) {
886
+ return mode;
887
+ }
881
888
  return mode & ~S_ISGID2;
882
889
  }
883
890
  function heldChmodMode(g, path, st, mode) {
884
891
  const perm = permOf(st);
885
- if (g.cred.euid !== 0 && perm.uid !== g.cred.euid) deny(g, "fchmod", path, "EPERM");
886
- if (!g.dac.enforcing || g.cred.euid === 0 || inGroup(g.cred, perm.gid)) return mode;
892
+ if (!st.fat && g.cred.euid !== 0 && perm.uid !== g.cred.euid) deny(g, "fchmod", path, "EPERM");
893
+ if (!g.dac.enforcing || g.cred.euid === 0 || st.fat || inGroup(g.cred, perm.gid)) return mode;
887
894
  return mode & ~S_ISGID2;
888
895
  }
889
896
  function heldRetimable(g, path, st, now) {
@@ -3702,7 +3709,7 @@ function satisfies(version, range2) {
3702
3709
  }
3703
3710
 
3704
3711
  // src/version.ts
3705
- var KERNEL_VERSION = true ? "1.176.1" : "0.0.0-dev";
3712
+ var KERNEL_VERSION = true ? "1.177.0" : "0.0.0-dev";
3706
3713
  var OS_RELEASE = "6.0.0-slicc";
3707
3714
  function procVersion() {
3708
3715
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9599,10 +9606,15 @@ var DriverConnection = class _DriverConnection {
9599
9606
  // src/mount/mount-fs.ts
9600
9607
  var S_ISGID3 = 1024;
9601
9608
  var ID = /^\d{1,10}$/;
9609
+ var MASK = /^0*[0-7]{1,4}$/;
9602
9610
  var MAX_ID2 = 4294967294;
9603
9611
  function idOption(value2) {
9604
9612
  return value2 === void 0 ? 0 : Number(value2);
9605
9613
  }
9614
+ function maskOption(value2) {
9615
+ if (value2 === void 0) return void 0;
9616
+ return Number.parseInt(value2, 8) & 511;
9617
+ }
9606
9618
  function checkIds(options) {
9607
9619
  for (const key of ["uid", "gid"]) {
9608
9620
  const value2 = options[key];
@@ -9610,6 +9622,27 @@ function checkIds(options) {
9610
9622
  if (!ID.test(value2) || Number(value2) > MAX_ID2) throw errnoError("EINVAL", `${key}=${value2}`);
9611
9623
  }
9612
9624
  }
9625
+ function checkMasks(options) {
9626
+ for (const key of ["umask", "fmask", "dmask"]) {
9627
+ const value2 = options[key];
9628
+ if (value2 === void 0) continue;
9629
+ if (!MASK.test(value2) || Number.parseInt(value2, 8) > 511) {
9630
+ throw errnoError("EINVAL", `${key}=${value2}`);
9631
+ }
9632
+ }
9633
+ }
9634
+ function modeOf(attr2, mount) {
9635
+ const type = attr2.kind === "directory" ? 16384 : attr2.kind === "symlink" ? 40960 : attr2.kind === "fifo" ? 4096 : 32768;
9636
+ const fallback = type | (attr2.kind === "directory" ? 493 : attr2.kind === "symlink" ? 511 : 420);
9637
+ if (mount.caps.owners) return attr2.mode ?? fallback;
9638
+ const umask = maskOption(mount.options.umask);
9639
+ const fmask = maskOption(mount.options.fmask) ?? umask;
9640
+ const dmask = maskOption(mount.options.dmask) ?? umask;
9641
+ const mask = attr2.kind === "directory" ? dmask : fmask;
9642
+ if (mask === void 0) return attr2.mode ?? fallback;
9643
+ const base = attr2.kind === "directory" || attr2.kind === "symlink" ? 511 : 438;
9644
+ return type | base & ~mask;
9645
+ }
9613
9646
  var MAX_LINKS5 = 40;
9614
9647
  var STALE_RETRIES = 3;
9615
9648
  var SETTLE_ROUNDS = 4;
@@ -9876,14 +9909,13 @@ function inodeless(mount, attr2) {
9876
9909
  }
9877
9910
  function statOf2(attr2, path, mount) {
9878
9911
  const mtime = new Date(attr2.mtime);
9879
- const fallback = attr2.kind === "directory" ? 16877 : attr2.kind === "symlink" ? 41471 : attr2.kind === "fifo" ? 4516 : 33188;
9880
9912
  return {
9881
9913
  isFile: attr2.kind === "file",
9882
9914
  isDirectory: attr2.kind === "directory",
9883
9915
  isSymbolicLink: attr2.kind === "symlink",
9884
9916
  ...attr2.kind === "fifo" ? { isFifo: true } : {},
9885
9917
  size: attr2.size,
9886
- mode: attr2.mode ?? fallback,
9918
+ mode: modeOf(attr2, mount),
9887
9919
  mtime,
9888
9920
  atime: attr2.atime === void 0 ? mtime : new Date(attr2.atime),
9889
9921
  ctime: mtime,
@@ -9897,7 +9929,8 @@ function statOf2(attr2, path, mount) {
9897
9929
  ...attr2.etag !== void 0 ? { version: attr2.etag } : {},
9898
9930
  ...mount.owners ? {
9899
9931
  uid: (mount.caps.owners ? attr2.uid : void 0) ?? idOption(mount.options.uid),
9900
- gid: (mount.caps.owners ? attr2.gid : void 0) ?? idOption(mount.options.gid)
9932
+ gid: (mount.caps.owners ? attr2.gid : void 0) ?? idOption(mount.options.gid),
9933
+ ...!mount.caps.owners && !mount.caps.chmod && (mount.options.umask !== void 0 || mount.options.fmask !== void 0 || mount.options.dmask !== void 0) ? { fat: true } : {}
9901
9934
  } : {}
9902
9935
  };
9903
9936
  }
@@ -10004,6 +10037,7 @@ var MountTable = class {
10004
10037
  if (!st.isDirectory) throw errnoError("ENOTDIR", target);
10005
10038
  const options = { ...spec.options };
10006
10039
  if (this.deps.owners) checkIds(options);
10040
+ checkMasks(options);
10007
10041
  const maxFile = options.maxfile !== void 0 ? parseSize(options.maxfile) : void 0;
10008
10042
  const opened2 = await this.deps.open(spec.type, { ...spec, target, options });
10009
10043
  let mounted;
@@ -14454,8 +14488,8 @@ var SCRUB_IDLE_MS = 50;
14454
14488
  var SCRUB_NONBLOCK_MAX = 64 * 1024;
14455
14489
  var encoder5 = new TextEncoder();
14456
14490
  function scrubberOf(secrets, sameLength = false) {
14457
- const usable2 = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
14458
- return usable2.length > 0 ? compile(usable2, { idleMs: SCRUB_IDLE_MS, sameLength }) : void 0;
14491
+ const usable3 = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
14492
+ return usable3.length > 0 ? compile(usable3, { idleMs: SCRUB_IDLE_MS, sameLength }) : void 0;
14459
14493
  }
14460
14494
  async function writeAll(file, bytes) {
14461
14495
  const write = file.write;
@@ -15094,6 +15128,56 @@ var SealedBroker = class {
15094
15128
  }
15095
15129
  };
15096
15130
 
15131
+ // src/sudo-timestamp.ts
15132
+ var TIMESTAMPS_MAX = 64;
15133
+ var MINUTE_MS2 = 6e4;
15134
+ function timestampKey(owner, type) {
15135
+ if (type === "global") return "global";
15136
+ const { terminal, parent } = owner;
15137
+ if (type === "tty" && terminal) {
15138
+ return JSON.stringify(["tty", terminal.tty, terminal.sid, terminal.started]);
15139
+ }
15140
+ return parent ? JSON.stringify(["ppid", parent.pid, parent.started]) : void 0;
15141
+ }
15142
+ var Timestamps = class {
15143
+ users = /* @__PURE__ */ new Map();
15144
+ now;
15145
+ constructor(now) {
15146
+ this.now = now;
15147
+ }
15148
+ current(owner, policy) {
15149
+ if (policy.minutes === 0) return false;
15150
+ const key = timestampKey(owner, policy.type);
15151
+ const at = key === void 0 ? void 0 : this.users.get(owner.uid)?.get(key);
15152
+ if (at === void 0) return false;
15153
+ const now = this.now();
15154
+ return at <= now && (policy.minutes < 0 || now - at < policy.minutes * MINUTE_MS2);
15155
+ }
15156
+ stamp(owner, policy) {
15157
+ const key = policy.minutes === 0 ? void 0 : timestampKey(owner, policy.type);
15158
+ if (key === void 0) return false;
15159
+ const now = this.now();
15160
+ const records = this.users.get(owner.uid) ?? /* @__PURE__ */ new Map();
15161
+ this.users.set(owner.uid, records);
15162
+ records.delete(key);
15163
+ records.set(key, now);
15164
+ for (const [old, at] of records) {
15165
+ const stale = policy.minutes > 0 && (at > now || now - at >= policy.minutes * MINUTE_MS2);
15166
+ if (stale || records.size > TIMESTAMPS_MAX) records.delete(old);
15167
+ }
15168
+ return true;
15169
+ }
15170
+ invalidate(owner, type) {
15171
+ const key = timestampKey(owner, type);
15172
+ const records = this.users.get(owner.uid);
15173
+ if (key !== void 0) records?.delete(key);
15174
+ if (records?.size === 0) this.users.delete(owner.uid);
15175
+ }
15176
+ remove(uid) {
15177
+ this.users.delete(uid);
15178
+ }
15179
+ };
15180
+
15097
15181
  // src/sudoers.ts
15098
15182
  var SUDOERS = "/etc/sudoers";
15099
15183
  var SUDOERS_D = "/etc/sudoers.d";
@@ -15113,6 +15197,8 @@ var SHOWN_MAX = 60;
15113
15197
  var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] Cmnd <command\u2026|*>";
15114
15198
  var SUDOERS_SYNTAX = "<user|%group|ALL> ALL=[(<target>[,<target>\u2026])] [NOPASSWD:] <command\u2026|ALL>";
15115
15199
  var EXPECTED = `expected '${KERNEL_SYNTAX}' or '${SUDOERS_SYNTAX}'`;
15200
+ var TIMESTAMP_TIMEOUT = 5;
15201
+ var TIMESTAMP_TYPE = "tty";
15116
15202
  function hasControl(text) {
15117
15203
  return CONTROL.test(text);
15118
15204
  }
@@ -15395,9 +15481,6 @@ function directiveError(text) {
15395
15481
  return /^#-?\d+\s+\S+\s*=/.test(text) ? "a #uid isn't supported: name the user, %group or ALL" : null;
15396
15482
  }
15397
15483
  const first = /^\S+/.exec(text)?.[0] ?? "";
15398
- if (/^Defaults([:@>!]|$)/.test(first)) {
15399
- return "Defaults aren't supported: the kernel's sudo has no settings, so this changes nothing";
15400
- }
15401
15484
  if (/^[A-Z][A-Za-z]*_Alias$/.test(first)) {
15402
15485
  return "aliases aren't supported: name the users, targets and commands in each line";
15403
15486
  }
@@ -15423,9 +15506,90 @@ function kernelLine(m, line, file) {
15423
15506
  )
15424
15507
  };
15425
15508
  }
15509
+ var DEFAULTS = /^Defaults(?=[:@>!\s]|$)/;
15510
+ var DEFAULTS_ONLY = `Defaults aren't supported, except timestamp_timeout and timestamp_type for everyone, a user or a %group: this line changes nothing`;
15511
+ var PARAM = /^(!*)([A-Za-z_]+)(?:\s*([-+]?=)\s*(.*))?$/s;
15512
+ var MINUTES = /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)$/;
15513
+ var TYPES = ["tty", "ppid", "global"];
15514
+ function defaultsValue(raw2) {
15515
+ const quoted = /^"(.*)"$/s.exec(raw2);
15516
+ return quoted ? quoted[1] : raw2;
15517
+ }
15518
+ function timeoutParam({ text, negated, op, value: value2 }) {
15519
+ if (negated && op === void 0) return { timestampTimeout: 0 };
15520
+ if (negated || op !== "=" || !MINUTES.test(value2) || !Number.isFinite(Number(value2))) {
15521
+ return `timestamp_timeout takes minutes, such as =5 or =2.5 (0 always asks, below 0 never expires), not ${shown(text)}`;
15522
+ }
15523
+ return { timestampTimeout: Number(value2) };
15524
+ }
15525
+ function typeParam({ text, negated, op, value: value2 }) {
15526
+ if (!negated && op === "=" && TYPES.includes(value2)) {
15527
+ return { timestampType: value2 };
15528
+ }
15529
+ if (value2 === "kernel") return "timestamp_type=kernel isn't supported: use tty, ppid or global";
15530
+ return `timestamp_type takes =tty, =ppid or =global, not ${shown(text)}`;
15531
+ }
15532
+ function defaultsParam(param) {
15533
+ const m = PARAM.exec(param);
15534
+ if (!m) return DEFAULTS_ONLY;
15535
+ const [, bangs = "", name2, op, raw2 = ""] = m;
15536
+ const read = { text: param, negated: bangs !== "", op, value: defaultsValue(raw2.trim()) };
15537
+ const settings = name2 === "timestamp_timeout" ? timeoutParam(read) : name2 === "timestamp_type" ? typeParam(read) : DEFAULTS_ONLY;
15538
+ if (typeof settings === "string") return settings;
15539
+ const value2 = settings.timestampTimeout ?? settings.timestampType;
15540
+ return { settings, text: `${name2}=${value2}` };
15541
+ }
15542
+ function defaultsWho(binding) {
15543
+ if (binding === "") return null;
15544
+ if (!binding.startsWith(":")) return DEFAULTS_ONLY;
15545
+ const who = binding.slice(1).split(",");
15546
+ for (const name2 of who) {
15547
+ if (!validWho(name2))
15548
+ return `expected users, %groups or ALL after Defaults:, not ${shown(name2)}`;
15549
+ }
15550
+ return who;
15551
+ }
15552
+ function defaultsLine(text) {
15553
+ const m = /^Defaults(\S*)(?:\s+(.*))?$/s.exec(text);
15554
+ const who = defaultsWho(m?.[1] ?? "");
15555
+ if (typeof who === "string") return { error: who };
15556
+ const list = (m?.[2] ?? "").trim();
15557
+ if (list === "")
15558
+ return {
15559
+ error: "expected timestamp_timeout=<minutes> or timestamp_type=<type> after Defaults"
15560
+ };
15561
+ const settings = {};
15562
+ const params = [];
15563
+ for (const param of list.split(",")) {
15564
+ const read = defaultsParam(param.trim());
15565
+ if (typeof read === "string") return { error: read };
15566
+ Object.assign(settings, read.settings);
15567
+ params.push(read.text);
15568
+ }
15569
+ return { defaults: { who, settings, params } };
15570
+ }
15571
+ function resolveDefaults(entries, applies) {
15572
+ const everyone = entries.filter((e) => e.who === null);
15573
+ const mine = entries.filter((e) => e.who?.some(applies));
15574
+ const matching = [...everyone, ...mine];
15575
+ const settings = Object.assign({}, ...matching.map((e) => e.settings));
15576
+ return {
15577
+ timestampTimeout: settings.timestampTimeout ?? TIMESTAMP_TIMEOUT,
15578
+ timestampType: settings.timestampType ?? TIMESTAMP_TYPE,
15579
+ matching
15580
+ };
15581
+ }
15582
+ function defaultsText(entries) {
15583
+ const all = resolveDefaults(entries, () => false);
15584
+ return [
15585
+ `Defaults timestamp_timeout=${all.timestampTimeout}, timestamp_type=${all.timestampType}`,
15586
+ ...entries.filter((e) => e.who !== null).map((e) => `Defaults:${e.who.join(",")} ${e.params.join(", ")}`)
15587
+ ];
15588
+ }
15426
15589
  function parseLine(line, file) {
15427
15590
  const text = line.trim();
15428
15591
  if (text === "") return null;
15592
+ if (DEFAULTS.test(text)) return defaultsLine(text);
15429
15593
  const directive = directiveError(text);
15430
15594
  if (directive !== void 0) return directive === null ? null : { error: directive };
15431
15595
  const kernel = KERNEL_FORM.exec(text);
@@ -15437,14 +15601,16 @@ function parseLine(line, file) {
15437
15601
  }
15438
15602
  function parsePolicy(text, file) {
15439
15603
  const rules = [];
15604
+ const defaults2 = [];
15440
15605
  const errors = [];
15441
15606
  for (const [n, line] of text.split("\n").entries()) {
15442
15607
  const parsed2 = parseLine(line, file);
15443
15608
  if (parsed2 === null) continue;
15444
15609
  if ("error" in parsed2) errors.push({ line: n + 1, message: parsed2.error });
15610
+ else if ("defaults" in parsed2) defaults2.push(parsed2.defaults);
15445
15611
  else rules.push(...parsed2.rules);
15446
15612
  }
15447
- return { rules, errors };
15613
+ return { rules, defaults: defaults2, errors };
15448
15614
  }
15449
15615
 
15450
15616
  // src/users.ts
@@ -15829,7 +15995,14 @@ var SECURE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
15829
15995
  var DEFAULT_SUDOERS = "%sudo Cmnd *\n";
15830
15996
  var SUDO_TIMEOUT_MS = 3e5;
15831
15997
  var TERM = /^[A-Za-z0-9._+-]+$/;
15832
- var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
15998
+ var USAGE = [
15999
+ "usage: sudo -K | -k",
16000
+ " sudo -v [-kn] [-u user]",
16001
+ " sudo -l [-kn]",
16002
+ " sudo [-kn] [-u user] [--] command [args...]"
16003
+ ].join("\n");
16004
+ var DONE = { done: true };
16005
+ var NOT_STAMPED = "sudo: approved, but the approver didn't say a person decided, so no timestamp was recorded";
15833
16006
  var DENIED2 = "sudo: approval denied";
15834
16007
  var DENIAL_MAX = 500;
15835
16008
  var ABORTED = /* @__PURE__ */ Symbol("aborted");
@@ -15903,6 +16076,17 @@ async function answerOf(approve, req, signal) {
15903
16076
  return cut === void 0 ? "deny" : await cut;
15904
16077
  }
15905
16078
  }
16079
+ function approvalOf(answer2) {
16080
+ if (answer2 === "once" || answer2 === "always") return { answer: answer2, person: false };
16081
+ if (typeof answer2 !== "object" || answer2 === null) return void 0;
16082
+ try {
16083
+ const { answer: kind, timestamp } = answer2;
16084
+ if (kind !== "once" && kind !== "always" || timestamp !== true) return void 0;
16085
+ return { answer: kind, person: true };
16086
+ } catch {
16087
+ return void 0;
16088
+ }
16089
+ }
15906
16090
  function recordedCommand(argv) {
15907
16091
  const [path = "", ...args] = argv;
15908
16092
  return commandText({ path, args: args.map((a) => [a]) });
@@ -15912,6 +16096,9 @@ function flags(parsed2, arg, next3) {
15912
16096
  const flag = arg[j];
15913
16097
  if (flag === "n") parsed2.nonInteractive = true;
15914
16098
  else if (flag === "l") parsed2.list = true;
16099
+ else if (flag === "v") parsed2.validate = true;
16100
+ else if (flag === "k") parsed2.reset = true;
16101
+ else if (flag === "K") parsed2.removeAll = true;
15915
16102
  else if (flag !== "u") return `sudo: invalid option -- '${flag}'
15916
16103
  ${USAGE}`;
15917
16104
  else {
@@ -15925,7 +16112,15 @@ ${USAGE}`;
15925
16112
  return 0;
15926
16113
  }
15927
16114
  function parseArgs(args) {
15928
- const parsed2 = { list: false, nonInteractive: false, user: "root", command: [] };
16115
+ const parsed2 = {
16116
+ list: false,
16117
+ nonInteractive: false,
16118
+ validate: false,
16119
+ reset: false,
16120
+ removeAll: false,
16121
+ user: "root",
16122
+ command: []
16123
+ };
15929
16124
  let i = 0;
15930
16125
  for (; i < args.length; i++) {
15931
16126
  const arg = args[i];
@@ -15939,8 +16134,17 @@ function parseArgs(args) {
15939
16134
  i += used;
15940
16135
  }
15941
16136
  parsed2.command = args.slice(i);
15942
- if (parsed2.list ? parsed2.command.length > 0 : parsed2.command.length === 0) return USAGE;
15943
- return parsed2;
16137
+ return usable(parsed2) ? parsed2 : USAGE;
16138
+ }
16139
+ function usable(parsed2) {
16140
+ const command = parsed2.command.length > 0;
16141
+ const modes = [parsed2.list, parsed2.validate, parsed2.removeAll].filter(Boolean).length;
16142
+ if (modes > 1 || parsed2.removeAll && parsed2.reset) return false;
16143
+ if (modes === 1) return !command;
16144
+ return command || parsed2.reset;
16145
+ }
16146
+ function policyOf(settings) {
16147
+ return { type: settings.timestampType, minutes: settings.timestampTimeout };
15944
16148
  }
15945
16149
  function described(rule) {
15946
16150
  const cmnd = rule.cmnd === "*" ? "ALL" : commandText(rule.cmnd);
@@ -15952,9 +16156,11 @@ var Sudo = class {
15952
16156
  o;
15953
16157
  warned = /* @__PURE__ */ new Set();
15954
16158
  pending = /* @__PURE__ */ new Set();
16159
+ timestamps;
15955
16160
  next = 0;
15956
16161
  constructor(options) {
15957
16162
  this.o = options;
16163
+ this.timestamps = new Timestamps(options.now ?? (() => performance.now()));
15958
16164
  }
15959
16165
  warn(message3) {
15960
16166
  if (this.warned.has(message3)) return;
@@ -16041,8 +16247,8 @@ var Sudo = class {
16041
16247
  const code = err.code;
16042
16248
  return skipped(`it can't be read (${typeof code === "string" ? code : "error"})`);
16043
16249
  }
16044
- const { rules, errors } = parsePolicy(text, path);
16045
- return { path, rules, problems: errors, ok: true };
16250
+ const { rules, defaults: defaults2, errors } = parsePolicy(text, path);
16251
+ return { path, rules, defaults: defaults2, problems: errors, ok: true };
16046
16252
  }
16047
16253
  async policy() {
16048
16254
  const files = [await this.policyFile(SUDOERS)];
@@ -16071,7 +16277,10 @@ var Sudo = class {
16071
16277
  }
16072
16278
  }
16073
16279
  if (!detailed && skipped > 0) await caller.report?.(skippedNotice(skipped));
16074
- return files.flatMap((f) => f.rules);
16280
+ return {
16281
+ rules: files.flatMap((f) => f.rules),
16282
+ defaults: files.flatMap((f) => f.defaults ?? [])
16283
+ };
16075
16284
  }
16076
16285
  async check(cred, call) {
16077
16286
  const lines2 = [];
@@ -16084,17 +16293,26 @@ var Sudo = class {
16084
16293
  if (read && problems.length === 0) lines2.push([1, `${path}: parsed OK`]);
16085
16294
  if (problems.length > 0) ok4 = false;
16086
16295
  };
16296
+ const values = (defaults2) => {
16297
+ for (const line of defaultsText(defaults2)) lines2.push([1, line]);
16298
+ };
16087
16299
  if (call.files !== void 0) {
16300
+ const defaults2 = [];
16088
16301
  for (const { name: name2, text } of call.files) {
16089
- report({ path: name2, rules: [], problems: parsePolicy(text).errors, ok: true });
16302
+ const parsed2 = parsePolicy(text);
16303
+ defaults2.push(...parsed2.defaults);
16304
+ report({ path: name2, rules: [], problems: parsed2.errors, ok: true });
16090
16305
  }
16306
+ if (defaults2.length > 0) values(defaults2);
16091
16307
  return { lines: lines2, ok: ok4 };
16092
16308
  }
16093
16309
  if (this.o.mode === "enforce" && cred.euid !== 0) {
16094
16310
  const hint = "check the policy as root (sudo visudo -c), or a file with visudo -c -f <file>";
16095
16311
  return { lines: [[2, `visudo: ${SUDOERS}: Permission denied: ${hint}`]], ok: false };
16096
16312
  }
16097
- for (const file of await this.policy()) report(file);
16313
+ const files = await this.policy();
16314
+ for (const file of files) report(file);
16315
+ values(files.flatMap((f) => f.defaults ?? []));
16098
16316
  return { lines: lines2, ok: ok4 };
16099
16317
  }
16100
16318
  async groupIds() {
@@ -16103,9 +16321,9 @@ var Sudo = class {
16103
16321
  async applies(caller, cred) {
16104
16322
  const gids = await this.groupIds();
16105
16323
  const mine = /* @__PURE__ */ new Set([cred.rgid, cred.egid, ...cred.groups]);
16106
- return (rule) => {
16107
- if (rule.who === "ALL" || rule.who === caller.name) return true;
16108
- const gid = rule.who.startsWith("%") ? gids.get(rule.who.slice(1)) : void 0;
16324
+ return (who) => {
16325
+ if (who === "ALL" || who === caller.name) return true;
16326
+ const gid = who.startsWith("%") ? gids.get(who.slice(1)) : void 0;
16109
16327
  return gid !== void 0 && mine.has(gid);
16110
16328
  };
16111
16329
  }
@@ -16150,18 +16368,40 @@ var Sudo = class {
16150
16368
  const mode = this.o.mode;
16151
16369
  if (parsed2.list) return { list: `sudo: users are '${mode}': commands run as the caller
16152
16370
  ` };
16153
- return { run: parsed2.command };
16371
+ return parsed2.command.length > 0 ? { run: parsed2.command } : DONE;
16154
16372
  }
16155
16373
  const { cred } = caller;
16156
16374
  const policy = await this.policyRules(caller);
16157
16375
  const self = await this.o.users.lookup(cred.ruid);
16158
16376
  if (!self) return failed2("EPERM", `sudo: uid ${cred.ruid} is not in the passwd database`);
16377
+ const owner = {
16378
+ uid: cred.ruid,
16379
+ terminal: caller.terminal,
16380
+ parent: caller.parent
16381
+ };
16382
+ const applies = await this.applies(self, cred);
16383
+ const settings = resolveDefaults(policy.defaults, applies);
16384
+ if (parsed2.removeAll) {
16385
+ this.timestamps.remove(owner.uid);
16386
+ return DONE;
16387
+ }
16388
+ if (parsed2.reset && !parsed2.validate && !parsed2.list && parsed2.command.length === 0) {
16389
+ this.timestamps.invalidate(owner, settings.timestampType);
16390
+ return DONE;
16391
+ }
16159
16392
  const target = await this.lookup(parsed2.user);
16160
16393
  if (!target) return failed2("ENOENT", `sudo: unknown user ${parsed2.user}`);
16161
16394
  const root = cred.euid === 0;
16162
- const rules = root ? [] : policy;
16163
- const applies = await this.applies(self, cred);
16164
- if (parsed2.list) return this.listed(self, root, rules.filter(applies));
16395
+ const rules = root ? [] : policy.rules;
16396
+ const mine = rules.filter((rule) => applies(rule.who));
16397
+ if (parsed2.list) return this.listed(self, root, mine, settings.matching);
16398
+ const session = { caller, self, target, owner, policy: policyOf(settings), parsed: parsed2, signal };
16399
+ if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
16400
+ return this.command(session, root, mine, call);
16401
+ }
16402
+ async command(s, root, mine, call) {
16403
+ const { caller, self, target, parsed: parsed2 } = s;
16404
+ const { cred } = caller;
16165
16405
  const [word = "", ...args] = parsed2.command;
16166
16406
  const cwd = await this.canonical(call.cwd);
16167
16407
  if (cwd === void 0 || !await this.reachable(cred, cwd)) {
@@ -16175,8 +16415,8 @@ var Sudo = class {
16175
16415
  const untrusted2 = await this.untrusted(path, cwd, word);
16176
16416
  if (untrusted2) return untrusted2;
16177
16417
  const argv = [path, ...args];
16178
- const matching = rules.filter(
16179
- (rule) => applies(rule) && (rule.target === "ALL" || rule.target === target.name) && (rule.cwd === void 0 || rule.cwd === cwd) && stdinMatches(rule, caller.stdin) && commandMatches(rule.cmnd, argv)
16418
+ const matching = mine.filter(
16419
+ (rule) => (rule.target === "ALL" || rule.target === target.name) && (rule.cwd === void 0 || rule.cwd === cwd) && stdinMatches(rule, caller.stdin) && commandMatches(rule.cmnd, argv)
16180
16420
  );
16181
16421
  const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
16182
16422
  if (root || matching.some((rule) => rule.nopasswd)) return grant();
@@ -16187,23 +16427,67 @@ var Sudo = class {
16187
16427
  `sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
16188
16428
  );
16189
16429
  }
16430
+ if (!parsed2.reset && this.timestamps.current(s.owner, s.policy)) {
16431
+ this.timestamps.stamp(s.owner, s.policy);
16432
+ this.logged(s, cwd, argv);
16433
+ return grant();
16434
+ }
16190
16435
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
16191
- if (this.pending.has(cred.ruid)) return failed2("EBUSY", "sudo: another request is pending");
16192
- this.pending.add(cred.ruid);
16193
- try {
16194
- const answer2 = await this.ask(caller, self, target, parsed2.command, path, cwd, signal);
16195
- if (typeof answer2 !== "string") return answer2;
16436
+ return this.pendingFor(cred.ruid, async () => {
16437
+ const answer2 = await this.ask(s, { argv: parsed2.command, path, cwd });
16438
+ if ("error" in answer2) return answer2;
16196
16439
  if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
16197
16440
  return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
16198
16441
  }
16199
16442
  const changed = await this.untrusted(path, cwd, word);
16200
16443
  if (changed) return changed;
16201
- if (answer2 === "once") return grant();
16202
- return await this.always(self, target, cwd, argv, recordable(cwd, parsed2.command), grant);
16444
+ const result = answer2.answer === "once" ? grant() : await this.always(self, target, cwd, argv, recordable(cwd, parsed2.command), grant);
16445
+ if (!("error" in result)) this.stamped(s, answer2);
16446
+ return result;
16447
+ });
16448
+ }
16449
+ async validate(s, root, mine, dir) {
16450
+ const { self, parsed: parsed2 } = s;
16451
+ if (root) return DONE;
16452
+ if (mine.length === 0) {
16453
+ return failed2("EPERM", `Sorry, user ${self.name} may not run sudo on ${this.o.hostname}.`);
16454
+ }
16455
+ if (mine.every((rule) => rule.nopasswd)) return DONE;
16456
+ if (!parsed2.reset && this.timestamps.current(s.owner, s.policy)) {
16457
+ this.timestamps.stamp(s.owner, s.policy);
16458
+ return DONE;
16459
+ }
16460
+ if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
16461
+ const cwd = await this.canonical(dir) ?? normalizePath(dir);
16462
+ return this.pendingFor(s.caller.cred.ruid, async () => {
16463
+ const answer2 = await this.ask(s, { argv: [], path: "", cwd, validate: true });
16464
+ if ("error" in answer2) return answer2;
16465
+ if (!answer2.person) await s.caller.report?.(NOT_STAMPED);
16466
+ this.stamped(s, answer2);
16467
+ return DONE;
16468
+ });
16469
+ }
16470
+ async pendingFor(uid, work) {
16471
+ if (this.pending.has(uid)) return failed2("EBUSY", "sudo: another request is pending");
16472
+ this.pending.add(uid);
16473
+ try {
16474
+ return await work();
16203
16475
  } finally {
16204
- this.pending.delete(cred.ruid);
16476
+ this.pending.delete(uid);
16205
16477
  }
16206
16478
  }
16479
+ stamped(s, answer2) {
16480
+ if (answer2.person && !s.parsed.reset) this.timestamps.stamp(s.owner, s.policy);
16481
+ }
16482
+ logged(s, cwd, argv) {
16483
+ const { caller, self, target, owner, policy } = s;
16484
+ const type = policy.type === "tty" && !owner.terminal ? "ppid" : policy.type;
16485
+ const tty = owner.terminal?.tty ?? caller.tty ?? "unknown";
16486
+ const command = argv.map(quoteWord).join(" ");
16487
+ (this.o.warn ?? console.warn)(
16488
+ `sudo: ${self.name} : TTY=${tty} ; PWD=${quoteWord(cwd)} ; USER=${target.name} ; TIMESTAMP=${type} ; COMMAND=${command}`
16489
+ );
16490
+ }
16207
16491
  async always(self, target, cwd, argv, shown2, grant) {
16208
16492
  if (!shown2 || !recordable(cwd, argv)) return { ...grant(), notice: RAN_ONCE };
16209
16493
  if (!await this.remember(self, target, cwd, argv)) {
@@ -16211,7 +16495,7 @@ var Sudo = class {
16211
16495
  }
16212
16496
  return grant();
16213
16497
  }
16214
- listed(self, root, rules) {
16498
+ listed(self, root, rules, defaults2) {
16215
16499
  const host = this.o.hostname;
16216
16500
  if (root) {
16217
16501
  return {
@@ -16224,11 +16508,20 @@ var Sudo = class {
16224
16508
  return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
16225
16509
  }
16226
16510
  const lines2 = rules.map(described).join("\n");
16227
- return { list: `User ${self.name} may run the following commands on ${host}:
16511
+ const params = defaults2.flatMap((d) => d.params).join(", ");
16512
+ const matching = params === "" ? "" : `Matching Defaults entries for ${self.name} on ${host}:
16513
+ ${params}
16514
+
16515
+ `;
16516
+ return {
16517
+ list: `${matching}User ${self.name} may run the following commands on ${host}:
16228
16518
  ${lines2}
16229
- ` };
16519
+ `
16520
+ };
16230
16521
  }
16231
- async ask(caller, self, target, argv, path, cwd, requester) {
16522
+ async ask(s, asked) {
16523
+ const { caller, self, target, signal: requester } = s;
16524
+ const { argv, path, cwd } = asked;
16232
16525
  const { approve } = this.o;
16233
16526
  if (!approve || requester.aborted) return failed2("EACCES", DENIED2);
16234
16527
  const createdAt = Date.now();
@@ -16247,9 +16540,11 @@ ${lines2}
16247
16540
  ...caller.tty ? { tty: caller.tty } : {},
16248
16541
  createdAt,
16249
16542
  expiresAt: createdAt + timeout,
16250
- always: recordable(cwd, [path, ...argv]),
16543
+ always: asked.validate ? false : recordable(cwd, [path, ...argv]),
16251
16544
  stdin: caller.stdin,
16252
- rootOnly: await this.rootOnly(cwd, argv.slice(1))
16545
+ rootOnly: asked.validate ? {} : await this.rootOnly(cwd, argv.slice(1)),
16546
+ ...asked.validate ? { validate: true } : {},
16547
+ timestamp: true
16253
16548
  };
16254
16549
  const control = new AbortController();
16255
16550
  const timer = setTimeout(() => control.abort("expired"), timeout);
@@ -16259,9 +16554,8 @@ ${lines2}
16259
16554
  try {
16260
16555
  const answer2 = await answerOf(approve, req, control.signal);
16261
16556
  if (answer2 === ABORTED) return ended();
16262
- if (answer2 === "once" || answer2 === "always") {
16263
- return control.signal.aborted ? ended() : answer2;
16264
- }
16557
+ const approved = approvalOf(answer2);
16558
+ if (approved) return control.signal.aborted ? ended() : approved;
16265
16559
  return failed2("EACCES", denialText(answer2));
16266
16560
  } finally {
16267
16561
  clearTimeout(timer);
@@ -19818,7 +20112,7 @@ function replaced(text, values) {
19818
20112
  for (const [ph, value2] of Object.entries(values)) out = out.split(ph).join(binary(value2));
19819
20113
  return out;
19820
20114
  }
19821
- function usable(values) {
20115
+ function usable2(values) {
19822
20116
  const out = {};
19823
20117
  for (const [ph, value2] of Object.entries(values ?? {}))
19824
20118
  if (encoder8.encode(value2).length >= MIN_VALUE) out[ph] = value2;
@@ -19833,7 +20127,7 @@ async function substituteHead(head, ask) {
19833
20127
  if (!request || !found || found.length !== 1) return void 0;
19834
20128
  const at = found[0];
19835
20129
  if (at.placeholders.length > SESSION_PLACEHOLDERS) return void 0;
19836
- const values = usable(
20130
+ const values = usable2(
19837
20131
  await ask(`header:${at.name}`, at.placeholders, {
19838
20132
  method: request[1],
19839
20133
  path: request[2]
@@ -20143,7 +20437,7 @@ async function cleartext(message3, asks) {
20143
20437
  const placeholders = placeholdersIn(password);
20144
20438
  if (placeholders.length === 0 || placeholders.length > SESSION_PLACEHOLDERS)
20145
20439
  return { bytes: message3, done: true };
20146
- const values = usable(await asks.cleartext(placeholders));
20440
+ const values = usable2(await asks.cleartext(placeholders));
20147
20441
  if (Object.keys(values).length === 0) return { bytes: message3, done: true };
20148
20442
  return { bytes: frame3(112, `${replaced(password, values)}\0`), values, done: true };
20149
20443
  }
@@ -20208,7 +20502,7 @@ var PgClient = class {
20208
20502
  const x = got.exchange;
20209
20503
  const binding = this.header === END_POINT ? this.server.binding : void 0;
20210
20504
  const serverFinal = `c=${channelOf(this.header, binding)},r=${got.nonce}`;
20211
- const values = usable(
20505
+ const values = usable2(
20212
20506
  await this.asks.scram({
20213
20507
  clientFirstBare: x.clientFirstBare,
20214
20508
  serverFirst: x.serverFirst,
@@ -20257,7 +20551,7 @@ function decoded(b642) {
20257
20551
  async function swapped(text, position, ask) {
20258
20552
  const placeholders = placeholdersIn(text);
20259
20553
  if (placeholders.length === 0 || placeholders.length > SESSION_PLACEHOLDERS) return void 0;
20260
- const values = usable(await ask(position, placeholders));
20554
+ const values = usable2(await ask(position, placeholders));
20261
20555
  if (Object.keys(values).length === 0) return void 0;
20262
20556
  return { text: replaced(text, values), values };
20263
20557
  }
@@ -20355,7 +20649,7 @@ function headEnd2(bytes) {
20355
20649
  }
20356
20650
  return -1;
20357
20651
  }
20358
- function modeOf(start2) {
20652
+ function modeOf2(start2) {
20359
20653
  const sniffs = [
20360
20654
  ["http1", sniffHttp1(start2)],
20361
20655
  ["smtp", sniffSmtp(start2)],
@@ -20464,7 +20758,7 @@ var SessionWriter = class {
20464
20758
  joined5.set(bytes, this.buffer.length);
20465
20759
  this.buffer.fill(0);
20466
20760
  this.buffer = joined5;
20467
- if (this.mode === "sniff") this.mode = modeOf(latin12(joined5.subarray(0, 8))) ?? "sniff";
20761
+ if (this.mode === "sniff") this.mode = modeOf2(latin12(joined5.subarray(0, 8))) ?? "sniff";
20468
20762
  if (this.mode === "pg" && !this.sawPg) {
20469
20763
  this.sawPg = true;
20470
20764
  if (!this.s.verified) void this.proof("pg");
@@ -28904,8 +29198,8 @@ var OpfsFs = class {
28904
29198
  const [entry] = await this.meta.get([target]);
28905
29199
  if (entry?.fifo || entry?.socket) throw fsError("ENXIO", path);
28906
29200
  const kept = fresh3 ? cached : this.files.get(target);
28907
- const usable2 = kept && performance.now() - (this.found.get(kept) ?? -CHECK_MS) < CHECK_MS;
28908
- return [usable2 ? kept : void 0, target, true];
29201
+ const usable3 = kept && performance.now() - (this.found.get(kept) ?? -CHECK_MS) < CHECK_MS;
29202
+ return [usable3 ? kept : void 0, target, true];
28909
29203
  }
28910
29204
  async writeAt(handle, target, path, offset, bytes) {
28911
29205
  await this.counting(target);
@@ -31901,6 +32195,7 @@ var SUDO_SOURCE = [
31901
32195
  " await say(1, answer.list);",
31902
32196
  " return 0;",
31903
32197
  " }",
32198
+ " if (answer.done === true) return 0;",
31904
32199
  " if (answer.run !== undefined) {",
31905
32200
  " try {",
31906
32201
  " pid = (await ctx.spawn({ argv: answer.run, pgid: 0 })).pid;",
@@ -32536,6 +32831,7 @@ var Launcher = class {
32536
32831
  );
32537
32832
  }
32538
32833
  this.users = new UserDb(this.fs, this.owners);
32834
+ const { sudoClock } = options;
32539
32835
  this.sudoer = new Sudo({
32540
32836
  fs: this.fs,
32541
32837
  users: this.users,
@@ -32545,7 +32841,8 @@ var Launcher = class {
32545
32841
  pnpmHome: this.pnpmHome,
32546
32842
  backing: (path, cwd) => this.backing(path, cwd),
32547
32843
  approve: options.approve,
32548
- timeoutMs: options.sudoTimeoutMs
32844
+ timeoutMs: options.sudoTimeoutMs,
32845
+ now: sudoClock ? () => sudoClock.now() : void 0
32549
32846
  });
32550
32847
  this.cron = this.cronService(options.cron);
32551
32848
  void this.unlinked.sweep("/");
@@ -33951,11 +34248,26 @@ var Launcher = class {
33951
34248
  pid: this.jobs.shown(pid),
33952
34249
  tags: this.tagsOf(pid),
33953
34250
  tty: this.described.get(pid)?.tty,
34251
+ ...this.timestampOwner(pid),
33954
34252
  stdin,
33955
34253
  report
33956
34254
  };
33957
34255
  return this.sudoer.request(caller, call, signal);
33958
34256
  }
34257
+ timestampOwner(pid) {
34258
+ const sid = this.jobs.sidOf(pid);
34259
+ const tty = this.jobs.controllingTerminal(pid)?.name;
34260
+ const leader = sid === void 0 ? void 0 : this.startedOf(sid);
34261
+ const host = this.jobs.shownParent(pid) ?? this.jobs.shown(pid);
34262
+ const started = this.startedOf(host);
34263
+ return {
34264
+ ...tty && sid !== void 0 && leader !== void 0 ? { terminal: { tty, sid, started: leader } } : {},
34265
+ ...started === void 0 ? {} : { parent: { pid: host, started } }
34266
+ };
34267
+ }
34268
+ startedOf(shown2) {
34269
+ return this.running(shown2) ? this.described.get(shown2)?.started : void 0;
34270
+ }
33959
34271
  visudo(pid, call) {
33960
34272
  return this.sudoer.check(this.credOf(pid), call);
33961
34273
  }
@@ -37642,6 +37954,7 @@ async function createNodeKernel(options = {}) {
37642
37954
  ...options.clipboard ? { clipboard: options.clipboard } : {},
37643
37955
  ...options.requests ? { embedder: checkedRequests(options.requests) } : {},
37644
37956
  ...options.approve ? { approve: options.approve } : {},
37957
+ ...options.sudo?.clock ? { sudoClock: options.sudo.clock } : {},
37645
37958
  ...options.wasmMemories ? { wasmMemories: options.wasmMemories } : {},
37646
37959
  cron: { lock: cronLock(root), ...options.cron?.clock ? { clock: options.cron.clock } : {} }
37647
37960
  });