@ai-ecoverse/slicc-kernel 1.43.0 → 1.44.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
@@ -1584,8 +1584,289 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
1584
1584
  };
1585
1585
  }
1586
1586
 
1587
+ // src/kernel/cred.ts
1588
+ var ROOT = Object.freeze({
1589
+ ruid: 0,
1590
+ euid: 0,
1591
+ suid: 0,
1592
+ rgid: 0,
1593
+ egid: 0,
1594
+ sgid: 0,
1595
+ groups: [0]
1596
+ });
1597
+ var MAX_ID = 4294967294;
1598
+ var MAX_GROUPS = 65536;
1599
+ var UIDS = ["ruid", "euid", "suid"];
1600
+ var GIDS = ["rgid", "egid", "sgid"];
1601
+ function userCred(uid, gid, groups = [gid]) {
1602
+ return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
1603
+ }
1604
+ function copyCred(cred) {
1605
+ return { ...cred, groups: [...cred.groups] };
1606
+ }
1607
+ function id(value) {
1608
+ if (value === void 0 || value === -1 || value === 4294967295) return void 0;
1609
+ if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
1610
+ throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
1611
+ }
1612
+ return value;
1613
+ }
1614
+ function changeCred(cur, change) {
1615
+ const privileged = cur.euid === 0;
1616
+ const next2 = copyCred(cur);
1617
+ for (const keys of [UIDS, GIDS]) {
1618
+ const own = keys.map((key) => cur[key]);
1619
+ for (const key of keys) {
1620
+ const value = id(change[key]);
1621
+ if (value === void 0) continue;
1622
+ if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
1623
+ next2[key] = value;
1624
+ }
1625
+ }
1626
+ if (change.groups !== void 0) {
1627
+ if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
1628
+ throw fsError("EINVAL", "not a group list");
1629
+ }
1630
+ const groups = change.groups.map((g) => id(g) ?? Number.NaN);
1631
+ if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
1632
+ if (!privileged) throw fsError("EPERM", "setgroups");
1633
+ next2.groups = groups;
1634
+ }
1635
+ return next2;
1636
+ }
1637
+ function maySignal(sender, target) {
1638
+ if (sender.euid === 0) return true;
1639
+ return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
1640
+ }
1641
+ function maySee(viewer, target) {
1642
+ return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
1643
+ }
1644
+
1645
+ // src/users.ts
1646
+ var PASSWD = "/etc/passwd";
1647
+ var GROUP = "/etc/group";
1648
+ var DEFAULT_PASSWD = "root:x:0:0:root:/root:/bin/bash\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\n";
1649
+ var DEFAULT_GROUP = "root:x:0:\nsudo:x:27:\nusers:x:100:\nnogroup:x:65534:\n";
1650
+ var NAME = /^[a-z_][a-z0-9_.-]*$/;
1651
+ var MAX_NAME = 255;
1652
+ var FIRST_UID = 1e3;
1653
+ var LAST_UID = 6e4;
1654
+ var USERS_GROUP = "users";
1655
+ var HOME_DIR = /^\/home\/[^/]+$/;
1656
+ var SYSTEM_PNPM_HOME = "/usr/local/share/pnpm";
1657
+ var isId = (text) => text !== void 0 && /^\d+$/.test(text);
1658
+ function lines(text) {
1659
+ return text.split("\n").filter((line, i, all) => line !== "" || i < all.length - 1);
1660
+ }
1661
+ function parsePasswd(text) {
1662
+ return lines(text).map((line) => {
1663
+ const [name2, , uid, gid, gecos = "", home = "/", shell = "/bin/sh"] = line.split(":");
1664
+ if (!name2 || !isId(uid) || !isId(gid)) return line;
1665
+ return { name: name2, uid: Number(uid), gid: Number(gid), gecos, home, shell };
1666
+ });
1667
+ }
1668
+ function parseGroup(text) {
1669
+ return lines(text).map((line) => {
1670
+ const [name2, , gid, members = ""] = line.split(":");
1671
+ if (!name2 || !isId(gid)) return line;
1672
+ return { name: name2, gid: Number(gid), members: members.split(",").filter(Boolean) };
1673
+ });
1674
+ }
1675
+ function formatPasswd(rows) {
1676
+ const line = (r) => `${r.name}:x:${r.uid}:${r.gid}:${r.gecos}:${r.home}:${r.shell}`;
1677
+ return `${rows.map((r) => typeof r === "string" ? r : line(r)).join("\n")}
1678
+ `;
1679
+ }
1680
+ function formatGroup(rows) {
1681
+ const line = (r) => `${r.name}:x:${r.gid}:${r.members.join(",")}`;
1682
+ return `${rows.map((r) => typeof r === "string" ? r : line(r)).join("\n")}
1683
+ `;
1684
+ }
1685
+ var parsed = (rows) => rows.filter((r) => typeof r !== "string");
1686
+ function homeOf(home) {
1687
+ if (typeof home !== "string" || !home.startsWith("/") || /[:\r\n]/.test(home)) {
1688
+ throw fsError("EINVAL", `not a home directory: ${String(home)}`);
1689
+ }
1690
+ return home;
1691
+ }
1692
+ function freeId(users, groups) {
1693
+ const taken = /* @__PURE__ */ new Set([...users.map((r) => r.uid), ...groups.map((r) => r.gid)]);
1694
+ let uid = FIRST_UID;
1695
+ while (taken.has(uid)) uid++;
1696
+ if (uid > LAST_UID) throw fsError("ENOSPC", "no free user id");
1697
+ return uid;
1698
+ }
1699
+ function credOf(account) {
1700
+ return userCred(account.uid, account.gid, account.groups);
1701
+ }
1702
+ function pnpmHomeOf(account, system = SYSTEM_PNPM_HOME) {
1703
+ return account.uid === 0 ? system : `${account.home}/.local/share/pnpm`;
1704
+ }
1705
+ function userEnv(account, system) {
1706
+ const pnpm = pnpmHomeOf(account, system);
1707
+ return {
1708
+ HOME: account.home,
1709
+ USER: account.name,
1710
+ LOGNAME: account.name,
1711
+ PNPM_HOME: pnpm,
1712
+ PATH: `${pnpm}/bin:/usr/local/bin:/usr/bin:/bin`
1713
+ };
1714
+ }
1715
+ var UserDb = class {
1716
+ fs;
1717
+ umasks = /* @__PURE__ */ new Map();
1718
+ writing = Promise.resolve();
1719
+ constructor(fs) {
1720
+ this.fs = fs;
1721
+ }
1722
+ async read(path, fallback) {
1723
+ try {
1724
+ return await this.fs.readFile(path);
1725
+ } catch (err) {
1726
+ if (err?.code === "ENOENT") return fallback;
1727
+ throw err;
1728
+ }
1729
+ }
1730
+ async tables() {
1731
+ return {
1732
+ passwd: parsePasswd(await this.read(PASSWD, DEFAULT_PASSWD)),
1733
+ group: parseGroup(await this.read(GROUP, DEFAULT_GROUP))
1734
+ };
1735
+ }
1736
+ async commit(passwd, group, then) {
1737
+ const before = [await this.read(PASSWD, DEFAULT_PASSWD), await this.read(GROUP, DEFAULT_GROUP)];
1738
+ try {
1739
+ await this.fs.writeFile(GROUP, group);
1740
+ await this.fs.writeFile(PASSWD, passwd);
1741
+ await then?.();
1742
+ } catch (err) {
1743
+ await this.fs.writeFile(GROUP, before[1]);
1744
+ await this.fs.writeFile(PASSWD, before[0]);
1745
+ throw err;
1746
+ }
1747
+ }
1748
+ serial(work) {
1749
+ const next2 = this.writing.then(work);
1750
+ this.writing = next2.catch(() => void 0);
1751
+ return next2;
1752
+ }
1753
+ async ensure() {
1754
+ await this.fs.mkdir("/etc", { recursive: true });
1755
+ for (const [path, text] of [
1756
+ [PASSWD, DEFAULT_PASSWD],
1757
+ [GROUP, DEFAULT_GROUP]
1758
+ ]) {
1759
+ if (!await this.fs.exists(path)) await this.fs.writeFile(path, text);
1760
+ }
1761
+ if (!await this.fs.exists("/root")) {
1762
+ await this.fs.mkdir("/root", { recursive: true });
1763
+ try {
1764
+ await this.fs.chmod("/root", 448);
1765
+ } catch (err) {
1766
+ await this.fs.rm("/root", { recursive: true, force: true });
1767
+ throw err;
1768
+ }
1769
+ }
1770
+ }
1771
+ account(row, groups) {
1772
+ const extra = groups.filter((g) => g.members.includes(row.name) && g.gid !== row.gid);
1773
+ const umask = this.umasks.get(row.name);
1774
+ return {
1775
+ name: row.name,
1776
+ uid: row.uid,
1777
+ gid: row.gid,
1778
+ home: row.home,
1779
+ shell: row.shell,
1780
+ groups: [row.gid, ...extra.map((g) => g.gid)],
1781
+ ...umask !== void 0 ? { umask } : {}
1782
+ };
1783
+ }
1784
+ async lookup(user) {
1785
+ const { passwd, group } = await this.tables();
1786
+ const key = typeof user === "number" ? "uid" : "name";
1787
+ const row = parsed(passwd).find((r) => r[key] === user);
1788
+ return row && this.account(row, parsed(group));
1789
+ }
1790
+ async list() {
1791
+ const { passwd, group } = await this.tables();
1792
+ const groups = parsed(group);
1793
+ return parsed(passwd).map((row) => this.account(row, groups));
1794
+ }
1795
+ add(options) {
1796
+ return this.serial(async () => {
1797
+ const { name: name2 } = options;
1798
+ if (typeof name2 !== "string" || name2.length > MAX_NAME || !NAME.test(name2)) {
1799
+ throw fsError("EINVAL", `not a user name: ${String(name2)}`);
1800
+ }
1801
+ const remember = () => {
1802
+ if (options.umask !== void 0) this.umasks.set(name2, options.umask & 511);
1803
+ };
1804
+ const { passwd, group } = await this.tables();
1805
+ const users = parsed(passwd);
1806
+ const groups = parsed(group);
1807
+ const found = users.find((r) => r.name === name2);
1808
+ if (found) {
1809
+ remember();
1810
+ return this.account(found, groups);
1811
+ }
1812
+ const home = homeOf(options.home ?? `/home/${name2}`);
1813
+ const extra = [USERS_GROUP, ...options.groups ?? []].map((g) => {
1814
+ const row2 = groups.find((r) => r.name === g);
1815
+ if (!row2) throw fsError("ENOENT", `no group ${g}`);
1816
+ return row2;
1817
+ });
1818
+ if (groups.some((r) => r.name === name2)) throw fsError("EEXIST", `group ${name2} exists`);
1819
+ const uid = freeId(users, groups);
1820
+ const row = { name: name2, uid, gid: uid, gecos: name2, home, shell: "/bin/bash" };
1821
+ for (const g of extra) if (!g.members.includes(name2)) g.members.push(name2);
1822
+ group.push({ name: name2, gid: uid, members: [] });
1823
+ passwd.push(row);
1824
+ const fresh = !await this.fs.exists(home);
1825
+ await this.fs.mkdir(home, { recursive: true });
1826
+ try {
1827
+ await this.fs.mkdir("/etc", { recursive: true });
1828
+ await this.commit(formatPasswd(passwd), formatGroup(group));
1829
+ } catch (err) {
1830
+ if (fresh) await this.fs.rm(home, { recursive: true, force: true });
1831
+ throw err;
1832
+ }
1833
+ remember();
1834
+ return this.account(row, parsed(group));
1835
+ });
1836
+ }
1837
+ remove(name2, options = {}) {
1838
+ return this.serial(async () => {
1839
+ const { passwd, group } = await this.tables();
1840
+ const row = parsed(passwd).find((r) => r.name === name2);
1841
+ if (!row) return false;
1842
+ if (row.uid < FIRST_UID || row.uid > LAST_UID) {
1843
+ throw fsError("EPERM", `${name2} is a system account`);
1844
+ }
1845
+ const keptGroups = group.filter(
1846
+ (r) => typeof r === "string" || !(r.name === name2 && r.gid === row.gid)
1847
+ );
1848
+ for (const r of parsed(keptGroups)) r.members = r.members.filter((m) => m !== name2);
1849
+ const home = this.fs.resolvePath("/", row.home);
1850
+ const shared = parsed(passwd).some(
1851
+ (r) => r !== row && this.fs.resolvePath("/", r.home) === home
1852
+ );
1853
+ const removeHome = !options.keepHome && !shared && HOME_DIR.test(home) && await this.fs.exists(home);
1854
+ const staged = `/home/.${name2}.removed-${Date.now()}`;
1855
+ await this.commit(
1856
+ formatPasswd(passwd.filter((r) => r !== row)),
1857
+ formatGroup(keptGroups),
1858
+ removeHome ? () => this.fs.rename(home, staged) : void 0
1859
+ );
1860
+ this.umasks.delete(name2);
1861
+ if (removeHome)
1862
+ await this.fs.rm(staged, { recursive: true, force: true }).catch(() => void 0);
1863
+ return true;
1864
+ });
1865
+ }
1866
+ };
1867
+
1587
1868
  // src/client/protocol.ts
