@ai-ecoverse/slicc-kernel 1.176.2 → 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
@@ -3709,7 +3709,7 @@ function satisfies(version, range2) {
3709
3709
  }
3710
3710
 
3711
3711
  // src/version.ts
3712
- var KERNEL_VERSION = true ? "1.176.2" : "0.0.0-dev";
3712
+ var KERNEL_VERSION = true ? "1.177.0" : "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
@@ -14488,8 +14488,8 @@ var SCRUB_IDLE_MS = 50;
14488
14488
  var SCRUB_NONBLOCK_MAX = 64 * 1024;
14489
14489
  var encoder5 = new TextEncoder();
14490
14490
  function scrubberOf(secrets, sameLength = false) {
14491
- const usable2 = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
14492
- 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;
14493
14493
  }
14494
14494
  async function writeAll(file, bytes) {
14495
14495
  const write = file.write;
@@ -15128,6 +15128,56 @@ var SealedBroker = class {
15128
15128
  }
15129
15129
  };
15130
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
+
15131
15181
  // src/sudoers.ts
15132
15182
  var SUDOERS = "/etc/sudoers";
15133
15183
  var SUDOERS_D = "/etc/sudoers.d";
@@ -15147,6 +15197,8 @@ var SHOWN_MAX = 60;
15147
15197
  var KERNEL_SYNTAX = "[NOPASSWD] <user|%group|ALL> [(<target>)] [Cwd <dir>] [Stdin none] Cmnd <command\u2026|*>";
15148
15198
  var SUDOERS_SYNTAX = "<user|%group|ALL> ALL=[(<target>[,<target>\u2026])] [NOPASSWD:] <command\u2026|ALL>";
15149
15199
  var EXPECTED = `expected '${KERNEL_SYNTAX}' or '${SUDOERS_SYNTAX}'`;
15200
+ var TIMESTAMP_TIMEOUT = 5;
15201
+ var TIMESTAMP_TYPE = "tty";
15150
15202
  function hasControl(text) {
15151
15203
  return CONTROL.test(text);
15152
15204
  }
@@ -15429,9 +15481,6 @@ function directiveError(text) {
15429
15481
  return /^#-?\d+\s+\S+\s*=/.test(text) ? "a #uid isn't supported: name the user, %group or ALL" : null;
15430
15482
  }
15431
15483
  const first = /^\S+/.exec(text)?.[0] ?? "";
15432
- if (/^Defaults([:@>!]|$)/.test(first)) {
15433
- return "Defaults aren't supported: the kernel's sudo has no settings, so this changes nothing";
15434
- }
15435
15484
  if (/^[A-Z][A-Za-z]*_Alias$/.test(first)) {
15436
15485
  return "aliases aren't supported: name the users, targets and commands in each line";
15437
15486
  }
@@ -15457,9 +15506,90 @@ function kernelLine(m, line, file) {
15457
15506
  )
15458
15507
  };
