@ai-ecoverse/slicc-kernel 1.67.1 → 1.69.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.
@@ -1991,6 +1991,16 @@ function written(device, bytes) {
1991
1991
  if (device === "full" && bytes.length > 0) throw new KernelError("ENOSPC");
1992
1992
  return bytes.length;
1993
1993
  }
1994
+ var DEVICE_PATHS = /* @__PURE__ */ new Map([
1995
+ ["/dev/null", "null"],
1996
+ ["/dev/zero", "zero"],
1997
+ ["/dev/full", "full"],
1998
+ ["/dev/urandom", "urandom"],
1999
+ ["/dev/random", "urandom"]
2000
+ ]);
2001
+ function deviceAt(path) {
2002
+ return DEVICE_PATHS.get(path);
2003
+ }
1994
2004
  function deviceFile(device, access2) {
1995
2005
  const read = async (max) => {
1996
2006
  if (device === "null") return new Uint8Array(0);
@@ -2019,6 +2029,7 @@ function kernelFdKind(file) {
2019
2029
  if (file.held) return "held";
2020
2030
  if (file.heldMeta && "device" in file.heldMeta) return "device";
2021
2031
  if (file.tty) return "tty";
2032
+ if (file.sealed) return "sealed";
2022
2033
  return file.seek ? "file" : "stream";
2023
2034
  }
2024
2035
  var FdTable = class _FdTable {
@@ -11407,6 +11418,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11407
11418
  "fd-open-tty",
11408
11419
  "fd-tty-names",
11409
11420
  "fd-tty-owner",
11421
+ "fd-open-sealed",
11410
11422
  "tty-get",
11411
11423
  "tty-set",
11412
11424
  "tty-winsz",
@@ -11462,6 +11474,7 @@ var WasmProcess = class {
11462
11474
  ignored = 0;
11463
11475
  inherited = 0;
11464
11476
  interrupt = new AbortController();
11477
+ gone = new AbortController();
11465
11478
  dying = false;
11466
11479
  umask = DEFAULT_UMASK;
11467
11480
  cred;
@@ -11684,6 +11697,7 @@ var WasmProcess = class {
11684
11697
  }
11685
11698
  async write(fd, body, nonblock = false, cancelled) {
11686
11699
  const file = this.fds.get(fd).file;
11700
+ if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
11687
11701
  if (!file.write) throw new KernelError("EBADF");
11688
11702
  if (!pollFile(file).writable) {
11689
11703
  if (nonblock) throw new KernelError("EAGAIN");
@@ -11697,6 +11711,8 @@ var WasmProcess = class {
11697
11711
  }
11698
11712
  async openVfs(req) {
11699
11713
  if (inProc(req.path)) return this.openProc(req, req.path);
11714
+ const device = req.orphan ? void 0 : deviceAt(req.path);
11715
+ if (device) return this.openDevice(req, device);
11700
11716
  const alias = req.orphan ? void 0 : await procAlias(this.options.fs, req.path, !req.exclusive);
11701
11717
  if (typeof alias === "object")
11702
11718
  throw Object.assign(new Error(alias.refused), { code: alias.refused });
@@ -11734,6 +11750,14 @@ var WasmProcess = class {
11734
11750
  }
11735
11751
  return fd;
11736
11752
  }
11753
+ openDevice(req, device) {
11754
+ if (req.exclusive) throw Object.assign(new Error(req.path), { code: "EEXIST" });
11755
+ const access2 = req.flags & WRITE_ACCESS;
11756
+ return this.fds.install(
11757
+ deviceFile(device, access2 === 0 ? "read" : access2 === 1 ? "write" : void 0),
11758
+ 3
11759
+ );
11760
+ }
11737
11761
  async identified(stat, known) {
11738
11762
  const first = await stat();
11739
11763
  if (first.orphan) return first;
@@ -11765,6 +11789,24 @@ var WasmProcess = class {
11765
11789
  if (name2 !== void 0) ttyOpenable(this.guarded(), tty);
11766
11790
  return this.fds.install(tty.file(), 3);
11767
11791
  }
11792
+ openSealed() {
11793
+ const broker = this.options.sealed;
11794
+ if (!broker) throw new KernelError("ENOENT");
11795
+ return this.fds.install(broker.open(), 3);
11796
+ }
11797
+ async sealedCaller() {
11798
+ const shown = await this.options.identity?.();
11799
+ const broker = this.options.sealed;
11800
+ const tag = broker.tagOf(this.pid);
11801
+ return {
11802
+ pid: shown?.pid ?? this.pid,
11803
+ ppid: shown?.ppid ?? 0,
11804
+ uid: this.cred.euid,
11805
+ gid: this.cred.egid,
11806
+ ...tag !== void 0 ? { tag } : {},
11807
+ users: broker.users
11808
+ };
11809
+ }
11768
11810
  guarded() {
11769
11811
  const { guard: guard2 } = this.options;
11770
11812
  return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
@@ -11885,6 +11927,8 @@ var WasmProcess = class {
11885
11927
  return { ok: true, kind: "json", json: this.openTty(req.name) };
11886
11928
  case "fd-tty-names":
11887
11929
  return { ok: true, kind: "json", json: this.options.jobs?.terminalNames() ?? [] };
11930
+ case "fd-open-sealed":
11931
+ return { ok: true, kind: "json", json: this.openSealed() };
11888
11932
  case "fd-tty-owner":
11889
11933
  return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
11890
11934
  case "fd-select": {
@@ -12320,6 +12364,7 @@ var WasmProcess = class {
12320
12364
  async exit() {
12321
12365
  if (this.exited) return;
12322
12366
  this.exited = true;
12367
+ this.gone.abort();
12323
12368
  this.die();
12324
12369
  this.clearAlarm();
12325
12370
  this.interrupt.abort();
@@ -12507,6 +12552,7 @@ function processOptions(opts, header3, hooks, created3) {
12507
12552
  return {
12508
12553
  ...inherited(opts),
12509
12554
  ...opts.owners ? { owners: true } : {},
12555
+ ...opts.sealed ? { sealed: opts.sealed } : {},
12510
12556
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
12511
12557
  ...opts.identity ? { identity: opts.identity } : {},
12512
12558
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
@@ -12989,6 +13035,178 @@ function replayingHops(inner, heldMs = HELD_MS) {
12989
13035
  };
12990
13036
  }
12991
13037
 
13038
+ // src/kernel/sealed.ts
13039
+ var SEALED_MAX_BYTES = 64 * 1024;
13040
+ var SEALED_TIMEOUT_MS = 1e4;
13041
+ var SEALED_OPS = /* @__PURE__ */ new Set([
13042
+ "seal",
13043
+ "placeholder",
13044
+ "list",
13045
+ "test",
13046
+ "request",
13047
+ "status",
13048
+ "cancel",
13049
+ "materialize"
13050
+ ]);
13051
+ var encoder3 = new TextEncoder();
13052
+ var decoder2 = new TextDecoder("utf-8", { fatal: true });
13053
+ function parseSealed(bytes) {
13054
+ try {
13055
+ if (bytes.length > SEALED_MAX_BYTES) throw new KernelError("EMSGSIZE");
13056
+ let parsed2;
13057
+ try {
13058
+ parsed2 = JSON.parse(decoder2.decode(bytes));
13059
+ } catch {
13060
+ throw new KernelError("EINVAL");
13061
+ }
13062
+ if (typeof parsed2 !== "object" || parsed2 === null || Array.isArray(parsed2))
13063
+ throw new KernelError("EINVAL");
13064
+ const req = parsed2;
13065
+ if (req.v !== 1 || typeof req.op !== "string" || !SEALED_OPS.has(req.op))
13066
+ throw new KernelError("EINVAL");
13067
+ return req;
13068
+ } finally {
13069
+ bytes.fill(0);
13070
+ }
13071
+ }
13072
+ function errorReply(code, message3) {
13073
+ return { v: 1, error: { code, message: message3 } };
13074
+ }
13075
+ function isRecord(value) {
13076
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13077
+ }
13078
+ function serialized(reply) {
13079
+ let plain;
13080
+ try {
13081
+ const text = JSON.stringify(reply);
13082
+ plain = text === void 0 ? void 0 : JSON.parse(text);
13083
+ } catch (err) {
13084
+ return JSON.stringify(errorReply("EIO", lineOf(err)));
13085
+ }
13086
+ if (!isRecord(plain)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
13087
+ const { v: _, ...rest2 } = plain;
13088
+ return JSON.stringify({ v: 1, ...rest2 });
13089
+ }
13090
+ function lineOf(err) {
13091
+ const text = err instanceof Error ? err.message : String(err);
13092
+ return (text.split("\n")[0] ?? "").slice(0, 200);
13093
+ }
13094
+ var SealedChannel = class {
13095
+ reply;
13096
+ offset = 0;
13097
+ inflight;
13098
+ broker;
13099
+ constructor(broker) {
13100
+ this.broker = broker;
13101
+ }
13102
+ send(bytes, caller, gone) {
13103
+ const length2 = bytes.length;
13104
+ const fields = parseSealed(bytes);
13105
+ this.reply = void 0;
13106
+ this.offset = 0;
13107
+ this.inflight?.control.abort("replaced");
13108
+ const control = new AbortController();
13109
+ const withdraw = () => control.abort("withdrawn");
13110
+ if (gone.aborted) withdraw();
13111
+ else gone.addEventListener("abort", withdraw, { once: true });
13112
+ const timer = setTimeout(() => control.abort("timeout"), this.broker.timeoutMs);
13113
+ const req = { ...fields, v: 1, op: fields.op, caller };
13114
+ const flight = { control, done: Promise.resolve() };
13115
+ flight.done = this.answer(req, control.signal).then((reply) => {
13116
+ clearTimeout(timer);
13117
+ gone.removeEventListener("abort", withdraw);
13118
+ if (this.inflight !== flight) return;
13119
+ this.inflight = void 0;
13120
+ if (reply) this.reply = encoder3.encode(serialized(reply));
13121
+ });
13122
+ this.inflight = flight;
13123
+ return length2;
13124
+ }
13125
+ async answer(req, signal) {
13126
+ const hook = this.broker.hook;
13127
+ if (!hook) return errorReply("ENOTCONN", "no vault is connected");
13128
+ const stopped = new Promise((resolve) => {
13129
+ signal.addEventListener("abort", () => resolve(void 0), { once: true });
13130
+ });
13131
+ if (signal.aborted) return void 0;
13132
+ const asked = Promise.resolve().then(() => hook(req, { signal })).then(
13133
+ (reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
13134
+ (err) => errorReply("EIO", lineOf(err))
13135
+ );
13136
+ const reply = await Promise.race([asked, stopped]);
13137
+ if (!signal.aborted) return reply;
13138
+ return signal.reason === "timeout" ? errorReply("ETIMEDOUT", "the vault did not answer within 10 s") : void 0;
13139
+ }
13140
+ async read(max, signal) {
13141
+ for (; ; ) {
13142
+ if (this.reply) {
13143
+ const out = this.reply.slice(this.offset, this.offset + max);
13144
+ this.offset += out.length;
13145
+ if (this.offset >= this.reply.length) {
13146
+ this.reply = void 0;
13147
+ this.offset = 0;
13148
+ }
13149
+ return out;
13150
+ }
13151
+ const flight = this.inflight;
13152
+ if (!flight) return new Uint8Array(0);
13153
+ await this.until(flight, signal);
13154
+ }
13155
+ }
13156
+ async until(flight, signal) {
13157
+ if (!signal) return flight.done;
13158
+ if (signal.aborted) throw new KernelError("EINTR");
13159
+ let stop = () => {
13160
+ };
13161
+ const interrupted2 = new Promise((_, reject) => {
13162
+ stop = () => reject(new KernelError("EINTR"));
13163
+ signal.addEventListener("abort", stop, { once: true });
13164
+ });
13165
+ try {
13166
+ await Promise.race([flight.done, interrupted2]);
13167
+ } finally {
13168
+ signal.removeEventListener("abort", stop);
13169
+ }
13170
+ }
13171
+ poll() {
13172
+ return { readable: this.reply !== void 0 || !this.inflight, writable: true, hangup: false };
13173
+ }
13174
+ async changed(signal) {
13175
+ const flight = this.inflight;
13176
+ if (flight) await this.until(flight, signal);
13177
+ }
13178
+ close() {
13179
+ this.inflight?.control.abort("withdrawn");
13180
+ this.inflight = void 0;
13181
+ this.reply = void 0;
13182
+ }
13183
+ };
13184
+ var SealedBroker = class {
13185
+ hook;
13186
+ users;
13187
+ timeoutMs;
13188
+ tagOf;
13189
+ constructor(hook, users, timeoutMs = SEALED_TIMEOUT_MS, tagOf = () => void 0) {
13190
+ this.hook = hook;
13191
+ this.users = users;
13192
+ this.timeoutMs = timeoutMs;
13193
+ this.tagOf = tagOf;
13194
+ }
13195
+ open() {
13196
+ const channel = new SealedChannel(this);
13197
+ return new OpenFile({
13198
+ read: (max, signal) => channel.read(max, signal),
13199
+ write: async () => {
13200
+ throw new KernelError("EINVAL");
13201
+ },
13202
+ poll: () => channel.poll(),
13203
+ changed: (signal) => channel.changed(signal),
13204
+ close: () => channel.close(),
13205
+ sealed: channel
13206
+ });
13207
+ }
13208
+ };
13209
+
12992
13210
  // src/kernel/settling.ts
12993
13211
  var SETUP_OPS = /* @__PURE__ */ new Set([
12994
13212
  "proc-list",
@@ -13636,13 +13854,13 @@ var HostfsClient = class {
13636
13854
  const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
13637
13855
  onOpen();
13638
13856
  const reader = response.body.getReader();
13639
- const decoder2 = new TextDecoder();
13857
+ const decoder3 = new TextDecoder();
13640
13858
  let buffered = "";
13641
13859
  try {
13642
13860
  for (; ; ) {
13643
13861
  const { done: done4, value } = await reader.read();
13644
13862
  if (done4) return;
13645
- buffered += decoder2.decode(value, { stream: true });
13863
+ buffered += decoder3.decode(value, { stream: true });
13646
13864
  let end = buffered.indexOf("\n");
13647
13865
  while (end >= 0) {
13648
13866
  const line = buffered.slice(0, end).trim();
@@ -15020,7 +15238,7 @@ var LAYOUT = [
15020
15238
  ];
15021
15239
  var LOAD_SAMPLE_MS = 5e3;
15022
15240
  var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
15023
- var encoder3 = new TextEncoder();
15241
+ var encoder4 = new TextEncoder();
15024
15242
  var USERS_MODES = ["off", "report", "enforce"];
15025
15243
  function usersMode(mode = "off") {
15026
15244
  if (USERS_MODES.includes(mode)) return mode;
@@ -15170,12 +15388,19 @@ var Launcher = class {
15170
15388
  fstab = Promise.resolve([]);
15171
15389
  usersMode;
15172
15390
  dac;
15391
+ sealed;
15173
15392
  nextPid = 1e3;
15174
15393
  boot = Date.now();
15175
15394
  terminals = 0;
15176
15395
  constructor(options) {
15177
15396
  this.usersMode = usersMode(options.users);
15178
15397
  this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
15398
+ this.sealed = new SealedBroker(
15399
+ options.sealed,
15400
+ this.usersMode,
15401
+ void 0,
15402
+ (pid) => this.tagOf(pid)
15403
+ );
15179
15404
  if (this.owners) options.fs.trackOwners?.();
15180
15405
  this.mounts = new MountTable({
15181
15406
  open: (type, spec) => this.driver(type, spec),
@@ -15575,6 +15800,7 @@ var Launcher = class {
15575
15800
  ...req.umask !== void 0 ? { umask: req.umask } : {},
15576
15801
  cred: req.cred,
15577
15802
  ...this.owners ? { owners: true } : {},
15803
+ sealed: this.sealed,
15578
15804
  ...this.dac ? { dac: this.dac } : {},
15579
15805
  program: req.program,
15580
15806
  argv0: req.argv0,
@@ -16213,7 +16439,7 @@ var Launcher = class {
16213
16439
  },
16214
16440
  account
16215
16441
  );
16216
- const report = (message3) => options.onData(encoder3.encode(`${message3}\r
16442
+ const report = (message3) => options.onData(encoder4.encode(`${message3}\r
16217
16443
  `));
16218
16444
  const handle = await this.launch(planned, {
16219
16445
  env,
@@ -16250,7 +16476,7 @@ var Launcher = class {
16250
16476
  };
16251
16477
  const stdout = collect(out, options.onStdout);
16252
16478
  const stderr = collect(err, options.onStderr);
16253
- const report = (message3) => stderr(encoder3.encode(`${message3}
16479
+ const report = (message3) => stderr(encoder4.encode(`${message3}
16254
16480
  `));
16255
16481
  if (!planned) {
16256
16482
  report(`${file}: command not found`);
@@ -16275,6 +16501,33 @@ var Launcher = class {
16275
16501
  }
16276
16502
  };
16277
16503
 
16504
+ // src/sealed-relay.ts
16505
+ function sealedAsker(port) {
16506
+ const asks = /* @__PURE__ */ new Map();
16507
+ let next2 = 0;
16508
+ const hook = (req, { signal }) => {
16509
+ const id2 = ++next2;
16510
+ const waiting = Promise.withResolvers();
16511
+ asks.set(id2, waiting);
16512
+ const abort = () => {
16513
+ if (!asks.delete(id2)) return;
16514
+ port.postMessage({ sealedAbort: { id: id2, reason: signal.reason } });
16515
+ waiting.reject(new Error(String(signal.reason)));
16516
+ };
16517
+ signal.addEventListener("abort", abort, { once: true });
16518
+ port.postMessage({ sealedAsk: { id: id2, req } });
16519
+ return waiting.promise.finally(() => signal.removeEventListener("abort", abort));
16520
+ };
16521
+ const answer = ({ id: id2, reply, error }) => {
16522
+ const waiting = asks.get(id2);
16523
+ if (!waiting) return;
16524
+ asks.delete(id2);
16525
+ if (reply) waiting.resolve(reply);
16526
+ else waiting.reject(new Error(error ?? "no reply"));
16527
+ };
16528
+ return { hook, answer };
16529
+ }
16530
+
16278
16531
  // src/serve.ts
16279
16532
  async function inOpfs(deps, root) {
16280
16533
  if (deps.syncAccess !== true) return false;
@@ -16389,11 +16642,31 @@ function usersOp(l, req) {
16389
16642
  if (req.op === "groups-remove-member") return l.users.removeMember(req.group, req.user);
16390
16643
  return l.users.list();
16391
16644
  }
16392
- function hostOptions(req, port) {
16645
+ function hostOptions(req, port, sealed) {
16393
16646
  return {
16394
16647
  ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
16395
16648
  ...req.users !== void 0 ? { users: req.users } : {},
16396
- ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {}
16649
+ ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
16650
+ ...req.sealed ? { sealed } : {}
16651
+ };
16652
+ }
16653
+ function policyAsker(port) {
16654
+ const policies = /* @__PURE__ */ new Map();
16655
+ let nextPolicy = 0;
16656
+ const askPolicy = (req) => new Promise((resolve) => {
16657
+ const id2 = ++nextPolicy;
16658
+ policies.set(id2, resolve);
16659
+ port.postMessage({ mountPolicy: { id: id2, req } });
16660
+ });
16661
+ return {
16662
+ mountPolicy: (policy) => policy === void 0 ? {} : { processMounts: policy === "ask" ? askPolicy : policy },
16663
+ allowed(data) {
16664
+ const answer = data.mountAllowed;
16665
+ if (!answer) return false;
16666
+ policies.get(answer.id)?.(answer.allowed);
16667
+ policies.delete(answer.id);
16668
+ return true;
16669
+ }
16397
16670
  };
16398
16671
  }
16399
16672
  function serveKernel(port, deps) {
@@ -16403,6 +16676,7 @@ function serveKernel(port, deps) {
16403
16676
  const terminals = /* @__PURE__ */ new Map();
16404
16677
  const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
16405
16678
  const clients = /* @__PURE__ */ new Set();
16679
+ const sealed = sealedAsker(port);
16406
16680
  const approvals = sudoApprovals(port);
16407
16681
  const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
16408
16682
  let holding;
@@ -16467,7 +16741,7 @@ function serveKernel(port, deps) {
16467
16741
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
16468
16742
  ...req.modules ? { modules: req.modules } : {},
16469
16743
  ...req.env ? { env: req.env } : {},
16470
- ...hostOptions(req, port),
16744
+ ...hostOptions(req, port, sealed.hook),
16471
16745
  ...req.approve ? { approve: approvals.ask } : {},
16472
16746
  ...net,
16473
16747
  ...media ? { media } : {},
@@ -16530,23 +16804,13 @@ function serveKernel(port, deps) {
16530
16804
  if (grant) waiting.resolve(grant);
16531
16805
  else waiting.reject(Object.assign(new Error(error ?? "no grant"), { code: "EACCES" }));
16532
16806
  }
16533
- const policies = /* @__PURE__ */ new Map();
16534
- let nextPolicy = 0;
16535
- const askPolicy = (req) => new Promise((resolve) => {
16536
- const id2 = ++nextPolicy;
16537
- policies.set(id2, resolve);
16538
- port.postMessage({ mountPolicy: { id: id2, req } });
16539
- });
16540
- const mountPolicy = (policy) => policy === void 0 ? {} : { processMounts: policy === "ask" ? askPolicy : policy };
16807
+ const { mountPolicy, allowed } = policyAsker(port);
16541
16808
  port.addEventListener("message", (event) => {
16809
+ const sealedReply = event.data.sealedReply;
16810
+ if (sealedReply) return sealed.answer(sealedReply);
16542
16811
  const grant = event.data.hostfsGrant;
16543
16812
  if (grant) return granted2(grant);
16544
- const answer = event.data.mountAllowed;
16545
- if (answer) {
16546
- policies.get(answer.id)?.(answer.allowed);
16547
- policies.delete(answer.id);
16548
- return;
16549
- }
16813
+ if (allowed(event.data)) return;
16550
16814
  if (answered(event.data, remote, uplink, approvals)) return;
16551
16815
  const req = event.data;
16552
16816
  handle(req).then(