1588
- var PROTOCOL = [1, 7];
1869
+ var PROTOCOL = [1, 8];
1589
1870
  var FS_METHODS = [
1590
1871
  "readFile",
1591
1872
  "writeFile",
@@ -1688,6 +1969,39 @@ function dialFor(l, req, open) {
1688
1969
  );
1689
1970
  return { result: port2, transfer: [port2] };
1690
1971
  }
1972
+ async function accountOf(l, user) {
1973
+ const found = await l.users.lookup(user);
1974
+ if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
1975
+ return found;
1976
+ }
1977
+ async function runAs(l, self, requested) {
1978
+ if (requested === void 0) return self;
1979
+ const target = await accountOf(l, requested);
1980
+ if (self.uid !== 0 && target.uid !== self.uid) {
1981
+ throw fsError("EPERM", `${self.name} may not run as ${target.name}`);
1982
+ }
1983
+ return target;
1984
+ }
1985
+ function processes(l, self) {
1986
+ const viewer = credOf(self);
1987
+ return l.list(viewer).map(({ tid: _, cred = viewer, umask: _umask, ...entry }) => ({
1988
+ ...entry,
1989
+ uid: cred.euid,
1990
+ gid: cred.egid
1991
+ }));
1992
+ }
1993
+ function killAs(l, host, self, req) {
1994
+ if (!l.kill(req.pid, signalOf(host)(req.signal), credOf(self))) {
1995
+ throw fsError("ESRCH", `pid ${req.pid}`);
1996
+ }
1997
+ return { result: true };
1998
+ }
1999
+ function terminalAction(terminals, req, host) {
2000
+ const session = terminals.get(req.terminal);
2001
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
2002
+ act(session, req, signalOf(host));
2003
+ return { result: true };
2004
+ }
1691
2005
  function killGroups(host, pgids) {
1692
2006
  if (pgids.length === 0) return;
1693
2007
  void host.launcher().then((launcher) => {
@@ -1701,9 +2015,9 @@ function joinable(l, pgid, ours) {
1701
2015
  }
1702
2016
  function cdpRegistry(send, gone) {
1703
2017
  const registered = /* @__PURE__ */ new Map();
1704
- const drop = (id) => {
1705
- registered.get(id)?.();
1706
- return registered.delete(id);
2018
+ const drop = (id2) => {
2019
+ registered.get(id2)?.();
2020
+ return registered.delete(id2);
1707
2021
  };
1708
2022
  return {
1709
2023
  serve(l, req) {
@@ -1718,7 +2032,7 @@ function cdpRegistry(send, gone) {
1718
2032
  },
1719
2033
  drop,
1720
2034
  close: () => {
1721
- for (const id of [...registered.keys()]) drop(id);
2035
+ for (const id2 of [...registered.keys()]) drop(id2);
1722
2036
  }
1723
2037
  };
1724
2038
  }
@@ -1738,6 +2052,7 @@ function watchOp(l, req, watches, send) {
1738
2052
  }
1739
2053
  function serveClient(port, host) {
1740
2054
  let state = "new";
2055
+ let self;
1741
2056
  const terminals = /* @__PURE__ */ new Map();
1742
2057
  const groups = /* @__PURE__ */ new Set();
1743
2058
  const sessions = /* @__PURE__ */ new Set();
@@ -1747,7 +2062,7 @@ function serveClient(port, host) {
1747
2062
  const send = (message2, transfer = []) => {
1748
2063
  if (state !== "closed") port.postMessage(message2, transfer);
1749
2064
  };
1750
- const reply = (id, body, transfer) => send({ id, ...body }, transfer);
2065
+ const reply = (id2, body, transfer) => send({ id: id2, ...body }, transfer);
1751
2066
  const cdps = cdpRegistry(send, ended.promise);
1752
2067
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
1753
2068
  if (state === "closed") return;
@@ -1772,6 +2087,7 @@ function serveClient(port, host) {
1772
2087
  return;
1773
2088
  }
1774
2089
  const launcher = await host.launcher();
2090
+ if (host.scope !== "transport") self = await accountOf(launcher, host.user ?? 0);
1775
2091
  const transport = host.transport?.() ?? launcher.transport;
1776
2092
  net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
1777
2093
  state = "open";
@@ -1787,6 +2103,7 @@ function serveClient(port, host) {
1787
2103
  async function terminal(req, l) {
1788
2104
  const session = await l.openTerminal(req.argv, {
1789
2105
  ...req.options,
2106
+ user: await runAs(l, self, req.options.user),
1790
2107
  onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
1791
2108
  });
1792
2109
  if (state === "closed") {
@@ -1808,6 +2125,7 @@ function serveClient(port, host) {
1808
2125
  let leader;
1809
2126
  const result = await l.run(req.argv, {
1810
2127
  ...req.options,
2128
+ user: await runAs(l, self, req.options.user),
1811
2129
  collect: false,
1812
2130
  onStdout: out(1),
1813
2131
  onStderr: out(2),
@@ -1833,19 +2151,12 @@ function serveClient(port, host) {
1833
2151
  return spawn(req, l);
1834
2152
  case "open-terminal":
1835
2153
  return terminal(req, l);
1836
- case "terminal": {
1837
- const session = terminals.get(req.terminal);
1838
- if (!session) throw new Error(`no terminal ${req.terminal}`);
1839
- act(session, req, signalOf(host));
1840
- return { result: true };
1841
- }
2154
+ case "terminal":
2155
+ return terminalAction(terminals, req, host);
1842
2156
  case "kill":
1843
- if (!l.kill(req.pid, signalOf(host)(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
1844
- return { result: true };
2157
+ return killAs(l, host, self, req);
1845
2158
  case "ps":
1846
- return {
1847
- result: l.list().map(({ tid: _, ...entry }) => entry)
1848
- };
2159
+ return { result: processes(l, self) };
1849
2160
  case "fs":
1850
2161
  if (!FS_METHODS.includes(req.method)) {
1851
2162
  throw new Error(`unknown file system call ${req.method}`);
@@ -2611,9 +2922,9 @@ var UNLINKED_DIR = ".slicc-unlinked";
2611
2922
  function pidOwner(proc) {
2612
2923
  return {
2613
2924
  id: `p${proc.pid}`,
2614
- alive(id) {
2925
+ alive(id2) {
2615
2926
  try {
2616
- proc.kill(Number(id.slice(1)), 0);
2927
+ proc.kill(Number(id2.slice(1)), 0);
2617
2928
  return true;
2618
2929
  } catch (err) {
2619
2930
  return err.code !== "ESRCH";
@@ -2800,9 +3111,9 @@ var UnlinkedKeeper = class {
2800
3111
  if (!names) return;
2801
3112
  const held = query && new Set(((await query()).held ?? []).map((lock) => lock.name));
2802
3113
  for (const name2 of names) {
2803
- const id = name2.slice(0, name2.lastIndexOf("."));
2804
- const at = id.indexOf("~");
2805
- const dead = id !== this.instance && (at < 0 ? held !== void 0 && !held.has(lockName(id)) : owner?.alive(id.slice(0, at)) === false);
3114
+ const id2 = name2.slice(0, name2.lastIndexOf("."));
3115
+ const at = id2.indexOf("~");
3116
+ const dead = id2 !== this.instance && (at < 0 ? held !== void 0 && !held.has(lockName(id2)) : owner?.alive(id2.slice(0, at)) === false);
2806
3117
  if (dead) await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
2807
3118
  }
2808
3119
  await this.tidy(dir);
@@ -2938,21 +3249,21 @@ function latin1Bytes(text) {
2938
3249
  return out;
2939
3250
  }
2940
3251
  function parseRequestHead(bytes) {
2941
- const lines = latin1(bytes).split(/\r?\n/);
2942
- while (lines.length > 0 && lines[0] === "") lines.shift();
2943
- const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
3252
+ const lines2 = latin1(bytes).split(/\r?\n/);
3253
+ while (lines2.length > 0 && lines2[0] === "") lines2.shift();
3254
+ const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines2.shift() ?? "");
2944
3255
  if (!match) throw new HttpError(400, "malformed request line");
2945
3256
  if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
2946
3257
  return {
2947
3258
  method: match[1],
2948
3259
  target: match[2],
2949
3260
  minor: Number(match[4]),
2950
- headers: parseFields(lines)
3261
+ headers: parseFields(lines2)
2951
3262
  };
2952
3263
  }
2953
- function parseFields(lines) {
3264
+ function parseFields(lines2) {
2954
3265
  const headers = [];
2955
- for (const line of lines) {
3266
+ for (const line of lines2) {
2956
3267
  if (line === "") continue;
2957
3268
  if (line.startsWith(" ") || line.startsWith(" ")) {
2958
3269
  throw new HttpError(400, "obsolete line folding");
@@ -3380,10 +3691,10 @@ var CdpFacade = class {
3380
3691
  reject(new HttpError(502, `the CDP host closed: ${reason ?? "no reason"}`));
3381
3692
  };
3382
3693
  conn.onmessage = (message2) => {
3383
- const parsed = JSON.parse(message2);
3384
- if (parsed.id !== 1) return;
3694
+ const parsed2 = JSON.parse(message2);
3695
+ if (parsed2.id !== 1) return;
3385
3696
  clearTimeout(timer);
3386
- resolve(parsed);
3697
+ resolve(parsed2);
3387
3698
  };
3388
3699
  conn.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
3389
3700
  }
@@ -3556,7 +3867,7 @@ function closeAll(entry) {
3556
3867
  }
3557
3868
 
3558
3869
  // src/commands.ts
3559
- var NAME = /^[A-Za-z0-9._+-]+$/;
3870
+ var NAME2 = /^[A-Za-z0-9._+-]+$/;
3560
3871
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
3561
3872
  var PACKAGE = /\$\{package\}/g;
3562
3873
  function inside(pkg, rel2) {
@@ -3645,7 +3956,7 @@ function commandsOf(pkg, manifest) {
3645
3956
  const shared = envOf(pkg, slicc.env);
3646
3957
  const out = [];
3647
3958
  for (const [name2, raw] of Object.entries(entries)) {
3648
- if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
3959
+ if (!NAME2.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
3649
3960
  continue;
3650
3961
  }
3651
3962
  const command = commandOf(pkg, name2, raw, packageAbi, shared);
@@ -3729,7 +4040,7 @@ async function scanFilesystems(fs, modules) {
3729
4040
  if (!declared || typeof declared !== "object") continue;
3730
4041
  for (const [type, entry] of Object.entries(declared)) {
3731
4042
  const module = inside(pkg, entry?.module);
3732
- if (NAME.test(type) && module && !found.has(type)) found.set(type, module);
4043
+ if (NAME2.test(type) && module && !found.has(type)) found.set(type, module);
3733
4044
  }
3734
4045
  }
3735
4046
  return found;
@@ -3749,7 +4060,7 @@ async function scanBinfmts(fs, modules, warn = () => {
3749
4060
  const declared = manifest.slicc?.binfmt;
3750
4061
  if (!declared || typeof declared !== "object") continue;
3751
4062
  for (const [suffix, command] of Object.entries(declared)) {
3752
- if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME.test(command)) continue;
4063
+ if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME2.test(command)) continue;
3753
4064
  const owner = owners.get(suffix);
3754
4065
  if (owner === void 0) {
3755
4066
  found.set(suffix, command);
@@ -4286,11 +4597,11 @@ var DriverConnection = class _DriverConnection {
4286
4597
  }
4287
4598
  call(call, transfer = []) {
4288
4599
  if (this.failure) return Promise.reject(this.failure);
4289
- const id = ++this.nextId;
4600
+ const id2 = ++this.nextId;
4290
4601
  return new Promise((resolve, reject) => {
4291
4602
  const timer = unref(
4292
4603
  setTimeout(() => {
4293
- this.waiting.delete(id);
4604
+ this.waiting.delete(id2);
4294
4605
  const error = errnoError(
4295
4606
  "EIO",
4296
4607
  `the driver did not answer ${call.op} within ${this.timeoutMs} ms`
@@ -4300,8 +4611,8 @@ var DriverConnection = class _DriverConnection {
4300
4611
  this.onFail?.(error);
4301
4612
  }, this.timeoutMs)
4302
4613
  );
4303
- this.waiting.set(id, { resolve, reject, timer });
4304
- this.port.postMessage({ ...call, id }, transfer);
4614
+ this.waiting.set(id2, { resolve, reject, timer });
4615
+ this.port.postMessage({ ...call, id: id2 }, transfer);
4305
4616
  });
4306
4617
  }
4307
4618
  fail(error) {
@@ -4376,22 +4687,22 @@ var HeldPaths = class extends Map {
4376
4687
  }
4377
4688
  isKept(path) {
4378
4689
  const ids = this.handlesAt(path);
4379
- return ids.length === 0 ? this.unlinked.has(path) : ids.every((id) => this.keptHandles.has(id));
4690
+ return ids.length === 0 ? this.unlinked.has(path) : ids.every((id2) => this.keptHandles.has(id2));
4380
4691
  }
4381
4692
  handlesAt(path) {
4382
- return [...this.handles].filter(([, key]) => key === path).map(([id]) => id);
4693
+ return [...this.handles].filter(([, key]) => key === path).map(([id2]) => id2);
4383
4694
  }
4384
4695
  keptFor(handle) {
4385
4696
  return this.keptHandles.get(handle);
4386
4697
  }
4387
4698
  keep(path, file) {
4388
- const fresh = this.handlesAt(path).filter((id) => !this.keptHandles.has(id));
4389
- for (const id of fresh) this.keptHandles.set(id, file);
4699
+ const fresh = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
4700
+ for (const id2 of fresh) this.keptHandles.set(id2, file);
4390
4701
  this.uses.set(file, Math.max(fresh.length, 1));
4391
4702
  this.unlinked.set(path, file);
4392
4703
  }
4393
4704
  unkeep(path, file) {
4394
- for (const [id, kept] of this.keptHandles) if (kept === file) this.keptHandles.delete(id);
4705
+ for (const [id2, kept] of this.keptHandles) if (kept === file) this.keptHandles.delete(id2);
4395
4706
  this.uses.delete(file);
4396
4707
  if (this.unlinked.get(path) === file) this.unlinked.delete(path);
4397
4708
  }
@@ -4428,7 +4739,7 @@ var HeldPaths = class extends Map {
4428
4739
  if (count > 0) this.opens.set(key, count);
4429
4740
  else {
4430
4741
  this.opens.delete(key);
4431
- for (const id of this.handlesAt(key)) this.handles.delete(id);
4742
+ for (const id2 of this.handlesAt(key)) this.handles.delete(id2);
4432
4743
  const kept = this.unlinked.get(key);
4433
4744
  if (kept) this.letGo(kept, true);
4434
4745
  for (const [alias, target] of this.aliases) if (target === key) this.aliases.delete(alias);
@@ -4475,8 +4786,8 @@ var HeldPaths = class extends Map {
4475
4786
  this.opens.delete(path);
4476
4787
  this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
4477
4788
  }
4478
- for (const [id, key] of this.handles) {
4479
- if (within3(key, from)) this.handles.set(id, to + key.slice(from.length));
4789
+ for (const [id2, key] of this.handles) {
4790
+ if (within3(key, from)) this.handles.set(id2, to + key.slice(from.length));
4480
4791
  }
4481
4792
  }
4482
4793
  isRevoked(path) {
@@ -5445,15 +5756,15 @@ function attachSyncSabResponder(port, sab, token, opts) {
5445
5756
  const pending = /* @__PURE__ */ new Map();
5446
5757
  let disposed = false;
5447
5758
  const { dispatch: dispatch2 } = opts;
5448
- function drop(id) {
5449
- const entry = pending.get(id);
5759
+ function drop(id2) {
5760
+ const entry = pending.get(id2);
5450
5761
  if (!entry) return;
5451
5762
  clearTimeout(entry.timer);
5452
- pending.delete(id);
5763
+ pending.delete(id2);
5453
5764
  }
5454
- function publish(id, entry, offset) {
5455
- if (Atomics.load(header3, SAB_I_REQ) !== id) {
5456
- drop(id);
5765
+ function publish(id2, entry, offset) {
5766
+ if (Atomics.load(header3, SAB_I_REQ) !== id2) {
5767
+ drop(id2);
5457
5768
  return;
5458
5769
  }
5459
5770
  const chunk2 = Math.min(window.byteLength, entry.payload.byteLength - offset);
@@ -5462,23 +5773,23 @@ function attachSyncSabResponder(port, sab, token, opts) {
5462
5773
  storeU53(header3, SAB_I_TOTAL, SAB_I_TOTAL_HI, entry.payload.byteLength);
5463
5774
  Atomics.store(header3, SAB_I_CHUNK, chunk2);
5464
5775
  storeU53(header3, SAB_I_OFFSET, SAB_I_OFFSET_HI, offset);
5465
- Atomics.store(header3, SAB_I_SEQ, id);
5776
+ Atomics.store(header3, SAB_I_SEQ, id2);
5466
5777
  Atomics.store(header3, SAB_I_STATE, SAB_STATE_READY);
5467
5778
  Atomics.add(header3, SAB_I_PUB, 1);
5468
5779
  Atomics.notify(header3, SAB_I_PUB);
5469
- if (offset + chunk2 >= entry.payload.byteLength) drop(id);
5780
+ if (offset + chunk2 >= entry.payload.byteLength) drop(id2);
5470
5781
  }
5471
- function settle(id, result) {
5782
+ function settle(id2, result) {
5472
5783
  if (disposed) return;
5473
5784
  const { status, payload } = encodeSabResult(result);
5474
- drop(id);
5785
+ drop(id2);
5475
5786
  const entry = {
5476
5787
  status,
5477
5788
  payload,
5478
- timer: setTimeout(() => drop(id), PENDING_TTL_MS)
5789
+ timer: setTimeout(() => drop(id2), PENDING_TTL_MS)
5479
5790
  };
5480
- pending.set(id, entry);
5481
- publish(id, entry, 0);
5791
+ pending.set(id2, entry);
5792
+ publish(id2, entry, 0);
5482
5793
  }
5483
5794
  const handler = (event) => {
5484
5795
  const data = event.data;
@@ -5494,14 +5805,14 @@ function attachSyncSabResponder(port, sab, token, opts) {
5494
5805
  return;
5495
5806
  }
5496
5807
  if (data.type !== SYNC_SAB_REQ_MSG || !data.req || typeof data.req !== "object") return;
5497
- const id = data.id;
5808
+ const id2 = data.id;
5498
5809
  const body = data.req;
5499
5810
  const req = { ...body, token };
5500
5811
  let dispatched;
5501
5812
  try {
5502
5813
  dispatched = dispatch2(req);
5503
5814
  } catch (err) {
5504
- settle(id, {
5815
+ settle(id2, {
5505
5816
  ok: false,
5506
5817
  errno: "EIO",
5507
5818
  message: err instanceof Error ? err.message : String(err)
@@ -5509,8 +5820,8 @@ function attachSyncSabResponder(port, sab, token, opts) {
5509
5820
  return;
5510
5821
  }
5511
5822
  void dispatched.then(
5512
- (result) => settle(id, result),
5513
- (err) => settle(id, {
5823
+ (result) => settle(id2, result),
5824
+ (err) => settle(id2, {
5514
5825
  ok: false,
5515
5826
  errno: "EIO",
5516
5827
  message: err instanceof Error ? err.message : String(err)
@@ -5523,7 +5834,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
5523
5834
  if (disposed) return;
5524
5835
  disposed = true;
5525
5836
  port.removeEventListener("message", handler);
5526
- for (const id of [...pending.keys()]) drop(id);
5837
+ for (const id2 of [...pending.keys()]) drop(id2);
5527
5838
  }
5528
5839
  };
5529
5840
  }
@@ -5583,47 +5894,47 @@ var AsyncOps = class {
5583
5894
  this.onReady = onReady;
5584
5895
  }
5585
5896
  submit(req) {
5586
- const id = this.hold();
5897
+ const id2 = this.hold();
5587
5898
  const running = new AbortController();
5588
- this.running.set(id, running);
5899
+ this.running.set(id2, running);
5589
5900
  this.run(req, running.signal).then(
5590
- (result) => this.complete(id, result),
5591
- (err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
5901
+ (result) => this.complete(id2, result),
5902
+ (err) => this.complete(id2, { ok: false, errno: "EIO", message: String(err) })
5592
5903
  );
5593
- return id;
5904
+ return id2;
5594
5905
  }
5595
5906
  resolve(value) {
5596
- const id = this.hold();
5597
- this.complete(id, json3(value));
5598
- return id;
5907
+ const id2 = this.hold();
5908
+ this.complete(id2, json3(value));
5909
+ return id2;
5599
5910
  }
5600
5911
  hold() {
5601
- const id = this.next++;
5602
- this.results.set(id, void 0);
5603
- return id;
5912
+ const id2 = this.next++;
5913
+ this.results.set(id2, void 0);
5914
+ return id2;
5604
5915
  }
5605
- take(id) {
5606
- if (!this.results.has(id)) throw new KernelError("EINVAL");
5607
- const result = this.results.get(id);
5916
+ take(id2) {
5917
+ if (!this.results.has(id2)) throw new KernelError("EINVAL");
5918
+ const result = this.results.get(id2);
5608
5919
  if (!result) throw new KernelError("EAGAIN");
5609
- this.forget(id);
5920
+ this.forget(id2);
5610
5921
  return result;
5611
5922
  }
5612
- cancel(id) {
5613
- this.running.get(id)?.abort();
5614
- this.forget(id);
5923
+ cancel(id2) {
5924
+ this.running.get(id2)?.abort();
5925
+ this.forget(id2);
5615
5926
  }
5616
- forget(id) {
5617
- this.running.delete(id);
5618
- this.results.delete(id);
5619
- const at = this.ready.indexOf(id);
5927
+ forget(id2) {
5928
+ this.running.delete(id2);
5929
+ this.results.delete(id2);
5930
+ const at = this.ready.indexOf(id2);
5620
5931
  if (at >= 0) this.ready.splice(at, 1);
5621
5932
  }
5622
5933
  async wait(timeoutMs, signal) {
5623
5934
  for (; ; ) {
5624
5935
  if (this.closed) return -1;
5625
- const id = this.ready.shift();
5626
- if (id !== void 0) return id;
5936
+ const id2 = this.ready.shift();
5937
+ if (id2 !== void 0) return id2;
5627
5938
  if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
5628
5939
  const woke = await this.sleep(timeoutMs, signal);
5629
5940
  if (!woke) return 0;
@@ -5659,11 +5970,11 @@ var AsyncOps = class {
5659
5970
  }
5660
5971
  });
5661
5972
  }
5662
- complete(id, result) {
5663
- this.running.delete(id);
5664
- if (!this.results.has(id)) return;
5665
- this.results.set(id, result);
5666
- this.ready.push(id);
5973
+ complete(id2, result) {
5974
+ this.running.delete(id2);
5975
+ if (!this.results.has(id2)) return;
5976
+ this.results.set(id2, result);
5977
+ this.ready.push(id2);
5667
5978
  this.wake();
5668
5979
  this.onReady?.();
5669
5980
  }
@@ -6340,8 +6651,8 @@ function name(dn) {
6340
6651
  rdns.push(set(seq(oid("2.5.4.3"), utf8(dn.commonName))));
6341
6652
  return seq(...rdns);
6342
6653
  }
6343
- function extension(id, critical, value) {
6344
- return critical ? seq(oid(id), TRUE, octets(value)) : seq(oid(id), octets(value));
6654
+ function extension(id2, critical, value) {
6655
+ return critical ? seq(oid(id2), TRUE, octets(value)) : seq(oid(id2), octets(value));
6345
6656
  }
6346
6657
  function children(der) {
6347
6658
  const out = [];
@@ -6457,9 +6768,9 @@ function pem(der) {
6457
6768
  for (let i = 0; i < der.length; i += 32768) {
6458
6769
  b64 += String.fromCharCode(...der.subarray(i, i + 32768));
6459
6770
  }
6460
- const lines = btoa(b64).match(/.{1,64}/g) ?? [];
6771
+ const lines2 = btoa(b64).match(/.{1,64}/g) ?? [];
6461
6772
  return `-----BEGIN CERTIFICATE-----
6462
- ${lines.join("\n")}
6773
+ ${lines2.join("\n")}
6463
6774
  -----END CERTIFICATE-----
6464
6775
  `;
6465
6776
  }
@@ -6499,11 +6810,11 @@ var RealmCa = class _RealmCa {
6499
6810
  static async create(owner, now) {
6500
6811
  const pair = await crypto.subtle.generateKey(ECDSA, false, ["sign", "verify"]);
6501
6812
  const spki = new Uint8Array(await crypto.subtle.exportKey("spki", pair.publicKey));
6502
- const id = Array.from(
6813
+ const id2 = Array.from(
6503
6814
  crypto.getRandomValues(new Uint8Array(4)),
6504
6815
  (b) => b.toString(16).padStart(2, "0")
6505
6816
  ).join("");
6506
- const name2 = { organization: "SLICC", commonName: `SLICC realm CA ${owner} ${id}` };
6817
+ const name2 = { organization: "SLICC", commonName: `SLICC realm CA ${owner} ${id2}` };
6507
6818
  const notAfter = now + CA_LIFETIME;
6508
6819
  const cert = await certificate({
6509
6820
  serial: randomSerial(),
@@ -7094,12 +7405,12 @@ function parsePrefix(text) {
7094
7405
  const parts = text.trim().split("/");
7095
7406
  if (parts.length > 2) return void 0;
7096
7407
  const [address = "", size] = parts;
7097
- const parsed = addressBits(address);
7098
- if (!parsed) return void 0;
7099
- const max = parsed.v6 ? 128 : 32;
7408
+ const parsed2 = addressBits(address);
7409
+ if (!parsed2) return void 0;
7410
+ const max = parsed2.v6 ? 128 : 32;
7100
7411
  const length2 = size === void 0 ? max : /^\d{1,3}$/.test(size) ? Number(size) : -1;
7101
7412
  if (length2 < 0 || length2 > max) return void 0;
7102
- return { ...parsed, length: length2 };
7413
+ return { ...parsed2, length: length2 };
7103
7414
  }
7104
7415
  function within4(prefix, addr) {
7105
7416
  if (prefix.v6 !== addr.v6) return false;
@@ -7125,10 +7436,10 @@ var Routes = class {
7125
7436
  this.ipv6 = ipv62;
7126
7437
  }
7127
7438
  set(table) {
7128
- const parsed = (table.prefixes ?? []).map(parsePrefix);
7129
- const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);
7439
+ const parsed2 = (table.prefixes ?? []).map(parsePrefix);
7440
+ const bad = (table.prefixes ?? []).find((_, i) => !parsed2[i]);
7130
7441
  if (bad !== void 0) throw new Error(`not an address prefix: ${bad}`);
7131
- this.table = parsed;
7442
+ this.table = parsed2;
7132
7443
  this.exit = table.exit === true;
7133
7444
  }
7134
7445
  classify(host) {
@@ -7204,21 +7515,21 @@ var Resolver = class {
7204
7515
  if (!this.uplink || key === "") return [];
7205
7516
  const asked = this.uplink.traits.ipv6 ? family : 4;
7206
7517
  if (family === 6 && asked !== 6) return [];
7207
- const id = `${asked}:${key}`;
7208
- const hit = this.cache.get(id);
7518
+ const id2 = `${asked}:${key}`;
7519
+ const hit = this.cache.get(id2);
7209
7520
  if (hit && hit.expires > this.now()) return hit.addresses;
7210
7521
  const signal = blocking?.();
7211
7522
  if (signal?.aborted) throw new KernelError("EINTR");
7212
- let lookup = this.pending.get(id);
7523
+ let lookup = this.pending.get(id2);
7213
7524
  if (!lookup) {
7214
7525
  const abort = new AbortController();
7215
- lookup = { work: this.lookup(key, asked, id, abort.signal), abort, waiters: 0 };
7216
- this.pending.set(id, lookup);
7217
- void lookup.work.then(() => this.pending.delete(id));
7526
+ lookup = { work: this.lookup(key, asked, id2, abort.signal), abort, waiters: 0 };
7527
+ this.pending.set(id2, lookup);
7528
+ void lookup.work.then(() => this.pending.delete(id2));
7218
7529
  }
7219
7530
  return waitFor(lookup, signal);
7220
7531
  }
7221
- async lookup(name2, family, id, abandoned) {
7532
+ async lookup(name2, family, id2, abandoned) {
7222
7533
  const uplink = this.uplink;
7223
7534
  const timeout = AbortSignal.any([AbortSignal.timeout(this.timeoutMs), abandoned]);
7224
7535
  let answer;
@@ -7235,7 +7546,7 @@ var Resolver = class {
7235
7546
  return [];
7236
7547
  }
7237
7548
  const addresses = answer.addresses.filter((a) => this.usable(name2, a, family));
7238
- if (addresses.length > 0) this.remember(id, addresses, answer.ttl);
7549
+ if (addresses.length > 0) this.remember(id2, addresses, answer.ttl);
7239
7550
  return addresses;
7240
7551
  }
7241
7552
  usable(name2, address, family) {
@@ -7247,11 +7558,11 @@ var Resolver = class {
7247
7558
  this.log(`slicc-kernel: dropped the uplink's answer ${address} for ${name2}: it is ${kind}`);
7248
7559
  return false;
7249
7560
  }
7250
- remember(id, addresses, ttl) {
7561
+ remember(id2, addresses, ttl) {
7251
7562
  const seconds = Math.min(ttl ?? RESOLVE_TTL_MAX, RESOLVE_TTL_MAX);
7252
7563
  if (seconds <= 0) return;
7253
- this.cache.delete(id);
7254
- this.cache.set(id, { addresses, expires: this.now() + seconds * 1e3 });
7564
+ this.cache.delete(id2);
7565
+ this.cache.set(id2, { addresses, expires: this.now() + seconds * 1e3 });
7255
7566
  if (this.cache.size > RESOLVE_CACHE_MAX) {
7256
7567
  this.cache.delete(this.cache.keys().next().value);
7257
7568
  }
@@ -8693,6 +9004,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
8693
9004
  "fd-list",
8694
9005
  "proc-alarm",
8695
9006
  "proc-umask",
9007
+ "proc-cred",
9008
+ "proc-setcred",
8696
9009
  "dl-log",
8697
9010
  "fd-renumber",
8698
9011
  "fd-promote",
@@ -8743,6 +9056,7 @@ var WasmProcess = class {
8743
9056
  interrupt = new AbortController();
8744
9057
  dying = false;
8745
9058
  umask = DEFAULT_UMASK;
9059
+ cred;
8746
9060
  execChild;
8747
9061
  killed = false;
8748
9062
  execTermsig;
@@ -8762,6 +9076,7 @@ var WasmProcess = class {
8762
9076
  this.options = options;
8763
9077
  this.inherited = options.ignored ?? 0;
8764
9078
  this.umask = options.umask ?? DEFAULT_UMASK;
9079
+ this.cred = copyCred(options.cred ?? ROOT);
8765
9080
  this.children = new ChildTable(fds, options.spawner, options.forker);
8766
9081
  this.nodes = options.nodes ?? new VfsNodes(options.fs);
8767
9082
  if (!options.nodes) options.openFiles?.add(this.nodes);
@@ -9278,6 +9593,11 @@ var WasmProcess = class {
9278
9593
  return { ok: true, kind: "void" };
9279
9594
  case "proc-umask":
9280
9595
  return { ok: true, kind: "json", json: this.setUmask(req.mask) };
9596
+ case "proc-cred":
9597
+ return { ok: true, kind: "json", json: copyCred(this.cred) };
9598
+ case "proc-setcred":
9599
+ this.cred = changeCred(this.cred, req.change);
9600
+ return { ok: true, kind: "json", json: copyCred(this.cred) };
9281
9601
  case "dl-log":
9282
9602
  if (req.append) this.dlLog.push(req.append);
9283
9603
  return { ok: true, kind: "json", json: this.dlLog.slice(req.from) };
@@ -9372,9 +9692,9 @@ var KILL_GRACE_MS = 2e3;
9372
9692
  var descIds = /* @__PURE__ */ new WeakMap();
9373
9693
  var nextDescId = 1;
9374
9694
  function descId(file) {
9375
- const id = descIds.get(file) ?? nextDescId++;
9376
- descIds.set(file, id);
9377
- return id;
9695
+ const id2 = descIds.get(file) ?? nextDescId++;
9696
+ descIds.set(file, id2);
9697
+ return id2;
9378
9698
  }
9379
9699
  function inheritedFds(fds) {
9380
9700
  return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
@@ -9491,7 +9811,8 @@ function processInit(opts, sab, umask, lane) {
9491
9811
  function inherited(opts) {
9492
9812
  return {
9493
9813
  ...opts.ignored ? { ignored: opts.ignored } : {},
9494
- ...opts.umask !== void 0 ? { umask: opts.umask } : {}
9814
+ ...opts.umask !== void 0 ? { umask: opts.umask } : {},
9815
+ ...opts.cred ? { cred: opts.cred } : {}
9495
9816
  };
9496
9817
  }
9497
9818
  function holdingToken(opts, holds) {
@@ -9655,6 +9976,7 @@ function spawnWasmProcess(opts) {
9655
9976
  ignoredSignals: (fork) => process2.inheritable(fork),
9656
9977
  signalMasks: () => process2.signalMasks(),
9657
9978
  umask: () => process2.umask,
9979
+ cred: () => copyCred(process2.cred),
9658
9980
  writesBack: () => opts.program.abi === "wasi" && !done4
9659
9981
  };
9660
9982
  }
@@ -9713,6 +10035,9 @@ var JobTable = class {
9713
10035
  pgidOf(pid) {
9714
10036
  return this.members.get(pid)?.pgid;
9715
10037
  }
10038
+ sidOf(pid) {
10039
+ return this.members.get(pid)?.sid;
10040
+ }
9716
10041
  member(pid) {
9717
10042
  const member = this.members.get(pid);
9718
10043
  if (!member) throw new KernelError("ESRCH");
@@ -9721,27 +10046,27 @@ var JobTable = class {
9721
10046
  idOf(pid) {
9722
10047
  return this.members.get(pid)?.shownAs ?? pid;
9723
10048
  }
9724
- byId(id) {
9725
- for (const member of this.members.values()) if (member.shownAs === id) return member;
9726
- return this.member(id);
10049
+ byId(id2) {
10050
+ for (const member of this.members.values()) if (member.shownAs === id2) return member;
10051
+ return this.member(id2);
9727
10052
  }
9728
10053
  setpgid(caller, pid, pgid) {
9729
10054
  const me = this.idOf(caller);
9730
10055
  const target = this.byId(pid || me);
9731
- const id = this.idOf(target.pid);
9732
- const group = pgid || id;
10056
+ const id2 = this.idOf(target.pid);
10057
+ const group = pgid || id2;
9733
10058
  if (group < 0) throw new KernelError("EINVAL");
9734
10059
  const self = this.member(caller);
9735
- if (id !== me) {
10060
+ if (id2 !== me) {
9736
10061
  if (target.ppid === void 0 || this.idOf(target.ppid) !== me) {
9737
10062
  throw new KernelError("ESRCH");
9738
10063
  }
9739
10064
  if (target.execed) throw new KernelError("EACCES");
9740
10065
  }
9741
10066
  if (target.sid !== self.sid) throw new KernelError("EPERM");
9742
- if (id === target.sid) throw new KernelError("EPERM");
10067
+ if (id2 === target.sid) throw new KernelError("EPERM");
9743
10068
  const exists = [...this.members.values()].some((m) => m.pgid === group && m.sid === target.sid);
9744
- if (group !== id && !exists) throw new KernelError("EPERM");
10069
+ if (group !== id2 && !exists) throw new KernelError("EPERM");
9745
10070
  target.pgid = group;
9746
10071
  }
9747
10072
  getpgid(caller, pid) {
@@ -9752,12 +10077,12 @@ var JobTable = class {
9752
10077
  }
9753
10078
  setsid(pid) {
9754
10079
  const member = this.member(pid);
9755
- const id = this.idOf(pid);
9756
- if (member.pgid === id) throw new KernelError("EPERM");
9757
- member.sid = id;
9758
- member.pgid = id;
9759
- this.terminals.delete(id);
9760
- return id;
10080
+ const id2 = this.idOf(pid);
10081
+ if (member.pgid === id2) throw new KernelError("EPERM");
10082
+ member.sid = id2;
10083
+ member.pgid = id2;
10084
+ this.terminals.delete(id2);
10085
+ return id2;
9761
10086
  }
9762
10087
  terminalNamed(name2) {
9763
10088
  return this.knownTerminals().find((tty) => tty.name === name2);
@@ -9774,15 +10099,17 @@ var JobTable = class {
9774
10099
  }
9775
10100
  acquireTerminal(pid, tty) {
9776
10101
  const member = this.members.get(pid);
9777
- const id = this.idOf(pid);
9778
- if (!member || member.sid !== id || this.terminals.has(id)) return false;
10102
+ const id2 = this.idOf(pid);
10103
+ if (!member || member.sid !== id2 || this.terminals.has(id2)) return false;
9779
10104
  if ([...this.terminals.values()].includes(tty)) return false;
9780
- this.terminals.set(id, tty);
10105
+ this.terminals.set(id2, tty);
9781
10106
  this.foreground.set(tty, member.pgid);
9782
10107
  return true;
9783
10108
  }
9784
- killGroup(pgid, sig) {
9785
- const targets = [...this.members.values()].filter((m) => m.pgid === pgid);
10109
+ killGroup(pgid, sig, may = () => true) {
10110
+ const group = [...this.members.values()].filter((m) => m.pgid === pgid);
10111
+ const targets = group.filter((m) => may(m.pid));
10112
+ if (targets.length === 0 && group.length > 0) throw new KernelError("EPERM");
9786
10113
  const pids = new Set(targets.map((m) => m.pid));
9787
10114
  if (sig !== 0) {
9788
10115
  for (const m of targets)
@@ -10244,7 +10571,7 @@ function unescape(field2) {
10244
10571
  );
10245
10572
  }
10246
10573
  function parseFstab(text) {
10247
- const lines = [];
10574
+ const lines2 = [];
10248
10575
  text.split("\n").forEach((raw, index) => {
10249
10576
  const content = raw.replace(/#.*$/, "").trim();
10250
10577
  if (!content) return;
@@ -10253,12 +10580,12 @@ function parseFstab(text) {
10253
10580
  if (options.split(",").includes("noauto")) return;
10254
10581
  const spec = { source, target, type, options: {} };
10255
10582
  try {
10256
- lines.push({ line: index + 1, spec: { ...spec, options: parseMountOptions(options) } });
10583
+ lines2.push({ line: index + 1, spec: { ...spec, options: parseMountOptions(options) } });
10257
10584
  } catch (err) {
10258
- lines.push({ line: index + 1, spec, invalid: err });
10585
+ lines2.push({ line: index + 1, spec, invalid: err });
10259
10586
  }
10260
10587
  });
10261
- return lines;
10588
+ return lines2;
10262
10589
  }
10263
10590
  function codeOf3(err) {
10264
10591
  const code = err?.code;
@@ -10276,11 +10603,11 @@ function wait(ms, signal) {
10276
10603
  signal.addEventListener("abort", done4, { once: true });
10277
10604
  });
10278
10605
  }
10279
- async function mountFstab(lines, mount, signal, retries = FSTAB_RETRIES, discard = () => {
10606
+ async function mountFstab(lines2, mount, signal, retries = FSTAB_RETRIES, discard = () => {
10280
10607
  }, settled = () => {
10281
10608
  }) {
10282
10609
  return Promise.all(
10283
- lines.map(async (each) => {
10610
+ lines2.map(async (each) => {
10284
10611
  const result = await mountLine(each, mount, signal, retries, discard);
10285
10612
  settled(each, result);
10286
10613
  return result;
@@ -10691,8 +11018,8 @@ async function openHostfs(spec, hook, fetch2, timing = {}) {
10691
11018
  function memoryMedia() {
10692
11019
  const handles = /* @__PURE__ */ new Map();
10693
11020
  return {
10694
- get: async (id) => handles.get(id),
10695
- put: async (id, handle) => void handles.set(id, handle)
11021
+ get: async (id2) => handles.get(id2),
11022
+ put: async (id2, handle) => void handles.set(id2, handle)
10696
11023
  };
10697
11024
  }
10698
11025
 
@@ -10936,10 +11263,10 @@ function importedMemory(bytes) {
10936
11263
  return { initial, maximum, shared: (flags & 2) !== 0 };
10937
11264
  };
10938
11265
  while (at < bytes.length) {
10939
- const id = bytes[at++];
11266
+ const id2 = bytes[at++];
10940
11267
  const size = u32();
10941
11268
  const end = at + size;
10942
- if (id !== 2) {
11269
+ if (id2 !== 2) {
10943
11270
  at = end;
10944
11271
  continue;
10945
11272
  }
@@ -11059,18 +11386,17 @@ function foreignImports(bytes) {
11059
11386
  const r = new Reader(bytes);
11060
11387
  let types = [];
11061
11388
  while (r.at < bytes.length) {
11062
- const id = r.byte();
11389
+ const id2 = r.byte();
11063
11390
  const end = r.u32() + r.at;
11064
- if (id === 1) types = readTypes(r);
11391
+ if (id2 === 1) types = readTypes(r);
11065
11392
  if (!types) return void 0;
11066
- if (id === 2) return readImports(r, types);
11393
+ if (id2 === 2) return readImports(r, types);
11067
11394
  r.at = end;
11068
11395
  }
11069
11396
  return {};
11070
11397
  }
11071
11398
 
11072
11399
  // src/launcher.ts
11073
- var PNPM_HOME = "/home/.local/share/pnpm";
11074
11400
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
11075
11401
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
11076
11402
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -11170,6 +11496,8 @@ var Launcher = class {
11170
11496
  zombies = /* @__PURE__ */ new Set();
11171
11497
  orphans = /* @__PURE__ */ new Set();
11172
11498
  described = /* @__PURE__ */ new Map();
11499
+ creds = /* @__PURE__ */ new Map();
11500
+ users;
11173
11501
  jobs = new JobTable();
11174
11502
  locks = new LockTable();
11175
11503
  settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
@@ -11221,7 +11549,7 @@ var Launcher = class {
11221
11549
  this.hostfs = options;
11222
11550
  this.fstabRetries = options.fstabRetries ?? FSTAB_RETRIES;
11223
11551
  this.processMounts = options.processMounts ?? true;
11224
- this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
11552
+ this.pnpmHome = options.env?.PNPM_HOME ?? SYSTEM_PNPM_HOME;
11225
11553
  this.modulesDir = options.modules ?? "/node_modules";
11226
11554
  this.cdp = new CdpHosts(options.cdp);
11227
11555
  const hostname = options.hostname ?? DEFAULT_HOSTNAME;
@@ -11257,6 +11585,7 @@ var Launcher = class {
11257
11585
  }
11258
11586
  );
11259
11587
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
11588
+ this.users = new UserDb(this.fs);
11260
11589
  void this.unlinked.sweep("/");
11261
11590
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
11262
11591
  this.catalog = void 0;
@@ -11457,6 +11786,7 @@ var Launcher = class {
11457
11786
  },
11458
11787
  ...req.ignored ? { ignored: req.ignored } : {},
11459
11788
  ...req.umask !== void 0 ? { umask: req.umask } : {},
11789
+ cred: req.cred,
11460
11790
  program: req.program,
11461
11791
  argv0: req.argv0,
11462
11792
  args: req.args,
@@ -11468,9 +11798,9 @@ var Launcher = class {
11468
11798
  onError: req.report,
11469
11799
  spawner: this.spawner(pid, req.report),
11470
11800
  forker: this.forker(pid, req),
11471
- kill: (target, sig) => this.kill(target, sig),
11801
+ kill: (target, sig) => this.kill(target, sig, this.credOf(pid), pid),
11472
11802
  writesBack: (target) => this.processes.get(target)?.writesBack() === true,
11473
- processes: () => ({ boot: this.boot, processes: this.list() }),
11803
+ processes: () => ({ boot: this.boot, processes: this.list(this.credOf(pid)) }),
11474
11804
  openFiles: this.openFiles,
11475
11805
  nodes: this.nodes,
11476
11806
  held: this.held,
@@ -11490,6 +11820,8 @@ var Launcher = class {
11490
11820
  ...this.shownIds(req)
11491
11821
  });
11492
11822
  this.processes.set(pid, handle);
11823
+ const { cred } = req;
11824
+ this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
11493
11825
  if (req.ppid !== void 0) this.settling.started(pid, req.ppid);
11494
11826
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
11495
11827
  if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
@@ -11516,12 +11848,13 @@ var Launcher = class {
11516
11848
  forget(pid) {
11517
11849
  this.jobs.remove(pid);
11518
11850
  this.described.delete(pid);
11851
+ this.creds.delete(pid);
11519
11852
  }
11520
11853
  reaped(pid) {
11521
11854
  if (this.zombies.delete(pid)) this.forget(pid);
11522
11855
  else this.orphans.add(pid);
11523
11856
  }
11524
- list() {
11857
+ list(viewer = ROOT) {
11525
11858
  const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
11526
11859
  const rootOf = (member) => {
11527
11860
  let root = member;
@@ -11539,6 +11872,8 @@ var Launcher = class {
11539
11872
  for (const member of members.values()) {
11540
11873
  const described = this.described.get(member.pid);
11541
11874
  if (member.execed || !described) continue;
11875
+ const cred = this.credOf(member.pid);
11876
+ if (!maySee(viewer, cred)) continue;
11542
11877
  const root = rootOf(member);
11543
11878
  listed.push({
11544
11879
  pid: root.pid,
@@ -11551,6 +11886,7 @@ var Launcher = class {
11551
11886
  state: this.processes.has(member.pid) ? "S" : "Z",
11552
11887
  memory: this.processes.get(member.pid)?.memory() ?? 0,
11553
11888
  umask: this.umaskOf(member.pid),
11889
+ cred,
11554
11890
  ...this.signalMasksOf(member.pid)
11555
11891
  });
11556
11892
  }
@@ -11574,6 +11910,7 @@ var Launcher = class {
11574
11910
  ppid,
11575
11911
  ignored: this.ignoredBy(ppid),
11576
11912
  umask: this.umaskOf(ppid),
11913
+ cred: this.credOf(ppid),
11577
11914
  ...exec
11578
11915
  });
11579
11916
  deciding?.attach();
@@ -11619,7 +11956,8 @@ var Launcher = class {
11619
11956
  ppid,
11620
11957
  fork: state,
11621
11958
  ignored: this.ignoredBy(ppid, true),
11622
- umask: this.umaskOf(ppid)
11959
+ umask: this.umaskOf(ppid),
11960
+ cred: this.credOf(ppid)
11623
11961
  })
11624
11962
  );
11625
11963
  }
@@ -11631,6 +11969,18 @@ var Launcher = class {
11631
11969
  umaskOf(pid) {
11632
11970
  return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
11633
11971
  }
11972
+ imageOf(pid) {
11973
+ const members = this.jobs.list();
11974
+ let image = pid;
11975
+ for (; ; ) {
11976
+ const next2 = members.find((m) => m.execParent === image);
11977
+ if (!next2) return image;
11978
+ image = next2.pid;
11979
+ }
11980
+ }
11981
+ credOf(pid) {
11982
+ return this.creds.get(pid)?.() ?? copyCred(ROOT);
11983
+ }
11634
11984
  released(path) {
11635
11985
  const at = normalizePath(path);
11636
11986
  this.unlinked.closed(at);
@@ -11644,10 +11994,13 @@ var Launcher = class {
11644
11994
  ignoredBy(pid, fork = false) {
11645
11995
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
11646
11996
  }
11647
- kill(pid, sig) {
11648
- if (pid < 0) return this.jobs.killGroup(-pid, sig);
11997
+ kill(pid, sig, sender, from) {
11998
+ const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
11999
+ const may = (target) => !sender || maySignal(sender, this.credOf(target)) || sig === SIG.CONT && session !== void 0 && this.jobs.sidOf(target) === session || from !== void 0 && this.imageOf(from) === this.imageOf(target);
12000
+ if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
11649
12001
  const handle = this.processes.get(pid);
11650
12002
  if (!handle) return false;
12003
+ if (!may(this.imageOf(pid))) throw fsError("EPERM", `pid ${pid}`);
11651
12004
  if (sig !== 0) handle.signal(sig);
11652
12005
  return true;
11653
12006
  }
@@ -11718,9 +12071,9 @@ var Launcher = class {
11718
12071
  ask(handle);
11719
12072
  });
11720
12073
  const served = serveFilesystem(port2, medium.handlers, FSA_CAPABILITIES);
11721
- const id = source.slice("fsa:".length);
11722
- this.removable.set(target, { id, medium, served });
11723
- const stored = this.inserted.get(id) ?? await this.media.get(id);
12074
+ const id2 = source.slice("fsa:".length);
12075
+ this.removable.set(target, { id: id2, medium, served });
12076
+ const stored = this.inserted.get(id2) ?? await this.media.get(id2);
11724
12077
  if (stored && await granted(stored)) medium.insert(stored);
11725
12078
  else ask(stored);
11726
12079
  return {
@@ -11804,15 +12157,16 @@ var Launcher = class {
11804
12157
  }
11805
12158
  async prepare() {
11806
12159
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
12160
+ await this.users.ensure();
11807
12161
  await writeCaFile(this.base, this.ca).catch(() => void 0);
11808
12162
  this.fstab = this.mountFstab();
11809
12163
  }
11810
12164
  async mountFstab() {
11811
12165
  const text = await this.base.readFile(FSTAB_PATH).catch(() => "");
11812
- const lines = parseFstab(text);
11813
- for (const { spec } of lines) this.mounts.note(spec, "pending");
12166
+ const lines2 = parseFstab(text);
12167
+ for (const { spec } of lines2) this.mounts.note(spec, "pending");
11814
12168
  return mountFstab(
11815
- lines,
12169
+ lines2,
11816
12170
  (spec) => this.mount(spec).catch((err) => {
11817
12171
  this.mounts.note(spec, "pending", err?.message ?? String(err));
11818
12172
  throw err;
@@ -11827,14 +12181,19 @@ var Launcher = class {
11827
12181
  }
11828
12182
  );
11829
12183
  }
11830
- environment(cwd, extra) {
12184
+ environment(cwd, extra, account) {
12185
+ return { ...this.env, ...userEnv(account, this.pnpmHome), PWD: cwd, ...extra };
12186
+ }
12187
+ async account(user) {
12188
+ if (typeof user === "object") return user;
12189
+ const found = await this.users.lookup(user ?? 0);
12190
+ if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
12191
+ return found;
12192
+ }
12193
+ identity(account) {
11831
12194
  return {
11832
- PATH: `/usr/bin:/bin:${this.pnpmHome}/bin`,
11833
- HOME: "/home",
11834
- PNPM_HOME: this.pnpmHome,
11835
- ...this.env,
11836
- PWD: cwd,
11837
- ...extra
12195
+ cred: credOf(account),
12196
+ ...account.umask !== void 0 ? { umask: account.umask } : {}
11838
12197
  };
11839
12198
  }
11840
12199
  async starting(argv, dir) {
@@ -11847,6 +12206,7 @@ var Launcher = class {
11847
12206
  return { cwd, file, planned: await this.plan(file, argv, cwd) };
11848
12207
  }
11849
12208
  async openTerminal(argv, options) {
12209
+ const account = await this.account(options.user);
11850
12210
  const { cwd, file, planned } = await this.starting(argv, options.cwd);
11851
12211
  if (!planned) throw new Error(`${file}: command not found`);
11852
12212
  let leader = 0;
@@ -11862,14 +12222,18 @@ var Launcher = class {
11862
12222
  fds.installAt(0, stdio);
11863
12223
  fds.installAt(1, stdio.retain());
11864
12224
  fds.installAt(2, stdio.retain());
11865
- const env = this.environment(cwd, {
11866
- TERM: "xterm-256color",
11867
- COLORTERM: "truecolor",
11868
- ...options.env
11869
- });
12225
+ const env = this.environment(
12226
+ cwd,
12227
+ {
12228
+ TERM: "xterm-256color",
12229
+ COLORTERM: "truecolor",
12230
+ ...options.env
12231
+ },
12232
+ account
12233
+ );
11870
12234
  const report = (message2) => options.onData(encoder3.encode(`${message2}\r
11871
12235
  `));
11872
- const handle = await this.launch(planned, { env, cwd, fds, report });
12236
+ const handle = await this.launch(planned, { env, cwd, fds, report, ...this.identity(account) });
11873
12237
  leader = handle.pid;
11874
12238
  void handle.exited.then(() => tty.hangup());
11875
12239
  return {
@@ -11885,8 +12249,9 @@ var Launcher = class {
11885
12249
  };
11886
12250
  }
11887
12251
  async run(argv, options = {}) {
12252
+ const account = await this.account(options.user);
11888
12253
  const { cwd, file, planned } = await this.starting(argv, options.cwd);
11889
- const env = this.environment(cwd, options.env);
12254
+ const env = this.environment(cwd, options.env, account);
11890
12255
  const out = [];
11891
12256
  const err = [];
11892
12257
  const keep = options.collect !== false;
@@ -11907,7 +12272,14 @@ var Launcher = class {
11907
12272
  fds.installAt(1, sinkFile(stdout));
11908
12273
  fds.installAt(2, sinkFile(stderr));
11909
12274
  const group = options.pgid !== void 0 ? { pgid: options.pgid } : {};
11910
- const handle = await this.launch(planned, { env, cwd, fds, report, ...group });
12275
+ const handle = await this.launch(planned, {
12276
+ env,
12277
+ cwd,
12278
+ fds,
12279
+ report,
12280
+ ...group,
12281
+ ...this.identity(account)
12282
+ });
11911
12283
  options.onStarted?.(handle.pid);
11912
12284
  const status = await handle.exited;
11913
12285
  return { status, stdout: concat5(out), stderr: concat5(err) };
@@ -12415,11 +12787,11 @@ function indexOf(haystack, needle) {
12415
12787
  return -1;
12416
12788
  }
12417
12789
  function parseHead(bytes) {
12418
- const [statusLine = "", ...lines] = latin12.decode(bytes).split("\r\n");
12790
+ const [statusLine = "", ...lines2] = latin12.decode(bytes).split("\r\n");
12419
12791
  const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
12420
12792
  if (!match) throw errorWithCode(`EPROTO: not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
12421
12793
  const headers = [];
12422
- for (const line of lines) {
12794
+ for (const line of lines2) {
12423
12795
  const colon = line.indexOf(":");
12424
12796
  if (colon > 0) headers.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()]);
12425
12797
  }
@@ -12488,13 +12860,13 @@ function stream(chunks2, socket, signal) {
12488
12860
  async function requestBytes(request) {
12489
12861
  const url = new URL(request.url);
12490
12862
  const body = request.body ? new Uint8Array(await request.arrayBuffer()) : void 0;
12491
- const lines = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
12863
+ const lines2 = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
12492
12864
  request.headers.forEach((value, name2) => {
12493
- if (!OWN_HEADERS.has(name2)) lines.push(`${name2}: ${value}`);
12865
+ if (!OWN_HEADERS.has(name2)) lines2.push(`${name2}: ${value}`);
12494
12866
  });
12495
- if (body) lines.push(`Content-Length: ${body.length}`);
12496
- lines.push("Connection: close", "", "");
12497
- const head = encoder4.encode(lines.join("\r\n"));
12867
+ if (body) lines2.push(`Content-Length: ${body.length}`);
12868
+ lines2.push("Connection: close", "", "");
12869
+ const head = encoder4.encode(lines2.join("\r\n"));
12498
12870
  const out = new Uint8Array(head.length + (body?.length ?? 0));
12499
12871
  out.set(head);
12500
12872
  if (body) out.set(body, head.length);
@@ -12616,14 +12988,14 @@ var Channel = class {
12616
12988
  this.ended.resolve(error);
12617
12989
  }
12618
12990
  request(call, handlers = {}) {
12619
- const id = ++this.nextId;
12991
+ const id2 = ++this.nextId;
12620
12992
  const { failure } = this;
12621
- if (failure) return { id, done: Promise.reject(failure) };
12993
+ if (failure) return { id: id2, done: Promise.reject(failure) };
12622
12994
  const done4 = new Promise((resolve, reject) => {
12623
- this.pending.set(id, { resolve, reject, ...handlers });
12624
- this.port.postMessage({ ...call, id });
12995
+ this.pending.set(id2, { resolve, reject, ...handlers });
12996
+ this.port.postMessage({ ...call, id: id2 });
12625
12997
  });
12626
- return { id, done: done4 };
12998
+ return { id: id2, done: done4 };
12627
12999
  }
12628
13000
  call(call) {
12629
13001
  return this.request(call).done;
@@ -12682,18 +13054,18 @@ async function serveCdpOn(channel, hook, options) {
12682
13054
  op: "serve-cdp",
12683
13055
  ...options.runtime ? { runtime: options.runtime } : {}
12684
13056
  };
12685
- const { id, done: done4 } = channel.request(req);
12686
- channel.cdpHooks.set(id, hook);
13057
+ const { id: id2, done: done4 } = channel.request(req);
13058
+ channel.cdpHooks.set(id2, hook);
12687
13059
  try {
12688
13060
  await done4;
12689
13061
  } catch (error) {
12690
- channel.cdpHooks.delete(id);
13062
+ channel.cdpHooks.delete(id2);
12691
13063
  throw error;
12692
13064
  }
12693
13065
  return {
12694
13066
  close: () => {
12695
- if (!channel.cdpHooks.delete(id)) return;
12696
- void channel.call({ op: "unserve-cdp", registration: id }).catch(() => void 0);
13067
+ if (!channel.cdpHooks.delete(id2)) return;
13068
+ void channel.call({ op: "unserve-cdp", registration: id2 }).catch(() => void 0);
12697
13069
  }
12698
13070
  };
12699
13071
  }
@@ -12706,6 +13078,8 @@ async function spawnOn(channel, argv, opts) {
12706
13078
  if (opts.pgid !== void 0)
12707
13079
  await since(channel, 3, "spawn into a process group", async () => {
12708
13080
  });
13081
+ if (opts.user !== void 0) await since(channel, 8, "spawn as a user", async () => {
13082
+ });
12709
13083
  const started = Promise.withResolvers();
12710
13084
  const options = { ...rest2, ...stdin !== void 0 ? { stdin: bytesOf2(stdin) } : {} };
12711
13085
  const { done: done4 } = channel.request(
@@ -12744,10 +13118,12 @@ async function runOn(channel, argv, opts) {
12744
13118
  }
12745
13119
  async function terminalOn(channel, argv, opts) {
12746
13120
  const { onData, ...options } = opts;
13121
+ if (opts.user !== void 0) await since(channel, 8, "a terminal as a user", async () => {
13122
+ });
12747
13123
  let listener = onData ?? null;
12748
13124
  const backlog = [];
12749
13125
  const started = Promise.withResolvers();
12750
- const { id, done: done4 } = channel.request(
13126
+ const { id: id2, done: done4 } = channel.request(
12751
13127
  { op: "open-terminal", argv, options },
12752
13128
  {
12753
13129
  output: (_fd, bytes) => listener ? listener(bytes) : backlog.push(bytes),
@@ -12756,7 +13132,7 @@ async function terminalOn(channel, argv, opts) {
12756
13132
  );
12757
13133
  done4.catch(started.reject);
12758
13134
  const pid = await started.promise;
12759
- const send = (action) => void channel.call({ op: "terminal", terminal: id, ...action }).catch(() => void 0);
13135
+ const send = (action) => void channel.call({ op: "terminal", terminal: id2, ...action }).catch(() => void 0);
12760
13136
  return {
12761
13137
  pid,
12762
13138
  exited: done4,
@@ -12797,22 +13173,22 @@ function fsOn(channel) {
12797
13173
  "ENOSYS"
12798
13174
  );
12799
13175
  }
12800
- const { id, done: done5 } = channel.request({
13176
+ const { id: id2, done: done5 } = channel.request({
12801
13177
  op: "watch",
12802
13178
  paths,
12803
13179
  recursive: options.recursive === true
12804
13180
  });
12805
- channel.watching.set(id, onChange);
13181
+ channel.watching.set(id2, onChange);
12806
13182
  try {
12807
13183
  await done5;
12808
13184
  } catch (error) {
12809
- channel.watching.delete(id);
13185
+ channel.watching.delete(id2);
12810
13186
  throw error;
12811
13187
  }
12812
13188
  return {
12813
13189
  close: () => {
12814
- if (!channel.watching.delete(id)) return;
12815
- void channel.call({ op: "unwatch", watch: id }).catch(() => void 0);
13190
+ if (!channel.watching.delete(id2)) return;
13191
+ void channel.call({ op: "unwatch", watch: id2 }).catch(() => void 0);
12816
13192
  }
12817
13193
  };
12818
13194
  }
@@ -13047,12 +13423,21 @@ async function createNodeKernel(options = {}) {
13047
13423
  umount: (target) => guard(async () => launcher.umount(target)),
13048
13424
  mounts: async () => launcher.mounts.list(),
13049
13425
  setRoutes: (routes) => guard(async () => launcher.setRoutes(routes)),
13050
- async connect() {
13426
+ users: {
13427
+ add: (user) => guard(() => launcher.users.add(user)),
13428
+ remove: (name2, options2) => guard(() => launcher.users.remove(name2, options2)),
13429
+ list: () => guard(() => launcher.users.list())
13430
+ },
13431
+ async connect({ user } = {}) {
13051
13432
  if (terminated) throw new Error(TERMINATED);
13433
+ if (user !== void 0 && !await launcher.users.lookup(user)) {
13434
+ throw fsError("ENOENT", `no user ${String(user)}`);
13435
+ }
13052
13436
  const { port1, port2 } = new MessageChannel2();
13053
13437
  const served = serveClient(port1, {
13054
13438
  launcher: async () => launcher,
13055
- signal: signalNumber
13439
+ signal: signalNumber,
13440
+ ...user !== void 0 ? { user } : {}
13056
13441
  });
13057
13442
  clients.add(served);
13058
13443
  void served.closed.then(() => clients.delete(served));