15459
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
+ }
15460
15589
  function parseLine(line, file) {
15461
15590
  const text = line.trim();
15462
15591
  if (text === "") return null;
15592
+ if (DEFAULTS.test(text)) return defaultsLine(text);
15463
15593
  const directive = directiveError(text);
15464
15594
  if (directive !== void 0) return directive === null ? null : { error: directive };
15465
15595
  const kernel = KERNEL_FORM.exec(text);
@@ -15471,14 +15601,16 @@ function parseLine(line, file) {
15471
15601
  }
15472
15602
  function parsePolicy(text, file) {
15473
15603
  const rules = [];
15604
+ const defaults2 = [];
15474
15605
  const errors = [];
15475
15606
  for (const [n, line] of text.split("\n").entries()) {
15476
15607
  const parsed2 = parseLine(line, file);
15477
15608
  if (parsed2 === null) continue;
15478
15609
  if ("error" in parsed2) errors.push({ line: n + 1, message: parsed2.error });
15610
+ else if ("defaults" in parsed2) defaults2.push(parsed2.defaults);
15479
15611
  else rules.push(...parsed2.rules);
15480
15612
  }
15481
- return { rules, errors };
15613
+ return { rules, defaults: defaults2, errors };
15482
15614
  }
15483
15615
 
15484
15616
  // src/users.ts
@@ -15863,7 +15995,14 @@ var SECURE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
15863
15995
  var DEFAULT_SUDOERS = "%sudo Cmnd *\n";
15864
15996
  var SUDO_TIMEOUT_MS = 3e5;
15865
15997
  var TERM = /^[A-Za-z0-9._+-]+$/;
15866
- 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";
15867
16006
  var DENIED2 = "sudo: approval denied";
15868
16007
  var DENIAL_MAX = 500;
15869
16008
  var ABORTED = /* @__PURE__ */ Symbol("aborted");
@@ -15937,6 +16076,17 @@ async function answerOf(approve, req, signal) {
15937
16076
  return cut === void 0 ? "deny" : await cut;
15938
16077
  }
15939
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
+ }
15940
16090
  function recordedCommand(argv) {
15941
16091
  const [path = "", ...args] = argv;
15942
16092
  return commandText({ path, args: args.map((a) => [a]) });
@@ -15946,6 +16096,9 @@ function flags(parsed2, arg, next3) {
15946
16096
  const flag = arg[j];
15947
16097
  if (flag === "n") parsed2.nonInteractive = true;
15948
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;
15949
16102
  else if (flag !== "u") return `sudo: invalid option -- '${flag}'
15950
16103
  ${USAGE}`;
15951
16104
  else {
@@ -15959,7 +16112,15 @@ ${USAGE}`;
15959
16112
  return 0;
15960
16113
  }
15961
16114
  function parseArgs(args) {
15962
- 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
+ };
15963
16124
  let i = 0;
15964
16125
  for (; i < args.length; i++) {
15965
16126
  const arg = args[i];
@@ -15973,8 +16134,17 @@ function parseArgs(args) {
15973
16134
  i += used;
15974
16135
  }
15975
16136
  parsed2.command = args.slice(i);
15976
- if (parsed2.list ? parsed2.command.length > 0 : parsed2.command.length === 0) return USAGE;
15977
- 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 };
15978
16148
  }
15979
16149
  function described(rule) {
15980
16150
  const cmnd = rule.cmnd === "*" ? "ALL" : commandText(rule.cmnd);
@@ -15986,9 +16156,11 @@ var Sudo = class {
15986
16156
  o;
15987
16157
  warned = /* @__PURE__ */ new Set();
15988
16158
  pending = /* @__PURE__ */ new Set();
16159
+ timestamps;
15989
16160
  next = 0;
15990
16161
  constructor(options) {
15991
16162
  this.o = options;
16163
+ this.timestamps = new Timestamps(options.now ?? (() => performance.now()));
15992
16164
  }
15993
16165
  warn(message3) {
15994
16166
  if (this.warned.has(message3)) return;
@@ -16075,8 +16247,8 @@ var Sudo = class {
16075
16247
  const code = err.code;
16076
16248
  return skipped(`it can't be read (${typeof code === "string" ? code : "error"})`);
16077
16249
  }
16078
- const { rules, errors } = parsePolicy(text, path);
16079
- 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 };
16080
16252
  }
16081
16253
  async policy() {
16082
16254
  const files = [await this.policyFile(SUDOERS)];
@@ -16105,7 +16277,10 @@ var Sudo = class {
16105
16277
  }
16106
16278
  }
16107
16279
  if (!detailed && skipped > 0) await caller.report?.(skippedNotice(skipped));
16108
- return files.flatMap((f) => f.rules);
16280
+ return {
16281
+ rules: files.flatMap((f) => f.rules),
16282
+ defaults: files.flatMap((f) => f.defaults ?? [])
16283
+ };
16109
16284
  }
16110
16285
  async check(cred, call) {
16111
16286
  const lines2 = [];
@@ -16118,17 +16293,26 @@ var Sudo = class {
16118
16293
  if (read && problems.length === 0) lines2.push([1, `${path}: parsed OK`]);
16119
16294
  if (problems.length > 0) ok4 = false;
16120
16295
  };
16296
+ const values = (defaults2) => {
16297
+ for (const line of defaultsText(defaults2)) lines2.push([1, line]);
16298
+ };
16121
16299
  if (call.files !== void 0) {
16300
+ const defaults2 = [];
16122
16301
  for (const { name: name2, text } of call.files) {
16123
- 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 });
16124
16305
  }
16306
+ if (defaults2.length > 0) values(defaults2);
16125
16307
  return { lines: lines2, ok: ok4 };
16126
16308
  }
16127
16309
  if (this.o.mode === "enforce" && cred.euid !== 0) {
16128
16310
  const hint = "check the policy as root (sudo visudo -c), or a file with visudo -c -f <file>";
16129
16311
  return { lines: [[2, `visudo: ${SUDOERS}: Permission denied: ${hint}`]], ok: false };
16130
16312
  }
16131
- 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 ?? []));
16132
16316
  return { lines: lines2, ok: ok4 };
16133
16317
  }
16134
16318
  async groupIds() {
@@ -16137,9 +16321,9 @@ var Sudo = class {
16137
16321
  async applies(caller, cred) {
16138
16322
  const gids = await this.groupIds();
16139
16323
  const mine = /* @__PURE__ */ new Set([cred.rgid, cred.egid, ...cred.groups]);
16140
- return (rule) => {
16141
- if (rule.who === "ALL" || rule.who === caller.name) return true;
16142
- 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;
16143
16327
  return gid !== void 0 && mine.has(gid);
16144
16328
  };
16145
16329
  }
@@ -16184,18 +16368,40 @@ var Sudo = class {
16184
16368
  const mode = this.o.mode;
16185
16369
  if (parsed2.list) return { list: `sudo: users are '${mode}': commands run as the caller
16186
16370
  ` };
16187
- return { run: parsed2.command };
16371
+ return parsed2.command.length > 0 ? { run: parsed2.command } : DONE;
16188
16372
  }
16189
16373
  const { cred } = caller;
16190
16374
  const policy = await this.policyRules(caller);
16191
16375
  const self = await this.o.users.lookup(cred.ruid);
16192
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
+ }
16193
16392
  const target = await this.lookup(parsed2.user);
16194
16393
  if (!target) return failed2("ENOENT", `sudo: unknown user ${parsed2.user}`);
16195
16394
  const root = cred.euid === 0;
16196
- const rules = root ? [] : policy;
16197
- const applies = await this.applies(self, cred);
16198
- 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;
16199
16405
  const [word = "", ...args] = parsed2.command;
16200
16406
  const cwd = await this.canonical(call.cwd);
16201
16407
  if (cwd === void 0 || !await this.reachable(cred, cwd)) {
@@ -16209,8 +16415,8 @@ var Sudo = class {
16209
16415
  const untrusted2 = await this.untrusted(path, cwd, word);
16210
16416
  if (untrusted2) return untrusted2;
16211
16417
  const argv = [path, ...args];
16212
- const matching = rules.filter(
16213
- (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)
16214
16420
  );
16215
16421
  const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
16216
16422
  if (root || matching.some((rule) => rule.nopasswd)) return grant();
@@ -16221,23 +16427,67 @@ var Sudo = class {
16221
16427
  `sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
16222
16428
  );
16223
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
+ }
16224
16435
  if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
16225
- if (this.pending.has(cred.ruid)) return failed2("EBUSY", "sudo: another request is pending");
16226
- this.pending.add(cred.ruid);
16227
- try {
16228
- const answer2 = await this.ask(caller, self, target, parsed2.command, path, cwd, signal);
16229
- 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;
16230
16439
  if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
16231
16440
  return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
16232
16441
  }
16233
16442
  const changed = await this.untrusted(path, cwd, word);
16234
16443
  if (changed) return changed;
16235
- if (answer2 === "once") return grant();
16236
- 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();
16237
16475
  } finally {
16238
- this.pending.delete(cred.ruid);
16476
+ this.pending.delete(uid);
16239
16477
  }
16240
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
+ }
16241
16491
  async always(self, target, cwd, argv, shown2, grant) {
16242
16492
  if (!shown2 || !recordable(cwd, argv)) return { ...grant(), notice: RAN_ONCE };
16243
16493
  if (!await this.remember(self, target, cwd, argv)) {
@@ -16245,7 +16495,7 @@ var Sudo = class {
16245
16495
  }
16246
16496
  return grant();
16247
16497
  }
16248
- listed(self, root, rules) {
16498
+ listed(self, root, rules, defaults2) {
16249
16499
  const host = this.o.hostname;
16250
16500
  if (root) {
16251
16501
  return {
@@ -16258,11 +16508,20 @@ var Sudo = class {
16258
16508
  return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
16259
16509
  }
16260
16510
  const lines2 = rules.map(described).join("\n");
16261
- 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}:
16262
16518
  ${lines2}
16263
- ` };
16519
+ `
16520
+ };
16264
16521
  }
16265
- 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;
16266
16525
  const { approve } = this.o;
16267
16526
  if (!approve || requester.aborted) return failed2("EACCES", DENIED2);
16268
16527
  const createdAt = Date.now();
@@ -16281,9 +16540,11 @@ ${lines2}
16281
16540
  ...caller.tty ? { tty: caller.tty } : {},
16282
16541
  createdAt,
16283
16542
  expiresAt: createdAt + timeout,
16284
- always: recordable(cwd, [path, ...argv]),
16543
+ always: asked.validate ? false : recordable(cwd, [path, ...argv]),
16285
16544
  stdin: caller.stdin,
16286
- 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
16287
16548
  };
16288
16549
  const control = new AbortController();
16289
16550
  const timer = setTimeout(() => control.abort("expired"), timeout);
@@ -16293,9 +16554,8 @@ ${lines2}
16293
16554
  try {
16294
16555
  const answer2 = await answerOf(approve, req, control.signal);
16295
16556
  if (answer2 === ABORTED) return ended();
16296
- if (answer2 === "once" || answer2 === "always") {
16297
- return control.signal.aborted ? ended() : answer2;
16298
- }
16557
+ const approved = approvalOf(answer2);
16558
+ if (approved) return control.signal.aborted ? ended() : approved;
16299
16559
  return failed2("EACCES", denialText(answer2));
16300
16560
  } finally {
16301
16561
  clearTimeout(timer);
@@ -19852,7 +20112,7 @@ function replaced(text, values) {
19852
20112
  for (const [ph, value2] of Object.entries(values)) out = out.split(ph).join(binary(value2));
19853
20113
  return out;
19854
20114
  }
19855
- function usable(values) {
20115
+ function usable2(values) {
19856
20116
  const out = {};
19857
20117
  for (const [ph, value2] of Object.entries(values ?? {}))
19858
20118
  if (encoder8.encode(value2).length >= MIN_VALUE) out[ph] = value2;
@@ -19867,7 +20127,7 @@ async function substituteHead(head, ask) {
19867
20127
  if (!request || !found || found.length !== 1) return void 0;
19868
20128
  const at = found[0];
19869
20129
  if (at.placeholders.length > SESSION_PLACEHOLDERS) return void 0;
19870
- const values = usable(
20130
+ const values = usable2(
19871
20131
  await ask(`header:${at.name}`, at.placeholders, {
19872
20132
  method: request[1],
19873
20133
  path: request[2]
@@ -20177,7 +20437,7 @@ async function cleartext(message3, asks) {
20177
20437
  const placeholders = placeholdersIn(password);
20178
20438
  if (placeholders.length === 0 || placeholders.length > SESSION_PLACEHOLDERS)
20179
20439
  return { bytes: message3, done: true };
20180
- const values = usable(await asks.cleartext(placeholders));
20440
+ const values = usable2(await asks.cleartext(placeholders));
20181
20441
  if (Object.keys(values).length === 0) return { bytes: message3, done: true };
20182
20442
  return { bytes: frame3(112, `${replaced(password, values)}\0`), values, done: true };
20183
20443
  }
@@ -20242,7 +20502,7 @@ var PgClient = class {
20242
20502
  const x = got.exchange;
20243
20503
  const binding = this.header === END_POINT ? this.server.binding : void 0;
20244
20504
  const serverFinal = `c=${channelOf(this.header, binding)},r=${got.nonce}`;
20245
- const values = usable(
20505
+ const values = usable2(
20246
20506
  await this.asks.scram({
20247
20507
  clientFirstBare: x.clientFirstBare,
20248
20508
  serverFirst: x.serverFirst,
@@ -20291,7 +20551,7 @@ function decoded(b642) {
20291
20551
  async function swapped(text, position, ask) {
20292
20552
  const placeholders = placeholdersIn(text);
20293
20553
  if (placeholders.length === 0 || placeholders.length > SESSION_PLACEHOLDERS) return void 0;
20294
- const values = usable(await ask(position, placeholders));
20554
+ const values = usable2(await ask(position, placeholders));
20295
20555
  if (Object.keys(values).length === 0) return void 0;
20296
20556
  return { text: replaced(text, values), values };
20297
20557
  }
@@ -28938,8 +29198,8 @@ var OpfsFs = class {
28938
29198
  const [entry] = await this.meta.get([target]);
28939
29199
  if (entry?.fifo || entry?.socket) throw fsError("ENXIO", path);
28940
29200
  const kept = fresh3 ? cached : this.files.get(target);
28941
- const usable2 = kept && performance.now() - (this.found.get(kept) ?? -CHECK_MS) < CHECK_MS;
28942
- 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];
28943
29203
  }
28944
29204
  async writeAt(handle, target, path, offset, bytes) {
28945
29205
  await this.counting(target);
@@ -31935,6 +32195,7 @@ var SUDO_SOURCE = [
31935
32195
  " await say(1, answer.list);",
31936
32196
  " return 0;",
31937
32197
  " }",
32198
+ " if (answer.done === true) return 0;",
31938
32199
  " if (answer.run !== undefined) {",
31939
32200
  " try {",
31940
32201
  " pid = (await ctx.spawn({ argv: answer.run, pgid: 0 })).pid;",
@@ -32570,6 +32831,7 @@ var Launcher = class {
32570
32831
  );
32571
32832
  }
32572
32833
  this.users = new UserDb(this.fs, this.owners);
32834
+ const { sudoClock } = options;
32573
32835
  this.sudoer = new Sudo({
32574
32836
  fs: this.fs,
32575
32837
  users: this.users,
@@ -32579,7 +32841,8 @@ var Launcher = class {
32579
32841
  pnpmHome: this.pnpmHome,
32580
32842
  backing: (path, cwd) => this.backing(path, cwd),
32581
32843
  approve: options.approve,
32582
- timeoutMs: options.sudoTimeoutMs
32844
+ timeoutMs: options.sudoTimeoutMs,
32845
+ now: sudoClock ? () => sudoClock.now() : void 0
32583
32846
  });
32584
32847
  this.cron = this.cronService(options.cron);
32585
32848
  void this.unlinked.sweep("/");
@@ -33985,11 +34248,26 @@ var Launcher = class {
33985
34248
  pid: this.jobs.shown(pid),
33986
34249
  tags: this.tagsOf(pid),
33987
34250
  tty: this.described.get(pid)?.tty,
34251
+ ...this.timestampOwner(pid),
33988
34252
  stdin,
33989
34253
  report
33990
34254
  };
33991
34255
  return this.sudoer.request(caller, call, signal);
33992
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
+ }
33993
34271
  visudo(pid, call) {
33994
34272
  return this.sudoer.check(this.credOf(pid), call);
33995
34273
  }
@@ -37676,6 +37954,7 @@ async function createNodeKernel(options = {}) {
37676
37954
  ...options.clipboard ? { clipboard: options.clipboard } : {},
37677
37955
  ...options.requests ? { embedder: checkedRequests(options.requests) } : {},
37678
37956
  ...options.approve ? { approve: options.approve } : {},
37957
+ ...options.sudo?.clock ? { sudoClock: options.sudo.clock } : {},
37679
37958
  ...options.wasmMemories ? { wasmMemories: options.wasmMemories } : {},
37680
37959
  cron: { lock: cronLock(root), ...options.cron?.clock ? { clock: options.cron.clock } : {} }
37681
37960
  });