@ai-ecoverse/slicc-kernel 1.81.0 → 1.83.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.
@@ -131,8 +131,8 @@ function descendants(path) {
131
131
  return IDBKeyRange.bound(`${path}/`, `${path}0`, false, true);
132
132
  }
133
133
  function settle(transaction, value) {
134
- return new Promise((resolve, reject) => {
135
- transaction.oncomplete = () => resolve(value());
134
+ return new Promise((resolve2, reject) => {
135
+ transaction.oncomplete = () => resolve2(value());
136
136
  transaction.onabort = () => reject(transaction.error ?? new Error("metadata transaction aborted"));
137
137
  });
138
138
  }
@@ -249,14 +249,14 @@ var IndexedDbMeta = class {
249
249
  }
250
250
  };
251
251
  function openMeta(name2, factory) {
252
- return new Promise((resolve, reject) => {
252
+ return new Promise((resolve2, reject) => {
253
253
  const request2 = factory.open(name2, META_VERSION);
254
254
  request2.onupgradeneeded = (event) => {
255
255
  if (event.oldVersion < 1) {
256
256
  request2.result.createObjectStore(META_STORE, { keyPath: "path" }).createIndex("dir", "dir");
257
257
  }
258
258
  };
259
- request2.onsuccess = () => resolve(new IndexedDbMeta(request2.result));
259
+ request2.onsuccess = () => resolve2(new IndexedDbMeta(request2.result));
260
260
  request2.onerror = () => reject(request2.error);
261
261
  });
262
262
  }
@@ -360,10 +360,106 @@ async function chownAs(fs, cred, path, change) {
360
360
  });
361
361
  }
362
362
 
363
+ // src/fs/integrity.ts
364
+ var KERNEL_CALLER = { pid: 0, uid: 0 };
365
+ function integrityOptions(options) {
366
+ if (options && typeof options.lookup !== "function") {
367
+ throw new TypeError("protect.integrity.lookup must be a function");
368
+ }
369
+ integrityUnknown(options);
370
+ }
371
+ function integrityUnknown(options) {
372
+ const unknown = options?.unknown ?? "allow";
373
+ if (unknown !== "allow" && unknown !== "deny") {
374
+ throw new TypeError("protect.integrity.unknown must be 'allow' or 'deny'");
375
+ }
376
+ return unknown;
377
+ }
378
+ function hex(digest) {
379
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
380
+ }
381
+ async function sha256(bytes) {
382
+ return hex(await crypto.subtle.digest("SHA-256", bytes));
383
+ }
384
+ var Integrity = class {
385
+ prefixes;
386
+ root;
387
+ lookup;
388
+ unknown;
389
+ report;
390
+ unverified = /* @__PURE__ */ new Set();
391
+ constructor(prefixes2, root, options, report) {
392
+ this.prefixes = prefixes2;
393
+ this.root = root;
394
+ this.lookup = options.lookup;
395
+ this.unknown = integrityUnknown(options);
396
+ this.report = report;
397
+ }
398
+ async real(path) {
399
+ const at = normalizePath(path);
400
+ const real = await this.root.realpath?.(at, true).catch(() => void 0) ?? at;
401
+ return isProtected(this.prefixes, real) ? real : void 0;
402
+ }
403
+ async key(path) {
404
+ const real = await this.real(path);
405
+ if (real === void 0 || !this.root.version) return void 0;
406
+ return `${real}\0${await this.root.version(real)}`;
407
+ }
408
+ async read(path, caller, plain) {
409
+ return (await this.readKeyed(path, caller, plain)).bytes;
410
+ }
411
+ async readKeyed(path, caller, plain) {
412
+ const real = await this.real(path);
413
+ if (real === void 0) return { bytes: await plain() };
414
+ const read = this.root.readVersioned ? await this.root.readVersioned(real) : { bytes: await this.root.readFileBuffer(real), version: void 0 };
415
+ const { bytes } = read;
416
+ const keyed = read.version === void 0 ? { bytes } : { bytes, key: `${real}\0${read.version}` };
417
+ const actual = await sha256(bytes);
418
+ const denial = { pid: caller.pid, uid: caller.uid, path: real, actual };
419
+ const tagged = caller.tag !== void 0 ? { tag: caller.tag } : {};
420
+ let expected;
421
+ try {
422
+ expected = await this.expected(real);
423
+ } catch {
424
+ this.say({ ...denial, ...tagged, op: "exec-integrity", errno: "EACCES" });
425
+ throw fsError("EACCES", `integrity lookup failed: ${real}`);
426
+ }
427
+ if (expected === void 0) {
428
+ if (this.unknown === "allow") {
429
+ if (!this.unverified.has(real)) {
430
+ this.unverified.add(real);
431
+ this.say({ ...denial, ...tagged, op: "exec-unverified", errno: "ENODATA" });
432
+ }
433
+ return keyed;
434
+ }
435
+ } else if (expected === actual) {
436
+ return keyed;
437
+ }
438
+ const known = expected !== void 0 ? { expected } : {};
439
+ this.say({ ...denial, ...tagged, ...known, op: "exec-integrity", errno: "EACCES" });
440
+ throw fsError("EACCES", `integrity check failed: ${real}`);
441
+ }
442
+ async expected(path) {
443
+ const answer = await this.lookup(path);
444
+ return typeof answer === "string" ? answer.toLowerCase() : void 0;
445
+ }
446
+ say(event) {
447
+ try {
448
+ this.report(event);
449
+ } catch {
450
+ }
451
+ }
452
+ };
453
+
363
454
  // src/fs/protect.ts
364
455
  function protectInit(protection) {
365
456
  const writers = [...writerTags(protection)];
366
- return { paths: [...protection.paths], ...writers.length > 0 ? { writers } : {} };
457
+ const { integrity } = protection;
458
+ return {
459
+ paths: [...protection.paths],
460
+ ...writers.length > 0 ? { writers } : {},
461
+ ...integrity ? { integrity: { unknown: integrityUnknown(integrity) } } : {}
462
+ };
367
463
  }
368
464
  function writerTags(protection) {
369
465
  const writers = protection?.writers ?? [];
@@ -644,11 +740,11 @@ var RemoteWatch = class {
644
740
  const seq2 = ++this.seq;
645
741
  this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
646
742
  if (this.peers.size === 0) return Promise.resolve();
647
- return new Promise((resolve) => {
743
+ return new Promise((resolve2) => {
648
744
  const done4 = () => {
649
745
  clearTimeout(timer);
650
746
  this.waiting.delete(seq2);
651
- resolve();
747
+ resolve2();
652
748
  };
653
749
  const timer = setTimeout(() => {
654
750
  done4();
@@ -744,7 +840,7 @@ async function openSync(handle) {
744
840
  const delay = SYNC_BACKOFF_MS[attempt];
745
841
  if (!LOCKED.has(err?.name)) throw err;
746
842
  if (delay === void 0) return void 0;
747
- await new Promise((resolve) => setTimeout(resolve, delay));
843
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
748
844
  }
749
845
  }
750
846
  }
@@ -1201,7 +1297,7 @@ var OpfsFs = class {
1201
1297
  const again = name2 === "NotReadableError" || locks && LOCKED.has(name2);
1202
1298
  if (!again || attempt >= READ_ATTEMPTS)
1203
1299
  throw translate(err, path, locks ? "write" : "read");
1204
- await new Promise((resolve) => setTimeout(resolve, attempt * 10));
1300
+ await new Promise((resolve2) => setTimeout(resolve2, attempt * 10));
1205
1301
  }
1206
1302
  }
1207
1303
  }
@@ -1220,6 +1316,29 @@ var OpfsFs = class {
1220
1316
  false
1221
1317
  );
1222
1318
  }
1319
+ async readVersioned(path) {
1320
+ const handle = await this.existing(path);
1321
+ return this.retrying(
1322
+ path,
1323
+ async () => {
1324
+ const file = await handle.getFile();
1325
+ const bytes = new Uint8Array(await file.arrayBuffer());
1326
+ return { bytes, version: `${file.lastModified}:${file.size}` };
1327
+ },
1328
+ false
1329
+ );
1330
+ }
1331
+ async version(path) {
1332
+ const handle = await this.existing(path);
1333
+ return this.retrying(
1334
+ path,
1335
+ async () => {
1336
+ const file = await handle.getFile();
1337
+ return `${file.lastModified}:${file.size}`;
1338
+ },
1339
+ false
1340
+ );
1341
+ }
1223
1342
  async pread(path, offset, length2) {
1224
1343
  const handle = await this.existing(path);
1225
1344
  return this.retrying(
@@ -1235,8 +1354,8 @@ var OpfsFs = class {
1235
1354
  }
1236
1355
  async truncate(path, size) {
1237
1356
  const handle = await this.existing(path, true);
1238
- const resolve = this.ranged || this.remote?.watched();
1239
- const target = resolve ? (await this.locate(path, true)).path : path;
1357
+ const resolve2 = this.ranged || this.remote?.watched();
1358
+ const target = resolve2 ? (await this.locate(path, true)).path : path;
1240
1359
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
1241
1360
  this.written(target);
1242
1361
  }
@@ -1660,8 +1779,8 @@ function memoryMedia() {
1660
1779
  };
1661
1780
  }
1662
1781
  function settle2(request2, value) {
1663
- return new Promise((resolve, reject) => {
1664
- request2.onsuccess = () => resolve(value());
1782
+ return new Promise((resolve2, reject) => {
1783
+ request2.onsuccess = () => resolve2(value());
1665
1784
  request2.onerror = () => reject(request2.error);
1666
1785
  });
1667
1786
  }
@@ -1699,7 +1818,7 @@ function pinScript(url) {
1699
1818
 
1700
1819
  // src/cdp/port.ts
1701
1820
  function portCdpHook(ask, gone) {
1702
- return (request2) => new Promise((resolve, reject) => {
1821
+ return (request2) => new Promise((resolve2, reject) => {
1703
1822
  const { port1, port2 } = new MessageChannel();
1704
1823
  let closed = false;
1705
1824
  const conn = {
@@ -1717,7 +1836,7 @@ function portCdpHook(ask, gone) {
1717
1836
  };
1718
1837
  port1.addEventListener("message", ({ data }) => {
1719
1838
  if ("m" in data) conn.onmessage?.(data.m);
1720
- else if ("open" in data) resolve(conn);
1839
+ else if ("open" in data) resolve2(conn);
1721
1840
  else if ("error" in data) {
1722
1841
  closed = true;
1723
1842
  port1.close();
@@ -2251,9 +2370,9 @@ var KernelPipe = class {
2251
2370
  let filled = 0;
2252
2371
  for (const chunk2 of this.chunks) {
2253
2372
  if (filled === out.length) break;
2254
- const count = Math.min(chunk2.length, out.length - filled);
2255
- out.set(chunk2.subarray(0, count), filled);
2256
- filled += count;
2373
+ const count2 = Math.min(chunk2.length, out.length - filled);
2374
+ out.set(chunk2.subarray(0, count2), filled);
2375
+ filled += count2;
2257
2376
  }
2258
2377
  return out;
2259
2378
  }
@@ -2284,25 +2403,25 @@ var KernelPipe = class {
2284
2403
  let filled = 0;
2285
2404
  while (filled < n) {
2286
2405
  const head = this.chunks[0];
2287
- const count = Math.min(head.length, n - filled);
2288
- out.set(head.subarray(0, count), filled);
2289
- filled += count;
2290
- if (count === head.length) this.chunks.shift();
2291
- else this.chunks[0] = head.subarray(count);
2406
+ const count2 = Math.min(head.length, n - filled);
2407
+ out.set(head.subarray(0, count2), filled);
2408
+ filled += count2;
2409
+ if (count2 === head.length) this.chunks.shift();
2410
+ else this.chunks[0] = head.subarray(count2);
2292
2411
  }
2293
2412
  this.size -= n;
2294
2413
  return out;
2295
2414
  }
2296
2415
  changed(signal) {
2297
2416
  if (signal?.aborted) return Promise.reject(new PipeError("EINTR"));
2298
- return new Promise((resolve, reject) => {
2417
+ return new Promise((resolve2, reject) => {
2299
2418
  const onAbort = () => {
2300
2419
  this.waiters = this.waiters.filter((w) => w !== waiter);
2301
2420
  reject(new PipeError("EINTR"));
2302
2421
  };
2303
2422
  const waiter = () => {
2304
2423
  signal?.removeEventListener("abort", onAbort);
2305
- resolve();
2424
+ resolve2();
2306
2425
  };
2307
2426
  this.waiters.push(waiter);
2308
2427
  signal?.addEventListener("abort", onAbort, { once: true });
@@ -2311,7 +2430,7 @@ var KernelPipe = class {
2311
2430
  wake() {
2312
2431
  const waiters = this.waiters;
2313
2432
  this.waiters = [];
2314
- for (const resolve of waiters) resolve();
2433
+ for (const resolve2 of waiters) resolve2();
2315
2434
  }
2316
2435
  };
2317
2436
 
@@ -2772,14 +2891,14 @@ var Changes = class {
2772
2891
  waiters = [];
2773
2892
  wait(signal) {
2774
2893
  if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
2775
- return new Promise((resolve, reject) => {
2894
+ return new Promise((resolve2, reject) => {
2776
2895
  const onAbort = () => {
2777
2896
  this.waiters = this.waiters.filter((w) => w !== waiter);
2778
2897
  reject(new KernelError("EINTR"));
2779
2898
  };
2780
2899
  const waiter = () => {
2781
2900
  signal?.removeEventListener("abort", onAbort);
2782
- resolve();
2901
+ resolve2();
2783
2902
  };
2784
2903
  this.waiters.push(waiter);
2785
2904
  signal?.addEventListener("abort", onAbort, { once: true });
@@ -2788,7 +2907,7 @@ var Changes = class {
2788
2907
  wake() {
2789
2908
  const waiters = this.waiters;
2790
2909
  this.waiters = [];
2791
- for (const resolve of waiters) resolve();
2910
+ for (const resolve2 of waiters) resolve2();
2792
2911
  }
2793
2912
  };
2794
2913
  function link(a, b) {
@@ -2956,7 +3075,7 @@ var KernelSocket = class _KernelSocket {
2956
3075
  });
2957
3076
  const { host, port } = addr;
2958
3077
  const dialled = new Promise(
2959
- (resolve) => resolve(route.uplink.dial({ network: "tcp", host, port, signal: abort.signal }))
3078
+ (resolve2) => resolve2(route.uplink.dial({ network: "tcp", host, port, signal: abort.signal }))
2960
3079
  );
2961
3080
  dialled.then(
2962
3081
  (conn) => abort.signal.aborted && conn.close(),
@@ -3306,8 +3425,8 @@ var MessageQueue = class {
3306
3425
  this.overflow = overflow;
3307
3426
  this.limit = limit;
3308
3427
  this.taken = taken;
3309
- this.closed = new Promise((resolve) => {
3310
- this.settle = resolve;
3428
+ this.closed = new Promise((resolve2) => {
3429
+ this.settle = resolve2;
3311
3430
  });
3312
3431
  }
3313
3432
  push(message3) {
@@ -3346,8 +3465,8 @@ var MessageQueue = class {
3346
3465
  continue;
3347
3466
  }
3348
3467
  if (this.ended) return;
3349
- await new Promise((resolve) => {
3350
- this.waiter = resolve;
3468
+ await new Promise((resolve2) => {
3469
+ this.waiter = resolve2;
3351
3470
  });
3352
3471
  }
3353
3472
  }
@@ -3438,8 +3557,8 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
3438
3557
  const relay = async (nid, socket) => {
3439
3558
  let ending;
3440
3559
  let late;
3441
- const unanswered = new Promise((resolve) => {
3442
- late = () => resolve("late");
3560
+ const unanswered = new Promise((resolve2) => {
3561
+ late = () => resolve2("late");
3443
3562
  });
3444
3563
  const flow = {
3445
3564
  inflight: 0,
@@ -3467,8 +3586,8 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
3467
3586
  }
3468
3587
  flow.inflight += payloadSize(data);
3469
3588
  while (flow.inflight > INBOUND_LIMIT && !flow.closing) {
3470
- await new Promise((resolve) => {
3471
- flow.wake = resolve;
3589
+ await new Promise((resolve2) => {
3590
+ flow.wake = resolve2;
3472
3591
  });
3473
3592
  }
3474
3593
  }
@@ -3487,7 +3606,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
3487
3606
  if (!socket) return;
3488
3607
  socket.send(call.data);
3489
3608
  while (socket.buffered > SEND_BUFFER && sockets.has(call.nid)) {
3490
- await new Promise((resolve) => setTimeout(resolve, 10));
3609
+ await new Promise((resolve2) => setTimeout(resolve2, 10));
3491
3610
  }
3492
3611
  peer.postMessage({ net: "ws-acked", nid: call.nid, n: payloadSize(call.data) });
3493
3612
  };
@@ -4467,6 +4586,27 @@ function clipboardAsker(port) {
4467
4586
  return { hook, answer };
4468
4587
  }
4469
4588
 
4589
+ // src/integrity-relay.ts
4590
+ function integrityAsker(port) {
4591
+ const asks = /* @__PURE__ */ new Map();
4592
+ let next2 = 0;
4593
+ const lookup = (path) => {
4594
+ const id2 = ++next2;
4595
+ const waiting = Promise.withResolvers();
4596
+ asks.set(id2, waiting);
4597
+ port.postMessage({ integrityAsk: { id: id2, path } });
4598
+ return waiting.promise;
4599
+ };
4600
+ const answer = ({ id: id2, hash, error }) => {
4601
+ const waiting = asks.get(id2);
4602
+ if (!waiting) return;
4603
+ asks.delete(id2);
4604
+ if (error !== void 0) waiting.reject(new Error(error));
4605
+ else waiting.resolve(hash);
4606
+ };
4607
+ return { lookup, answer };
4608
+ }
4609
+
4470
4610
  // src/kernel/net/loopback-names.ts
4471
4611
  var HOST_LOOPBACK = "host.slicc.internal";
4472
4612
  var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
@@ -4973,10 +5113,10 @@ var Budget = class {
4973
5113
  async acquire(n, signal) {
4974
5114
  while (this.free < n) {
4975
5115
  signal.throwIfAborted();
4976
- await new Promise((resolve) => {
5116
+ await new Promise((resolve2) => {
4977
5117
  const done4 = () => {
4978
5118
  signal.removeEventListener("abort", done4);
4979
- resolve();
5119
+ resolve2();
4980
5120
  };
4981
5121
  this.waiters.push(done4);
4982
5122
  signal.addEventListener("abort", done4, { once: true });
@@ -5118,14 +5258,14 @@ async function watchHangup(conn, abort, until) {
5118
5258
  }
5119
5259
  }
5120
5260
  function untilAborted(response, signal) {
5121
- return new Promise((resolve, reject) => {
5261
+ return new Promise((resolve2, reject) => {
5122
5262
  const onAbort = () => {
5123
5263
  reject(signal.reason ?? new KernelError("EINTR"));
5124
5264
  response.then((late) => late.cancel()).catch(() => void 0);
5125
5265
  };
5126
5266
  if (signal.aborted) return onAbort();
5127
5267
  signal.addEventListener("abort", onAbort, { once: true });
5128
- response.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
5268
+ response.then(resolve2, reject).finally(() => signal.removeEventListener("abort", onAbort));
5129
5269
  });
5130
5270
  }
5131
5271
  function plainResponse(status, message3) {
@@ -5158,8 +5298,8 @@ function wireCode(code) {
5158
5298
  return sendableCode(code) ? code : 1011;
5159
5299
  }
5160
5300
  function quietly(ms) {
5161
- return new Promise((resolve) => {
5162
- AbortSignal.timeout(ms).addEventListener("abort", () => resolve(), { once: true });
5301
+ return new Promise((resolve2) => {
5302
+ AbortSignal.timeout(ms).addEventListener("abort", () => resolve2(), { once: true });
5163
5303
  });
5164
5304
  }
5165
5305
  var RealmProxy = class {
@@ -5257,7 +5397,7 @@ var RealmProxy = class {
5257
5397
  return false;
5258
5398
  }
5259
5399
  const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
5260
- const keep = keepsAlive(req);
5400
+ const keep2 = keepsAlive(req);
5261
5401
  const framing = requestFraming(req.headers);
5262
5402
  const reserve = framing.kind === "length" ? framing.length : framing.kind === "chunked" ? this.cap() : 0;
5263
5403
  if (reserve > this.cap()) throw new HttpError(413, `request body over ${this.cap()} bytes`);
@@ -5280,7 +5420,7 @@ var RealmProxy = class {
5280
5420
  } finally {
5281
5421
  this.bodies.release(reserve);
5282
5422
  }
5283
- return await this.relay(conn, req.method, req.minor, response, keep);
5423
+ return await this.relay(conn, req.method, req.minor, response, keep2);
5284
5424
  } finally {
5285
5425
  this.stop.signal.removeEventListener("abort", stopExchange);
5286
5426
  abort.abort();
@@ -5328,8 +5468,8 @@ var RealmProxy = class {
5328
5468
  bodiless: bodiless2
5329
5469
  });
5330
5470
  const chunked = !bodiless2 && minor >= 1;
5331
- const keep = keepAlive && (bodiless2 || chunked);
5332
- headers.push(["Connection", keep ? "keep-alive" : "close"]);
5471
+ const keep2 = keepAlive && (bodiless2 || chunked);
5472
+ headers.push(["Connection", keep2 ? "keep-alive" : "close"]);
5333
5473
  if (chunked) headers.push(["Transfer-Encoding", "chunked"]);
5334
5474
  const signal = this.stop.signal;
5335
5475
  await conn.write(
@@ -5338,7 +5478,7 @@ var RealmProxy = class {
5338
5478
  );
5339
5479
  if (bodiless2) {
5340
5480
  await response.cancel();
5341
- return keep;
5481
+ return keep2;
5342
5482
  }
5343
5483
  try {
5344
5484
  for await (const piece of response.body) {
@@ -5349,7 +5489,7 @@ var RealmProxy = class {
5349
5489
  return false;
5350
5490
  }
5351
5491
  if (chunked) await conn.write(LAST_CHUNK, signal);
5352
- return keep;
5492
+ return keep2;
5353
5493
  }
5354
5494
  async connect(conn, incoming, req, idle) {
5355
5495
  const target = tunnelTarget(req);
@@ -5572,10 +5712,10 @@ function header(der, at) {
5572
5712
  let len = der[at + 1];
5573
5713
  let start2 = at + 2;
5574
5714
  if (len & 128) {
5575
- const count = len & 127;
5715
+ const count2 = len & 127;
5576
5716
  len = 0;
5577
- for (let i = 0; i < count; i++) len = len * 256 + der[start2 + i];
5578
- start2 += count;
5717
+ for (let i = 0; i < count2; i++) len = len * 256 + der[start2 + i];
5718
+ start2 += count2;
5579
5719
  }
5580
5720
  if (start2 + len > der.length) throw new Error("truncated DER");
5581
5721
  return [tag, start2, start2 + len];
@@ -5693,18 +5833,18 @@ var LEAF_LIFETIME = 7 * DAY;
5693
5833
  var ECDSA = { name: "ECDSA", namedCurve: "P-256" };
5694
5834
  var SIGN = { name: "ECDSA", hash: "SHA-256" };
5695
5835
  function request(req) {
5696
- return new Promise((resolve, reject) => {
5697
- req.onsuccess = () => resolve(req.result);
5836
+ return new Promise((resolve2, reject) => {
5837
+ req.onsuccess = () => resolve2(req.result);
5698
5838
  req.onerror = () => reject(req.error);
5699
5839
  });
5700
5840
  }
5701
5841
  function indexedDbCaStore(dbName = "slicc-realm-ca") {
5702
5842
  let db;
5703
5843
  const open = () => {
5704
- db ??= new Promise((resolve, reject) => {
5844
+ db ??= new Promise((resolve2, reject) => {
5705
5845
  const req = indexedDB.open(dbName, 1);
5706
5846
  req.onupgradeneeded = () => req.result.createObjectStore("ca");
5707
- req.onsuccess = () => resolve(req.result);
5847
+ req.onsuccess = () => resolve2(req.result);
5708
5848
  req.onerror = () => reject(req.error);
5709
5849
  });
5710
5850
  db.catch(() => {
@@ -5728,8 +5868,8 @@ function indexedDbCaStore(dbName = "slicc-realm-ca") {
5728
5868
  if (kept === record) store.put(record, owner);
5729
5869
  else stored = kept;
5730
5870
  };
5731
- await new Promise((resolve, reject) => {
5732
- tx.oncomplete = () => resolve();
5871
+ await new Promise((resolve2, reject) => {
5872
+ tx.oncomplete = () => resolve2();
5733
5873
  tx.onerror = () => reject(tx.error);
5734
5874
  });
5735
5875
  return stored;
@@ -6258,8 +6398,8 @@ var RemoteTransport = class {
6258
6398
  }
6259
6399
  ask(call) {
6260
6400
  if (this.failure) return Promise.reject(this.failure);
6261
- return new Promise((resolve, reject) => {
6262
- this.waiting.set(call.nid, { resolve, reject });
6401
+ return new Promise((resolve2, reject) => {
6402
+ this.waiting.set(call.nid, { resolve: resolve2, reject });
6263
6403
  this.port.postMessage(call);
6264
6404
  });
6265
6405
  }
@@ -6664,7 +6804,7 @@ var CdpFacade = class {
6664
6804
  const conn = await this.host(runtime);
6665
6805
  try {
6666
6806
  const reply = await new Promise(
6667
- (resolve, reject) => {
6807
+ (resolve2, reject) => {
6668
6808
  const timer = setTimeout(
6669
6809
  () => reject(new HttpError(504, "the CDP host did not answer Target.getTargets")),
6670
6810
  this.options.listTimeout ?? LIST_TIMEOUT
@@ -6677,7 +6817,7 @@ var CdpFacade = class {
6677
6817
  const parsed2 = JSON.parse(message3);
6678
6818
  if (parsed2.id !== 1) return;
6679
6819
  clearTimeout(timer);
6680
- resolve(parsed2);
6820
+ resolve2(parsed2);
6681
6821
  };
6682
6822
  conn.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
6683
6823
  }
@@ -6737,9 +6877,9 @@ var Relay = class {
6737
6877
  }
6738
6878
  async start(head) {
6739
6879
  await this.send(head);
6740
- const hostGone = new Promise((resolve) => {
6880
+ const hostGone = new Promise((resolve2) => {
6741
6881
  this.host.onclose = (reason) => {
6742
- void this.end(reason ? CLOSE.error : CLOSE.normal, reason ?? "").then(() => resolve());
6882
+ void this.end(reason ? CLOSE.error : CLOSE.normal, reason ?? "").then(() => resolve2());
6743
6883
  };
6744
6884
  });
6745
6885
  this.host.onmessage = (message3) => {
@@ -6753,8 +6893,8 @@ var Relay = class {
6753
6893
  await Promise.race([pumping, hostGone]);
6754
6894
  await this.writes;
6755
6895
  let timer;
6756
- const waited = new Promise((resolve) => {
6757
- timer = setTimeout(resolve, this.closeWaitMs);
6896
+ const waited = new Promise((resolve2) => {
6897
+ timer = setTimeout(resolve2, this.closeWaitMs);
6758
6898
  });
6759
6899
  await Promise.race([pumping, waited]);
6760
6900
  clearTimeout(timer);
@@ -7093,7 +7233,7 @@ function owned(path, st) {
7093
7233
  function unchanged(a, b, bytes) {
7094
7234
  return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
7095
7235
  }
7096
- async function sha256(bytes) {
7236
+ async function sha2562(bytes) {
7097
7237
  const digest = new Uint8Array(
7098
7238
  await crypto.subtle.digest("SHA-256", bytes)
7099
7239
  );
@@ -7149,7 +7289,7 @@ async function attest(fs, asked, o) {
7149
7289
  const bytes = (await fs.readFileBuffer(path)).slice();
7150
7290
  const after = await fs.lstat(path);
7151
7291
  if (unchanged(before, after, bytes)) {
7152
- return attested(path, after, bytes, await sha256(bytes), o, parents);
7292
+ return attested(path, after, bytes, await sha2562(bytes), o, parents);
7153
7293
  }
7154
7294
  if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
7155
7295
  }
@@ -7404,16 +7544,16 @@ var FreshModules = class {
7404
7544
  if (head) await decide(path, head);
7405
7545
  };
7406
7546
  }
7407
- const bound = /* @__PURE__ */ new WeakMap();
7547
+ const bound2 = /* @__PURE__ */ new WeakMap();
7408
7548
  return new Proxy(fs, {
7409
7549
  get(target, key, receiver) {
7410
7550
  if (Object.hasOwn(overrides, key)) return overrides[key];
7411
7551
  const value = Reflect.get(target, key, receiver);
7412
7552
  if (typeof value !== "function") return value;
7413
- let method = bound.get(value);
7553
+ let method = bound2.get(value);
7414
7554
  if (method === void 0) {
7415
7555
  method = value.bind(target);
7416
- bound.set(value, method);
7556
+ bound2.set(value, method);
7417
7557
  }
7418
7558
  return method;
7419
7559
  }
@@ -7481,12 +7621,12 @@ var PathGate = class {
7481
7621
  tails = /* @__PURE__ */ new Map();
7482
7622
  async acquire(path) {
7483
7623
  const prev = this.tails.get(path) ?? Promise.resolve();
7484
- const { promise, resolve } = Promise.withResolvers();
7624
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
7485
7625
  const tail = prev.then(() => promise);
7486
7626
  this.tails.set(path, tail);
7487
7627
  await prev;
7488
7628
  return () => {
7489
- resolve();
7629
+ resolve2();
7490
7630
  if (this.tails.get(path) === tail) this.tails.delete(path);
7491
7631
  };
7492
7632
  }
@@ -7964,7 +8104,7 @@ var ChildTable = class {
7964
8104
  }
7965
8105
  async nextChange(candidates, flags2, interrupt) {
7966
8106
  let wake;
7967
- const stateChange = new Promise((resolve) => wake = resolve);
8107
+ const stateChange = new Promise((resolve2) => wake = resolve2);
7968
8108
  const watching = flags2.untraced || flags2.continued;
7969
8109
  if (watching && wake) this.stateChanged.push(wake);
7970
8110
  try {
@@ -8011,6 +8151,65 @@ var ChildTable = class {
8011
8151
  }
8012
8152
  };
8013
8153
 
8154
+ // src/kernel/exec-ticket.ts
8155
+ var TICKET_ENV = "SLICC_SEALED_TICKET";
8156
+ var TICKET_TTL_MS = 3e4;
8157
+ var MAX_TICKETS = 256;
8158
+ var TICKET = /^[0-9a-f]{32}$/;
8159
+ async function argvDigest(argv) {
8160
+ const bytes = new TextEncoder().encode(JSON.stringify(argv));
8161
+ const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
8162
+ return [...hash].map((b) => b.toString(16).padStart(2, "0")).join("");
8163
+ }
8164
+ function isStrings(value) {
8165
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
8166
+ }
8167
+ function stringRecord(value) {
8168
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
8169
+ const entries = Object.entries(value);
8170
+ return entries.every(([, v]) => typeof v === "string") ? Object.fromEntries(entries) : void 0;
8171
+ }
8172
+ var TicketBook = class {
8173
+ booked = /* @__PURE__ */ new Map();
8174
+ now;
8175
+ normalise = async (path) => path;
8176
+ constructor(now = Date.now) {
8177
+ this.now = now;
8178
+ }
8179
+ get size() {
8180
+ return this.booked.size;
8181
+ }
8182
+ async record(req, reply, pid) {
8183
+ const { ticket, error } = reply;
8184
+ if (error !== void 0 || typeof ticket !== "string" || !TICKET.test(ticket)) return;
8185
+ const { path, argv, cwd } = req;
8186
+ const entry = {
8187
+ pid,
8188
+ path: await this.normalise(path),
8189
+ requested: path,
8190
+ requestedCwd: cwd,
8191
+ argv: await argvDigest(argv),
8192
+ cwd: await this.normalise(cwd),
8193
+ at: this.now()
8194
+ };
8195
+ this.prune();
8196
+ while (this.booked.size >= MAX_TICKETS)
8197
+ this.booked.delete(this.booked.keys().next().value);
8198
+ this.booked.set(ticket, entry);
8199
+ }
8200
+ take(ticket) {
8201
+ const booked = this.booked.get(ticket);
8202
+ this.booked.delete(ticket);
8203
+ return booked && this.now() - booked.at < TICKET_TTL_MS ? booked : void 0;
8204
+ }
8205
+ prune() {
8206
+ const now = this.now();
8207
+ for (const [ticket, booked] of this.booked) {
8208
+ if (now - booked.at >= TICKET_TTL_MS) this.booked.delete(ticket);
8209
+ }
8210
+ }
8211
+ };
8212
+
8014
8213
  // src/mount/protocol.ts
8015
8214
  var DRIVER_PROTOCOL = [1, 2];
8016
8215
  function driverVersionError(theirs) {
@@ -8081,7 +8280,7 @@ var DriverConnection = class _DriverConnection {
8081
8280
  call(call, transfer = []) {
8082
8281
  if (this.failure) return Promise.reject(this.failure);
8083
8282
  const id2 = ++this.nextId;
8084
- return new Promise((resolve, reject) => {
8283
+ return new Promise((resolve2, reject) => {
8085
8284
  const timer = unref(
8086
8285
  setTimeout(() => {
8087
8286
  this.waiting.delete(id2);
@@ -8094,7 +8293,7 @@ var DriverConnection = class _DriverConnection {
8094
8293
  this.onFail?.(error);
8095
8294
  }, this.timeoutMs)
8096
8295
  );
8097
- this.waiting.set(id2, { resolve, reject, timer });
8296
+ this.waiting.set(id2, { resolve: resolve2, reject, timer });
8098
8297
  this.port.postMessage({ ...call, id: id2 }, transfer);
8099
8298
  });
8100
8299
  }
@@ -8237,8 +8436,8 @@ var HeldPaths = class extends Map {
8237
8436
  this.keptHandles.delete(handle);
8238
8437
  if (kept) this.letGo(kept);
8239
8438
  }
8240
- const count = (this.opens.get(key) ?? 0) + (on ? 1 : -1);
8241
- if (count > 0) this.opens.set(key, count);
8439
+ const count2 = (this.opens.get(key) ?? 0) + (on ? 1 : -1);
8440
+ if (count2 > 0) this.opens.set(key, count2);
8242
8441
  else {
8243
8442
  this.opens.delete(key);
8244
8443
  for (const id2 of this.handlesAt(key)) this.handles.delete(id2);
@@ -8252,8 +8451,8 @@ var HeldPaths = class extends Map {
8252
8451
  return this.lastHandle;
8253
8452
  }
8254
8453
  holdWrite(path, on) {
8255
- const count = (this.get(path) ?? 0) + (on ? 1 : -1);
8256
- if (count > 0) this.set(path, count);
8454
+ const count2 = (this.get(path) ?? 0) + (on ? 1 : -1);
8455
+ if (count2 > 0) this.set(path, count2);
8257
8456
  else {
8258
8457
  this.delete(path);
8259
8458
  this.revoked.delete(path);
@@ -8275,18 +8474,18 @@ var HeldPaths = class extends Map {
8275
8474
  for (const path of this.keys()) if (within5(path, prefix)) this.revoked.add(path);
8276
8475
  }
8277
8476
  renamed(from, to) {
8278
- for (const [path, count] of [...this]) {
8477
+ for (const [path, count2] of [...this]) {
8279
8478
  if (!within5(path, from)) continue;
8280
8479
  const moved2 = to + path.slice(from.length);
8281
8480
  this.delete(path);
8282
- this.set(moved2, (this.get(moved2) ?? 0) + count);
8481
+ this.set(moved2, (this.get(moved2) ?? 0) + count2);
8283
8482
  if (this.revoked.delete(path)) this.revoked.add(moved2);
8284
8483
  }
8285
- for (const [path, count] of [...this.opens]) {
8484
+ for (const [path, count2] of [...this.opens]) {
8286
8485
  if (!within5(path, from)) continue;
8287
8486
  const moved2 = to + path.slice(from.length);
8288
8487
  this.opens.delete(path);
8289
- this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
8488
+ this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count2);
8290
8489
  }
8291
8490
  for (const [id2, key] of this.handles) {
8292
8491
  if (within5(key, from)) this.handles.set(id2, to + key.slice(from.length));
@@ -10580,7 +10779,7 @@ var AsyncOps = class {
10580
10779
  this.wake();
10581
10780
  }
10582
10781
  sleep(timeoutMs, signal) {
10583
- return new Promise((resolve, reject) => {
10782
+ return new Promise((resolve2, reject) => {
10584
10783
  let timer;
10585
10784
  const cleanup = () => {
10586
10785
  clearTimeout(timer);
@@ -10589,7 +10788,7 @@ var AsyncOps = class {
10589
10788
  };
10590
10789
  const wake = () => {
10591
10790
  cleanup();
10592
- resolve(true);
10791
+ resolve2(true);
10593
10792
  };
10594
10793
  const abort = () => {
10595
10794
  cleanup();
@@ -10600,7 +10799,7 @@ var AsyncOps = class {
10600
10799
  if (timeoutMs !== void 0) {
10601
10800
  timer = setTimeout(() => {
10602
10801
  cleanup();
10603
- resolve(false);
10802
+ resolve2(false);
10604
10803
  }, timeoutMs);
10605
10804
  }
10606
10805
  });
@@ -11022,7 +11221,7 @@ var Resolver = class {
11022
11221
  };
11023
11222
  function waitFor(lookup, signal) {
11024
11223
  lookup.waiters++;
11025
- return new Promise((resolve, reject) => {
11224
+ return new Promise((resolve2, reject) => {
11026
11225
  const abort = () => {
11027
11226
  if (--lookup.waiters === 0) lookup.abort.abort(new KernelError("EINTR"));
11028
11227
  reject(new KernelError("EINTR"));
@@ -11032,7 +11231,7 @@ function waitFor(lookup, signal) {
11032
11231
  if (signal?.aborted) return;
11033
11232
  lookup.waiters--;
11034
11233
  signal?.removeEventListener("abort", abort);
11035
- resolve(value);
11234
+ resolve2(value);
11036
11235
  });
11037
11236
  });
11038
11237
  }
@@ -11213,8 +11412,8 @@ var KernelTty = class {
11213
11412
  }
11214
11413
  rubOut(n) {
11215
11414
  const dropped2 = this.line.splice(this.line.length - n, n);
11216
- const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
11217
- const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
11415
+ const utf83 = (this.termios.c_iflag & IUTF8) !== 0;
11416
+ const cells = utf83 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
11218
11417
  if (cells > 0 && this.termios.c_lflag & ECHO) {
11219
11418
  this.output(encoder3.encode("\b \b".repeat(cells)), false);
11220
11419
  }
@@ -11284,14 +11483,14 @@ var KernelTty = class {
11284
11483
  }
11285
11484
  changed(signal) {
11286
11485
  if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
11287
- return new Promise((resolve, reject) => {
11486
+ return new Promise((resolve2, reject) => {
11288
11487
  const onAbort = () => {
11289
11488
  this.waiters = this.waiters.filter((w) => w !== waiter);
11290
11489
  reject(new KernelError("EINTR"));
11291
11490
  };
11292
11491
  const waiter = () => {
11293
11492
  signal?.removeEventListener("abort", onAbort);
11294
- resolve();
11493
+ resolve2();
11295
11494
  };
11296
11495
  this.waiters.push(waiter);
11297
11496
  signal?.addEventListener("abort", onAbort, { once: true });
@@ -11300,7 +11499,7 @@ var KernelTty = class {
11300
11499
  wake() {
11301
11500
  const waiters = this.waiters;
11302
11501
  this.waiters = [];
11303
- for (const resolve of waiters) resolve();
11502
+ for (const resolve2 of waiters) resolve2();
11304
11503
  }
11305
11504
  };
11306
11505
 
@@ -11399,14 +11598,14 @@ var PtyPair = class {
11399
11598
  }
11400
11599
  changed(signal) {
11401
11600
  if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
11402
- return new Promise((resolve, reject) => {
11601
+ return new Promise((resolve2, reject) => {
11403
11602
  const onAbort = () => {
11404
11603
  this.waiters = this.waiters.filter((w) => w !== waiter);
11405
11604
  reject(new KernelError("EINTR"));
11406
11605
  };
11407
11606
  const waiter = () => {
11408
11607
  signal?.removeEventListener("abort", onAbort);
11409
- resolve();
11608
+ resolve2();
11410
11609
  };
11411
11610
  this.waiters.push(waiter);
11412
11611
  signal?.addEventListener("abort", onAbort, { once: true });
@@ -11415,7 +11614,7 @@ var PtyPair = class {
11415
11614
  wake() {
11416
11615
  const waiters = this.waiters;
11417
11616
  this.waiters = [];
11418
- for (const resolve of waiters) resolve();
11617
+ for (const resolve2 of waiters) resolve2();
11419
11618
  }
11420
11619
  };
11421
11620
  var PtyTable = class {
@@ -11480,7 +11679,8 @@ function ptySyscall(req, ctx) {
11480
11679
  if (!ctx.ptys) throw new KernelError("ENOENT");
11481
11680
  const pair = ctx.ptys.get(req.n);
11482
11681
  ttyOpenable(ctx.guard, pair.slave);
11483
- const fd = ctx.fds.install(pair.openSlave(), 3);
11682
+ const slave = pair.openSlave();
11683
+ const fd = ctx.fds.install(ctx.ttyFilter?.(slave) ?? slave, 3);
11484
11684
  if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
11485
11685
  return json4(fd);
11486
11686
  }
@@ -11553,8 +11753,8 @@ async function runChild(req, deps) {
11553
11753
  };
11554
11754
  const controls = [];
11555
11755
  const capture = () => {
11556
- const { promise, resolve } = Promise.withResolvers();
11557
- const control = { shut: false, ended: resolve, eof: promise };
11756
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
11757
+ const control = { shut: false, ended: resolve2, eof: promise };
11558
11758
  controls.push(control);
11559
11759
  return { capture: true, max, overflow: () => stop("ENOBUFS"), control };
11560
11760
  };
@@ -11647,7 +11847,7 @@ async function waitReady(fds, read, write, deadline, interrupted2) {
11647
11847
  return file.changed === void 0 ? [] : [file.changed(round.signal).catch(() => void 0)];
11648
11848
  });
11649
11849
  let timer;
11650
- const expiry = deadline === Number.POSITIVE_INFINITY ? [] : [new Promise((resolve) => timer = setTimeout(resolve, deadline - Date.now()))];
11850
+ const expiry = deadline === Number.POSITIVE_INFINITY ? [] : [new Promise((resolve2) => timer = setTimeout(resolve2, deadline - Date.now()))];
11651
11851
  try {
11652
11852
  await Promise.race([...changes, ...expiry, interrupted2]);
11653
11853
  } finally {
@@ -11731,10 +11931,10 @@ async function socketSyscall(req, proc) {
11731
11931
  }
11732
11932
  }
11733
11933
  function interruptible2(dialing, signal) {
11734
- return new Promise((resolve, reject) => {
11934
+ return new Promise((resolve2, reject) => {
11735
11935
  const abort = () => reject(new KernelError("EINTR"));
11736
11936
  signal.addEventListener("abort", abort, { once: true });
11737
- dialing.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
11937
+ dialing.then(resolve2, reject).finally(() => signal.removeEventListener("abort", abort));
11738
11938
  });
11739
11939
  }
11740
11940
  function unnamed(domain) {
@@ -12581,7 +12781,7 @@ var WasmProcess = class {
12581
12781
  if (this.stopped) return "stop";
12582
12782
  this.stopped = sig;
12583
12783
  this.stops++;
12584
- this.resumed = new Promise((resolve) => this.wake = resolve);
12784
+ this.resumed = new Promise((resolve2) => this.wake = resolve2);
12585
12785
  const blocked = this.interrupt;
12586
12786
  this.interrupt = new AbortController();
12587
12787
  blocked.abort();
@@ -12646,7 +12846,9 @@ var WasmProcess = class {
12646
12846
  const { ptys, jobs } = this.options;
12647
12847
  const owner = this.options.owners ? this.ttyOwner() : void 0;
12648
12848
  const guard2 = this.guarded();
12649
- return ptySyscall(req, { pid: this.pid, fds: this.fds, ptys, jobs, guard: guard2, owner });
12849
+ const { ttyFilter } = this.options;
12850
+ const ctx = { pid: this.pid, fds: this.fds, ptys, jobs, guard: guard2, owner, ttyFilter };
12851
+ return ptySyscall(req, ctx);
12650
12852
  }
12651
12853
  return await this.procSyscall(req, cancelled);
12652
12854
  } catch (e) {
@@ -12821,7 +13023,8 @@ var WasmProcess = class {
12821
13023
  const tty = name2 === void 0 ? this.controllingTerminal() : this.options.jobs?.terminalNamed(name2);
12822
13024
  if (!tty) throw new KernelError("ENXIO");
12823
13025
  if (name2 !== void 0) ttyOpenable(this.guarded(), tty);
12824
- return this.fds.install(tty.file(), 3);
13026
+ const file = tty.file();
13027
+ return this.fds.install(this.options.ttyFilter?.(file) ?? file, 3);
12825
13028
  }
12826
13029
  openSealed() {
12827
13030
  const broker = this.options.sealed;
@@ -12861,15 +13064,8 @@ var WasmProcess = class {
12861
13064
  async sealedCaller() {
12862
13065
  const shown = await this.options.identity?.();
12863
13066
  const broker = this.options.sealed;
12864
- const tag = broker.tagOf(this.pid);
12865
- return {
12866
- pid: shown?.pid ?? this.pid,
12867
- ppid: shown?.ppid ?? 0,
12868
- uid: this.cred.euid,
12869
- gid: this.cred.egid,
12870
- ...tag !== void 0 ? { tag } : {},
12871
- users: broker.users
12872
- };
13067
+ const ids = { pid: shown?.pid ?? this.pid, ppid: shown?.ppid ?? 0 };
13068
+ return broker.stamp(ids, this.cred, broker.tagOf(this.pid));
12873
13069
  }
12874
13070
  guarded() {
12875
13071
  const { guard: guard2 } = this.options;
@@ -13377,13 +13573,13 @@ var WasmProcess = class {
13377
13573
  while (this.dlLock.depth > 0 && this.dlLock.owner !== owner) {
13378
13574
  if (this.dying) throw new KernelError("EINTR");
13379
13575
  const interrupt = this.interrupt.signal;
13380
- const gone = await new Promise((resolve) => {
13576
+ const gone = await new Promise((resolve2) => {
13381
13577
  const waiter = {
13382
13578
  owner,
13383
13579
  wake: (ended) => {
13384
13580
  this.dlLock.waiters.delete(waiter);
13385
13581
  interrupt.removeEventListener("abort", recheck);
13386
- resolve(ended);
13582
+ resolve2(ended);
13387
13583
  }
13388
13584
  };
13389
13585
  const recheck = () => waiter.wake(false);
@@ -13639,6 +13835,7 @@ function processOptions(opts, header3, hooks, created3) {
13639
13835
  ...inherited(opts),
13640
13836
  ...opts.owners ? { owners: true } : {},
13641
13837
  ...opts.sealed ? { sealed: opts.sealed } : {},
13838
+ ...opts.ttyFilter ? { ttyFilter: opts.ttyFilter } : {},
13642
13839
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
13643
13840
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
13644
13841
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
@@ -14133,6 +14330,523 @@ function replayingHops(inner, heldMs = HELD_MS) {
14133
14330
  };
14134
14331
  }
14135
14332
 
14333
+ // node_modules/@ai-ecoverse/slicc-scrub/dist/index.js
14334
+ var FRAGMENT = 16;
14335
+ var MIN_VALUE = 8;
14336
+ var MIN_BASE64 = 8;
14337
+ var encoder4 = new TextEncoder();
14338
+ var STD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
14339
+ function utf82(text) {
14340
+ return encoder4.encode(text);
14341
+ }
14342
+ function base642(bytes, padded = true) {
14343
+ let out = "";
14344
+ for (let i = 0; i < bytes.length; i += 3) {
14345
+ const a = bytes[i];
14346
+ const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
14347
+ const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
14348
+ const triple = a << 16 | b << 8 | c;
14349
+ const left = bytes.length - i;
14350
+ out += STD[triple >> 18 & 63] + STD[triple >> 12 & 63];
14351
+ out += left > 1 ? STD[triple >> 6 & 63] : padded ? "=" : "";
14352
+ out += left > 2 ? STD[triple & 63] : padded ? "=" : "";
14353
+ }
14354
+ return out;
14355
+ }
14356
+ function urlSafe(text) {
14357
+ return text.replace(/\+/g, "-").replace(/\//g, "_");
14358
+ }
14359
+ function hex2(bytes) {
14360
+ let out = "";
14361
+ for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
14362
+ return out;
14363
+ }
14364
+ function embedded(bytes, offset) {
14365
+ const shifted = new Uint8Array(offset + bytes.length);
14366
+ shifted.set(bytes, offset);
14367
+ const encoded = base642(shifted);
14368
+ const first = Math.ceil(8 * offset / 6);
14369
+ const last = Math.floor(8 * (offset + bytes.length) / 6);
14370
+ return encoded.slice(first, last);
14371
+ }
14372
+ function unicode(value, upper) {
14373
+ let out = "";
14374
+ for (let i = 0; i < value.length; i++) {
14375
+ const unit = value.charCodeAt(i);
14376
+ if (unit === 34) out += '\\"';
14377
+ else if (unit === 92) out += "\\\\";
14378
+ else if (unit < 32 || unit > 126) {
14379
+ const code = unit.toString(16).padStart(4, "0");
14380
+ out += `\\u${upper ? code.toUpperCase() : code}`;
14381
+ } else out += value[i];
14382
+ }
14383
+ return out;
14384
+ }
14385
+ function percent(value) {
14386
+ let component;
14387
+ try {
14388
+ component = encodeURIComponent(value);
14389
+ } catch {
14390
+ return [];
14391
+ }
14392
+ const lower = component.replace(/%[0-9A-F]{2}/g, (escape) => escape.toLowerCase());
14393
+ const plus = component.replace(/%20/g, "+");
14394
+ let form = "";
14395
+ for (const byte of utf82(value)) {
14396
+ const char = String.fromCharCode(byte);
14397
+ if (/[A-Za-z0-9*\-._]/.test(char)) form += char;
14398
+ else if (byte === 32) form += "+";
14399
+ else form += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
14400
+ }
14401
+ return [component, lower, plus, form];
14402
+ }
14403
+ function html(value, apostrophe) {
14404
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, apostrophe);
14405
+ }
14406
+ function encodings(bytes) {
14407
+ const standard = [base642(bytes), base642(bytes, false)];
14408
+ for (const offset of [0, 1, 2]) {
14409
+ const run2 = embedded(bytes, offset);
14410
+ if (run2.length >= MIN_BASE64) standard.push(run2);
14411
+ }
14412
+ return [...standard, ...standard.map(urlSafe)];
14413
+ }
14414
+ function shapes(value) {
14415
+ const raw = utf82(value);
14416
+ const seen = /* @__PURE__ */ new Set([value]);
14417
+ const out = [];
14418
+ if (raw.length < FRAGMENT) out.push({ bytes: raw, form: "raw" });
14419
+ for (let offset = 0; raw.length >= FRAGMENT && offset + FRAGMENT <= raw.length; offset++) {
14420
+ out.push({ bytes: raw.subarray(offset, offset + FRAGMENT), form: "fragment", offset });
14421
+ }
14422
+ const add = (text, form) => {
14423
+ if (seen.has(text)) return;
14424
+ seen.add(text);
14425
+ out.push({ bytes: utf82(text), form });
14426
+ };
14427
+ const json5 = JSON.stringify(value).slice(1, -1);
14428
+ for (const text of [
14429
+ json5,
14430
+ json5.replace(/\//g, "\\/"),
14431
+ unicode(value, false),
14432
+ unicode(value, true)
14433
+ ]) {
14434
+ add(text, "json");
14435
+ }
14436
+ for (const text of percent(value)) add(text, "percent");
14437
+ for (const text of [html(value, "&#39;"), html(value, "&#x27;")]) add(text, "html");
14438
+ for (const text of encodings(raw)) add(text, "base64");
14439
+ add(hex2(raw), "hex");
14440
+ add(hex2(raw).toUpperCase(), "hex");
14441
+ return out;
14442
+ }
14443
+ function wrappable(form) {
14444
+ return form === "base64" || form === "hex";
14445
+ }
14446
+ var Automaton = class {
14447
+ edges = /* @__PURE__ */ new Map();
14448
+ fail = [0];
14449
+ depth = [0];
14450
+ children = [0];
14451
+ out = [[]];
14452
+ dict = [-1];
14453
+ lengths = [];
14454
+ add(bytes) {
14455
+ let node = 0;
14456
+ for (const byte of bytes) {
14457
+ const key = node * 256 + byte;
14458
+ let next2 = this.edges.get(key);
14459
+ if (next2 === void 0) {
14460
+ next2 = this.depth.length;
14461
+ this.edges.set(key, next2);
14462
+ this.fail.push(0);
14463
+ this.depth.push(this.depth[node] + 1);
14464
+ this.children.push(0);
14465
+ this.children[node] += 1;
14466
+ this.out.push([]);
14467
+ this.dict.push(-1);
14468
+ }
14469
+ node = next2;
14470
+ }
14471
+ const id2 = this.lengths.length;
14472
+ this.lengths.push(bytes.length);
14473
+ this.out[node].push(id2);
14474
+ return id2;
14475
+ }
14476
+ build() {
14477
+ const queue = [];
14478
+ const byNode = /* @__PURE__ */ new Map();
14479
+ for (const [key, child] of this.edges) {
14480
+ const parent = Math.floor(key / 256);
14481
+ const list = byNode.get(parent) ?? [];
14482
+ list.push([key % 256, child]);
14483
+ byNode.set(parent, list);
14484
+ }
14485
+ for (const [, child] of byNode.get(0) ?? []) queue.push(child);
14486
+ for (let head = 0; head < queue.length; head++) {
14487
+ const node = queue[head];
14488
+ const fallback = this.fail[node];
14489
+ this.dict[node] = this.out[fallback].length > 0 ? fallback : this.dict[fallback];
14490
+ for (const [byte, child] of byNode.get(node) ?? []) {
14491
+ let state = fallback;
14492
+ let target = this.edges.get(state * 256 + byte);
14493
+ while (target === void 0 && state > 0) {
14494
+ state = this.fail[state];
14495
+ target = this.edges.get(state * 256 + byte);
14496
+ }
14497
+ this.fail[child] = target ?? 0;
14498
+ queue.push(child);
14499
+ }
14500
+ }
14501
+ }
14502
+ step(state, byte) {
14503
+ let current = state;
14504
+ for (; ; ) {
14505
+ const next2 = this.edges.get(current * 256 + byte);
14506
+ if (next2 !== void 0) return next2;
14507
+ if (current === 0) return 0;
14508
+ current = this.fail[current];
14509
+ }
14510
+ }
14511
+ matches(state, visit) {
14512
+ for (let node = state; node > 0; node = this.dict[node]) {
14513
+ for (const id2 of this.out[node]) visit(id2);
14514
+ }
14515
+ }
14516
+ partials(state, length2, visit) {
14517
+ for (let node = state; node > 0; node = this.fail[node]) {
14518
+ if (this.children[node] > 0) visit(length2 - this.depth[node]);
14519
+ }
14520
+ }
14521
+ };
14522
+ var RANK = {
14523
+ raw: 0,
14524
+ fragment: 1,
14525
+ json: 2,
14526
+ percent: 3,
14527
+ html: 4,
14528
+ base64: 5,
14529
+ hex: 6,
14530
+ wrapped: 7
14531
+ };
14532
+ var CR2 = 13;
14533
+ var LF2 = 10;
14534
+ var EMPTY2 = new Uint8Array(0);
14535
+ function register(automaton, sources, ids, bytes, source) {
14536
+ const key = Array.from(bytes).join(",");
14537
+ let id2 = ids.get(key);
14538
+ if (id2 === void 0) {
14539
+ id2 = automaton.add(bytes);
14540
+ ids.set(key, id2);
14541
+ sources[id2] = [];
14542
+ }
14543
+ sources[id2].push(source);
14544
+ }
14545
+ function prepare(secrets) {
14546
+ const main = new Automaton();
14547
+ const wrap = new Automaton();
14548
+ const sources = [];
14549
+ const wrapSources = [];
14550
+ const ids = /* @__PURE__ */ new Map();
14551
+ const wrapIds = /* @__PURE__ */ new Map();
14552
+ const values = secrets.map((secret) => utf82(secret.value));
14553
+ secrets.forEach((secret, index) => {
14554
+ if (typeof secret.value !== "string" || values[index].length < MIN_VALUE) {
14555
+ throw new RangeError(`the value of ${secret.name} is shorter than ${MIN_VALUE} bytes`);
14556
+ }
14557
+ for (const shape of shapes(secret.value)) {
14558
+ const source = { secret: index, form: shape.form, offset: shape.offset };
14559
+ register(main, sources, ids, shape.bytes, source);
14560
+ if (wrappable(shape.form)) register(wrap, wrapSources, wrapIds, shape.bytes, source);
14561
+ }
14562
+ });
14563
+ main.build();
14564
+ wrap.build();
14565
+ const replacements = secrets.map((secret) => [
14566
+ utf82(secret.placeholder),
14567
+ utf82(`[redacted:${secret.name}]`)
14568
+ ]);
14569
+ return { secrets, values, main, wrap, sources, wrapSources, replacements };
14570
+ }
14571
+ function better(a, b) {
14572
+ if (!b) return true;
14573
+ if (a.end !== b.end) return a.end > b.end;
14574
+ if (a.rank !== b.rank) return a.rank < b.rank;
14575
+ return a.secret < b.secret;
14576
+ }
14577
+ function resolve(c, input, start2, end, source) {
14578
+ if (source.form !== "fragment") {
14579
+ return {
14580
+ start: start2,
14581
+ end,
14582
+ secret: source.secret,
14583
+ form: source.form,
14584
+ rank: RANK[source.form],
14585
+ growing: false
14586
+ };
14587
+ }
14588
+ const value = c.values[source.secret];
14589
+ let stop = end;
14590
+ let at = source.offset + FRAGMENT;
14591
+ while (stop < input.length && at < value.length && input[stop] === value[at]) {
14592
+ stop++;
14593
+ at++;
14594
+ }
14595
+ const form = source.offset === 0 && at === value.length ? "raw" : "fragment";
14596
+ const growing = stop === input.length && at < value.length;
14597
+ return { start: start2, end: stop, secret: source.secret, form, rank: RANK[form], growing };
14598
+ }
14599
+ function keep(best, candidate) {
14600
+ if (better(candidate, best.get(candidate.start))) best.set(candidate.start, candidate);
14601
+ }
14602
+ function scanMain(c, input, best, partials) {
14603
+ let state = 0;
14604
+ for (let i = 0; i < input.length; i++) {
14605
+ state = c.main.step(state, input[i]);
14606
+ c.main.matches(state, (id2) => {
14607
+ const start2 = i + 1 - c.main.lengths[id2];
14608
+ for (const source of c.sources[id2]) {
14609
+ const candidate = resolve(c, input, start2, i + 1, source);
14610
+ if (candidate.growing) partials.push(candidate.start);
14611
+ keep(best, candidate);
14612
+ }
14613
+ });
14614
+ }
14615
+ c.main.partials(state, input.length, (start2) => partials.push(start2));
14616
+ }
14617
+ function scanWrapped(c, input, best, partials) {
14618
+ const map = [];
14619
+ for (let i = 0; i < input.length; i++) if (input[i] !== CR2 && input[i] !== LF2) map.push(i);
14620
+ if (map.length === input.length) return;
14621
+ let state = 0;
14622
+ for (let v = 0; v < map.length; v++) {
14623
+ state = c.wrap.step(state, input[map[v]]);
14624
+ c.wrap.matches(state, (id2) => {
14625
+ const first = v + 1 - c.wrap.lengths[id2];
14626
+ const start2 = map[first];
14627
+ const end = map[v] + 1;
14628
+ const wrapped = end - start2 !== c.wrap.lengths[id2];
14629
+ for (const source of c.wrapSources[id2]) {
14630
+ const form = wrapped ? "wrapped" : source.form;
14631
+ keep(best, { start: start2, end, secret: source.secret, form, rank: RANK[form], growing: false });
14632
+ }
14633
+ });
14634
+ }
14635
+ c.wrap.partials(state, map.length, (start2) => partials.push(map[start2]));
14636
+ }
14637
+ function count(c, hits, candidate) {
14638
+ const name2 = c.secrets[candidate.secret].name;
14639
+ const key = `${name2}\0${candidate.form}`;
14640
+ const hit = hits.get(key) ?? { name: name2, form: candidate.form, count: 0 };
14641
+ hit.count += 1;
14642
+ hits.set(key, hit);
14643
+ }
14644
+ function concat5(parts2) {
14645
+ let length2 = 0;
14646
+ for (const part of parts2) length2 += part.length;
14647
+ const out = new Uint8Array(length2);
14648
+ let at = 0;
14649
+ for (const part of parts2) {
14650
+ out.set(part, at);
14651
+ at += part.length;
14652
+ }
14653
+ return out;
14654
+ }
14655
+ function pass(c, input, final) {
14656
+ const best = /* @__PURE__ */ new Map();
14657
+ const partials = [];
14658
+ scanMain(c, input, best, partials);
14659
+ scanWrapped(c, input, best, partials);
14660
+ const holds = final ? [] : [...new Set(partials)].sort((a, b) => a - b);
14661
+ const starts = [...best.keys()].sort((a, b) => a - b);
14662
+ const parts2 = [];
14663
+ const hits = /* @__PURE__ */ new Map();
14664
+ let pos = 0;
14665
+ let cut = input.length;
14666
+ let s = 0;
14667
+ let h = 0;
14668
+ for (; ; ) {
14669
+ while (s < starts.length && starts[s] < pos) s++;
14670
+ while (h < holds.length && holds[h] < pos) h++;
14671
+ const hold = h < holds.length ? holds[h] : Infinity;
14672
+ const next2 = s < starts.length ? starts[s] : Infinity;
14673
+ if (hold <= next2) {
14674
+ cut = Math.min(hold, input.length);
14675
+ break;
14676
+ }
14677
+ const candidate = best.get(next2);
14678
+ parts2.push(
14679
+ input.subarray(pos, next2),
14680
+ c.replacements[candidate.secret][candidate.form === "raw" ? 0 : 1]
14681
+ );
14682
+ count(c, hits, candidate);
14683
+ pos = candidate.end;
14684
+ }
14685
+ parts2.push(input.subarray(pos, cut));
14686
+ return { output: concat5(parts2), cut, hits };
14687
+ }
14688
+ function merge(into, from) {
14689
+ for (const [key, hit] of from) {
14690
+ const known = into.get(key);
14691
+ if (known) known.count += hit.count;
14692
+ else into.set(key, { ...hit });
14693
+ }
14694
+ }
14695
+ function compile(secrets, options = {}) {
14696
+ const c = prepare(secrets.map((secret) => ({ ...secret })));
14697
+ const decoder4 = new TextDecoder();
14698
+ const idleMs = options.idleMs ?? 250;
14699
+ const scrubBytes = (input) => {
14700
+ if (c.secrets.length === 0) return { bytes: input.slice(), hits: [] };
14701
+ const result = pass(c, input, true);
14702
+ return { bytes: result.output, hits: [...result.hits.values()] };
14703
+ };
14704
+ return {
14705
+ version: options.version ?? 0,
14706
+ idleMs,
14707
+ size: c.main.lengths.length,
14708
+ bytes: scrubBytes,
14709
+ text(input) {
14710
+ const { bytes, hits } = scrubBytes(utf82(input));
14711
+ return { text: decoder4.decode(bytes), hits };
14712
+ },
14713
+ stream() {
14714
+ let pending = EMPTY2;
14715
+ const hits = /* @__PURE__ */ new Map();
14716
+ const run2 = (final) => {
14717
+ if (c.secrets.length === 0 || pending.length === 0) {
14718
+ const out = pending;
14719
+ pending = EMPTY2;
14720
+ return out;
14721
+ }
14722
+ const result = pass(c, pending, final);
14723
+ merge(hits, result.hits);
14724
+ pending = pending.slice(result.cut);
14725
+ return result.output;
14726
+ };
14727
+ return {
14728
+ push(chunk2) {
14729
+ pending = concat5([pending, chunk2]);
14730
+ return run2(false);
14731
+ },
14732
+ idle() {
14733
+ return pending.length < FRAGMENT ? run2(true) : EMPTY2;
14734
+ },
14735
+ end() {
14736
+ return run2(true);
14737
+ },
14738
+ get hits() {
14739
+ return [...hits.values()];
14740
+ }
14741
+ };
14742
+ }
14743
+ };
14744
+ }
14745
+
14746
+ // src/kernel/scrub-file.ts
14747
+ var SCRUB_IDLE_MS = 50;
14748
+ var encoder5 = new TextEncoder();
14749
+ function scrubberOf(secrets) {
14750
+ const usable = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
14751
+ return usable.length > 0 ? compile(usable, { idleMs: SCRUB_IDLE_MS }) : void 0;
14752
+ }
14753
+ async function writeAll2(file, bytes, signal) {
14754
+ const write = file.write;
14755
+ for (let at = 0; at < bytes.length; ) {
14756
+ at += await write.call(file, bytes.subarray(at), at === 0 ? signal : void 0);
14757
+ }
14758
+ }
14759
+ function bound(file, key) {
14760
+ const method = file[key];
14761
+ return typeof method === "function" ? { [key]: method.bind(file) } : {};
14762
+ }
14763
+ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
14764
+ const file = inner.file;
14765
+ const stream = scrubber.stream();
14766
+ let queue = Promise.resolve();
14767
+ let timer;
14768
+ let failed3;
14769
+ const raise = () => {
14770
+ if (failed3 === void 0) return;
14771
+ const err = failed3;
14772
+ failed3 = void 0;
14773
+ throw err;
14774
+ };
14775
+ const enqueue = (bytes, signal) => {
14776
+ const next2 = queue.then(() => bytes.length > 0 ? writeAll2(file, bytes, signal) : void 0);
14777
+ queue = next2.catch(() => void 0);
14778
+ return next2;
14779
+ };
14780
+ const release = () => {
14781
+ timer = void 0;
14782
+ enqueue(stream.idle()).catch((err) => {
14783
+ failed3 = err;
14784
+ });
14785
+ };
14786
+ return new OpenFile({
14787
+ ...bound(file, "read"),
14788
+ ...bound(file, "peek"),
14789
+ ...bound(file, "poll"),
14790
+ ...bound(file, "changed"),
14791
+ ...bound(file, "stat"),
14792
+ ...file.tty ? { tty: file.tty } : {},
14793
+ ...file.clipboard ? { clipboard: file.clipboard } : {},
14794
+ ...file.write ? {
14795
+ write: async (bytes, signal) => {
14796
+ if (timer !== void 0) clearTimeout(timer);
14797
+ raise();
14798
+ await enqueue(stream.push(bytes), signal);
14799
+ timer = setTimeout(release, idleMs);
14800
+ return bytes.length;
14801
+ }
14802
+ } : {},
14803
+ flush: async () => {
14804
+ await queue;
14805
+ raise();
14806
+ await file.flush?.();
14807
+ },
14808
+ close: async () => {
14809
+ if (timer !== void 0) clearTimeout(timer);
14810
+ let failure2;
14811
+ try {
14812
+ await enqueue(stream.end());
14813
+ raise();
14814
+ } catch (err) {
14815
+ failure2 = { err };
14816
+ }
14817
+ await inner.release();
14818
+ if (failure2) throw failure2.err;
14819
+ }
14820
+ });
14821
+ }
14822
+ function scrubbedAll(file, scrubbers) {
14823
+ return scrubbers.reduce((inner, scrubber) => scrubbed(inner, scrubber), file);
14824
+ }
14825
+ function stdioFiles(fds) {
14826
+ const files = /* @__PURE__ */ new Set();
14827
+ for (const fd of [1, 2]) {
14828
+ try {
14829
+ files.add(fds.get(fd));
14830
+ } catch {
14831
+ }
14832
+ }
14833
+ return files;
14834
+ }
14835
+ function scrubStdio(fds, scrubber) {
14836
+ const wrapped = /* @__PURE__ */ new Map();
14837
+ const stdio = stdioFiles(fds);
14838
+ for (const fd of fds.numbers()) {
14839
+ const original = fds.get(fd);
14840
+ if (!stdio.has(original) && !original.file.tty) continue;
14841
+ const known = wrapped.get(original);
14842
+ const flags2 = fds.statusFlags(fd);
14843
+ const wrapper = known ? known.retain() : scrubbed(original.retain(), scrubber);
14844
+ wrapped.set(original, wrapper);
14845
+ fds.installAt(fd, wrapper);
14846
+ if (flags2 !== void 0) fds.setStatusFlags(fd, flags2);
14847
+ }
14848
+ }
14849
+
14136
14850
  // src/kernel/sealed.ts
14137
14851
  var SEALED_MAX_BYTES = 64 * 1024;
14138
14852
  var SEALED_TIMEOUT_MS = 1e4;
@@ -14146,7 +14860,7 @@ var SEALED_OPS = /* @__PURE__ */ new Set([
14146
14860
  "cancel",
14147
14861
  "materialize"
14148
14862
  ]);
14149
- var encoder4 = new TextEncoder();
14863
+ var encoder6 = new TextEncoder();
14150
14864
  var decoder3 = new TextDecoder("utf-8", { fatal: true });
14151
14865
  function parseSealed(bytes) {
14152
14866
  try {
@@ -14162,11 +14876,16 @@ function parseSealed(bytes) {
14162
14876
  const req = parsed2;
14163
14877
  if (req.v !== 1 || typeof req.op !== "string" || !SEALED_OPS.has(req.op))
14164
14878
  throw new KernelError("EINVAL");
14879
+ if (req.op === "materialize" && !materializable(req)) throw new KernelError("EINVAL");
14165
14880
  return req;
14166
14881
  } finally {
14167
14882
  bytes.fill(0);
14168
14883
  }
14169
14884
  }
14885
+ function materializable(req) {
14886
+ if (req.shellGrants !== true && !isStrings(req.names)) return false;
14887
+ return isStrings(req.argv) && typeof req.path === "string" && typeof req.cwd === "string";
14888
+ }
14170
14889
  function errorReply(code, message3) {
14171
14890
  return { v: 1, error: { code, message: message3 } };
14172
14891
  }
@@ -14210,12 +14929,14 @@ var SealedChannel = class {
14210
14929
  const timer = setTimeout(() => control.abort("timeout"), this.broker.timeoutMs);
14211
14930
  const req = { ...fields, v: 1, op: fields.op, caller };
14212
14931
  const flight = { control, done: Promise.resolve() };
14213
- flight.done = this.answer(req, control.signal).then((reply) => {
14932
+ flight.done = this.answer(req, control.signal).then(async (reply) => {
14214
14933
  clearTimeout(timer);
14215
14934
  gone.removeEventListener("abort", withdraw);
14935
+ if (reply && req.op === "materialize")
14936
+ await this.broker.tickets.record(req, reply, caller.pid);
14216
14937
  if (this.inflight !== flight) return;
14217
14938
  this.inflight = void 0;
14218
- if (reply) this.reply = encoder4.encode(serialized(reply));
14939
+ if (reply) this.reply = encoder6.encode(serialized(reply));
14219
14940
  });
14220
14941
  this.inflight = flight;
14221
14942
  return length2;
@@ -14223,8 +14944,8 @@ var SealedChannel = class {
14223
14944
  async answer(req, signal) {
14224
14945
  const hook = this.broker.hook;
14225
14946
  if (!hook) return errorReply("ENOTCONN", "no vault is connected");
14226
- const stopped = new Promise((resolve) => {
14227
- signal.addEventListener("abort", () => resolve(void 0), { once: true });
14947
+ const stopped = new Promise((resolve2) => {
14948
+ signal.addEventListener("abort", () => resolve2(void 0), { once: true });
14228
14949
  });
14229
14950
  if (signal.aborted) return void 0;
14230
14951
  const asked = Promise.resolve().then(() => hook(req, { signal })).then(
@@ -14279,17 +15000,60 @@ var SealedChannel = class {
14279
15000
  this.reply = void 0;
14280
15001
  }
14281
15002
  };
15003
+ function swapOf(reply) {
15004
+ if (!isRecord(reply) || reply.error !== void 0) return void 0;
15005
+ const values = stringRecord(reply.values);
15006
+ const placeholders = stringRecord(reply.placeholders);
15007
+ return values && placeholders ? { values, placeholders } : void 0;
15008
+ }
14282
15009
  var SealedBroker = class {
14283
15010
  hook;
14284
15011
  users;
14285
15012
  timeoutMs;
14286
15013
  tagOf;
15014
+ tickets = new TicketBook();
15015
+ userOf = async () => void 0;
14287
15016
  constructor(hook, users, timeoutMs = SEALED_TIMEOUT_MS, tagOf = () => void 0) {
14288
15017
  this.hook = hook;
14289
15018
  this.users = users;
14290
15019
  this.timeoutMs = timeoutMs;
14291
15020
  this.tagOf = tagOf;
14292
15021
  }
15022
+ async stamp(ids, cred, tag) {
15023
+ const user = await this.userOf(cred.euid).catch(() => void 0);
15024
+ return {
15025
+ pid: ids.pid,
15026
+ ppid: ids.ppid,
15027
+ uid: cred.euid,
15028
+ gid: cred.egid,
15029
+ ...user !== void 0 ? { user } : {},
15030
+ groups: [...cred.groups],
15031
+ users: this.users,
15032
+ ...tag !== void 0 ? { tag } : {}
15033
+ };
15034
+ }
15035
+ async consume(fields) {
15036
+ const { hook } = this;
15037
+ if (!hook) return void 0;
15038
+ const control = new AbortController();
15039
+ const timer = setTimeout(() => control.abort("timeout"), this.timeoutMs);
15040
+ const stopped = new Promise((resolve2) => {
15041
+ control.signal.addEventListener("abort", () => resolve2(void 0), { once: true });
15042
+ });
15043
+ const req = {
15044
+ ...fields,
15045
+ v: 1,
15046
+ op: "materializeConsume",
15047
+ caller: fields.stamp
15048
+ };
15049
+ try {
15050
+ const asked = Promise.resolve().then(() => hook(req, { signal: control.signal })).then(swapOf, () => void 0);
15051
+ return await Promise.race([asked, stopped]);
15052
+ } finally {
15053
+ clearTimeout(timer);
15054
+ control.abort("done");
15055
+ }
15056
+ }
14293
15057
  open() {
14294
15058
  const channel = new SealedChannel(this);
14295
15059
  return new OpenFile({
@@ -14788,11 +15552,11 @@ function codeOf3(err) {
14788
15552
  }
14789
15553
  function wait(ms, signal) {
14790
15554
  if (signal.aborted) return Promise.resolve();
14791
- return new Promise((resolve) => {
15555
+ return new Promise((resolve2) => {
14792
15556
  const done4 = () => {
14793
15557
  clearTimeout(timer);
14794
15558
  signal.removeEventListener("abort", done4);
14795
- resolve();
15559
+ resolve2();
14796
15560
  };
14797
15561
  const timer = setTimeout(done4, ms);
14798
15562
  signal.addEventListener("abort", done4, { once: true });
@@ -15119,11 +15883,11 @@ async function resize(h, path, size) {
15119
15883
  }
15120
15884
  }
15121
15885
  function sleep(ms, signal) {
15122
- return new Promise((resolve) => {
15886
+ return new Promise((resolve2) => {
15123
15887
  const done4 = () => {
15124
15888
  clearTimeout(timer);
15125
15889
  signal.removeEventListener("abort", done4);
15126
- resolve();
15890
+ resolve2();
15127
15891
  };
15128
15892
  const timer = setTimeout(done4, ms);
15129
15893
  signal.addEventListener("abort", done4);
@@ -15206,7 +15970,7 @@ var PORT_DEAD_MS = 1e4;
15206
15970
  var PORT_LINGER_MS = 1e3;
15207
15971
  function timed(transport, ms) {
15208
15972
  const inflight = /* @__PURE__ */ new Set();
15209
- const guard2 = (name2, run2, late, expire) => new Promise((resolve, reject) => {
15973
+ const guard2 = (name2, run2, late, expire) => new Promise((resolve2, reject) => {
15210
15974
  let expired = false;
15211
15975
  const running = run2();
15212
15976
  inflight.add(running);
@@ -15223,7 +15987,7 @@ function timed(transport, ms) {
15223
15987
  (value) => {
15224
15988
  clearTimeout(timer);
15225
15989
  if (expired) late?.(value);
15226
- resolve(value);
15990
+ resolve2(value);
15227
15991
  },
15228
15992
  (err) => {
15229
15993
  clearTimeout(timer);
@@ -15358,7 +16122,7 @@ var PortTransport = class {
15358
16122
  ask(message3, bytes = 0, transfer = []) {
15359
16123
  const id2 = ++this.next;
15360
16124
  if (this.dead) return Promise.reject(fsError2("ENOTCONN", this.dead));
15361
- return new Promise((resolve, reject) => {
16125
+ return new Promise((resolve2, reject) => {
15362
16126
  const timer = setTimeout(
15363
16127
  () => {
15364
16128
  this.waiting.delete(id2);
@@ -15372,7 +16136,7 @@ var PortTransport = class {
15372
16136
  clearTimeout(timer);
15373
16137
  this.waiting.delete(id2);
15374
16138
  if (reply.error) reject(fsError2(reply.error.code ?? "EIO", reply.error.message ?? ""));
15375
- else resolve(reply);
16139
+ else resolve2(reply);
15376
16140
  });
15377
16141
  try {
15378
16142
  this.port.postMessage({ id: id2, ...message3 }, transfer);
@@ -15401,7 +16165,7 @@ var PortTransport = class {
15401
16165
  this.fail("the folder's watch ended");
15402
16166
  return Promise.resolve();
15403
16167
  }
15404
- return new Promise((resolve, reject) => {
16168
+ return new Promise((resolve2, reject) => {
15405
16169
  const finish = () => {
15406
16170
  this.waiting.delete(id2);
15407
16171
  signal.removeEventListener("abort", stop);
@@ -15412,7 +16176,7 @@ var PortTransport = class {
15412
16176
  this.port.postMessage({ id: id2, op: "unwatch" });
15413
16177
  } catch {
15414
16178
  }
15415
- resolve();
16179
+ resolve2();
15416
16180
  };
15417
16181
  this.watchBeat();
15418
16182
  this.waiting.set(id2, (reply) => {
@@ -15420,7 +16184,7 @@ var PortTransport = class {
15420
16184
  if (reply.event) return onEvent(reply.event);
15421
16185
  finish();
15422
16186
  if (reply.error) reject(fsError2(reply.error.code ?? "ENOTCONN", reply.error.message ?? ""));
15423
- else resolve();
16187
+ else resolve2();
15424
16188
  });
15425
16189
  signal.addEventListener("abort", stop, { once: true });
15426
16190
  try {
@@ -15533,10 +16297,10 @@ var ChunkedBytes = class {
15533
16297
  }
15534
16298
  truncate(size) {
15535
16299
  if (size < this.size) {
15536
- const keep = Math.ceil(size / CHUNK3);
15537
- this.chunks.length = Math.min(this.chunks.length, keep);
15538
- const tail = this.chunks[keep - 1];
15539
- const from = size - (keep - 1) * CHUNK3;
16300
+ const keep2 = Math.ceil(size / CHUNK3);
16301
+ this.chunks.length = Math.min(this.chunks.length, keep2);
16302
+ const tail = this.chunks[keep2 - 1];
16303
+ const from = size - (keep2 - 1) * CHUNK3;
15540
16304
  if (tail && from < tail.length) tail.fill(0, from);
15541
16305
  }
15542
16306
  this.size = size;
@@ -15781,8 +16545,8 @@ function importedMemory(bytes) {
15781
16545
  at = end;
15782
16546
  continue;
15783
16547
  }
15784
- const count = u32();
15785
- for (let i = 0; i < count; i++) {
16548
+ const count2 = u32();
16549
+ for (let i = 0; i < count2; i++) {
15786
16550
  const module = name2();
15787
16551
  const field3 = name2();
15788
16552
  const kind = bytes[at++];
@@ -16211,7 +16975,7 @@ ${lines2}
16211
16975
  const withdraw = () => control.abort("withdrawn");
16212
16976
  requester.addEventListener("abort", withdraw, { once: true });
16213
16977
  const aborted2 = new Promise(
16214
- (resolve) => control.signal.addEventListener("abort", () => resolve("aborted"), { once: true })
16978
+ (resolve2) => control.signal.addEventListener("abort", () => resolve2("aborted"), { once: true })
16215
16979
  );
16216
16980
  try {
16217
16981
  const answered2 = Promise.resolve().then(() => approve(req, { signal: control.signal })).catch(() => "deny");
@@ -16337,7 +17101,7 @@ var LAYOUT = [
16337
17101
  ];
16338
17102
  var LOAD_SAMPLE_MS = 5e3;
16339
17103
  var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
16340
- var encoder5 = new TextEncoder();
17104
+ var encoder7 = new TextEncoder();
16341
17105
  var USERS_MODES = ["off", "report", "enforce"];
16342
17106
  function usersMode(mode = "off") {
16343
17107
  if (USERS_MODES.includes(mode)) return mode;
@@ -16368,6 +17132,30 @@ function shebang(head) {
16368
17132
  if (head[0] !== 35 || head[1] !== 33) return void 0;
16369
17133
  return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
16370
17134
  }
17135
+ async function rootOnly(fs, path) {
17136
+ const names = chain(path);
17137
+ for (const [i, at] of names.entries()) {
17138
+ const st = await fs.lstat(at).catch(() => void 0);
17139
+ const kind = i === names.length - 1 ? st?.isFile : st?.isDirectory;
17140
+ if (!kind || st?.uid !== 0 || (st.mode & 18) !== 0) return false;
17141
+ }
17142
+ return true;
17143
+ }
17144
+ function plainEnv(words2) {
17145
+ const next2 = words2[1];
17146
+ if (baseName(words2[0] ?? "") !== "env" || next2 === void 0) return false;
17147
+ return next2 === "-S" || !next2.startsWith("-") && !next2.includes("=");
17148
+ }
17149
+ function shebangCommand(words2) {
17150
+ if (!plainEnv(words2)) {
17151
+ const arg2 = words2.slice(1).join(" ");
17152
+ return { interp: words2[0], args: arg2 ? [arg2] : [] };
17153
+ }
17154
+ const split2 = words2[1] === "-S";
17155
+ const [interp, ...rest2] = words2.slice(split2 ? 2 : 1);
17156
+ const arg = rest2.join(" ");
17157
+ return { interp, args: split2 ? rest2 : arg ? [arg] : [] };
17158
+ }
16371
17159
  function interpreter(head) {
16372
17160
  const words2 = shebang(head);
16373
17161
  if (!words2) return "node";
@@ -16421,7 +17209,7 @@ function childHandle(handle) {
16421
17209
  onState: (listener) => handle.onState(listener)
16422
17210
  };
16423
17211
  }
16424
- function concat5(chunks) {
17212
+ function concat6(chunks) {
16425
17213
  const out = new Uint8Array(chunks.reduce((n, chunk2) => n + chunk2.length, 0));
16426
17214
  let offset = 0;
16427
17215
  for (const chunk2 of chunks) {
@@ -16453,6 +17241,13 @@ var Launcher = class {
16453
17241
  unwatchScopes = () => void 0;
16454
17242
  warned = /* @__PURE__ */ new Set();
16455
17243
  compiled = /* @__PURE__ */ new Map();
17244
+ texts = /* @__PURE__ */ new Map();
17245
+ integrity;
17246
+ scannedFs;
17247
+ get scanned() {
17248
+ this.scannedFs ??= this.verifiedFs(this.base);
17249
+ return this.scannedFs;
17250
+ }
16456
17251
  processes = /* @__PURE__ */ new Map();
16457
17252
  zombies = /* @__PURE__ */ new Set();
16458
17253
  orphans = /* @__PURE__ */ new Set();
@@ -16500,6 +17295,7 @@ var Launcher = class {
16500
17295
  usersMode;
16501
17296
  dac;
16502
17297
  sealed;
17298
+ scrubs = /* @__PURE__ */ new Map();
16503
17299
  clipboard;
16504
17300
  rootFs;
16505
17301
  fileWatches;
@@ -16523,6 +17319,8 @@ var Launcher = class {
16523
17319
  void 0,
16524
17320
  (pid) => this.tagOf(pid)
16525
17321
  );
17322
+ this.sealed.tickets.normalise = (path) => physicalPathAsync(this.base, path);
17323
+ this.sealed.userOf = async (uid) => (await this.users.lookup(uid))?.name;
16526
17324
  if (this.owners) options.fs.trackOwners?.();
16527
17325
  this.mounts = new MountTable({
16528
17326
  open: (type, spec) => this.driver(type, spec),
@@ -16556,7 +17354,7 @@ var Launcher = class {
16556
17354
  if (options.uplink) this.net.useUplink({ uplink: options.uplink, routes: this.routes });
16557
17355
  enableNetwork(this.net, {
16558
17356
  transport: this.transport,
16559
- engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
17357
+ engine: kernelTlsEngine(packageTlsEngine(this.verifiedFs(options.fs), this.modulesDir)),
16560
17358
  ca: this.ca
16561
17359
  });
16562
17360
  enableCdp(this.net, this.cdp);
@@ -16591,6 +17389,16 @@ var Launcher = class {
16591
17389
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
16592
17390
  this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
16593
17391
  options.fs.protect?.(this.prefixes);
17392
+ const integrity = options.protect?.integrity;
17393
+ integrityOptions(integrity);
17394
+ if (integrity && this.prefixes.length > 0) {
17395
+ this.integrity = new Integrity(
17396
+ this.prefixes,
17397
+ options.fs,
17398
+ integrity,
17399
+ (event) => options.onDenial?.(event)
17400
+ );
17401
+ }
16594
17402
  this.users = new UserDb(this.fs, this.owners);
16595
17403
  this.sudoer = new Sudo({
16596
17404
  fs: this.fs,
@@ -16636,7 +17444,7 @@ var Launcher = class {
16636
17444
  commands(scope = "") {
16637
17445
  let found = this.catalogs.get(scope);
16638
17446
  if (!found) {
16639
- found = this.roots(scope).then((roots) => scanCommands(this.base, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
17447
+ found = this.roots(scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
16640
17448
  this.catalogs.set(scope, found);
16641
17449
  }
16642
17450
  return found;
@@ -16644,7 +17452,7 @@ var Launcher = class {
16644
17452
  ownCommands(scope) {
16645
17453
  let found = this.ownCatalogs.get(scope);
16646
17454
  if (!found) {
16647
- found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.base, roots)).then((commands) => new Set(commands.keys()));
17455
+ found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => new Set(commands.keys()));
16648
17456
  this.ownCatalogs.set(scope, found);
16649
17457
  }
16650
17458
  return found;
@@ -16653,7 +17461,7 @@ var Launcher = class {
16653
17461
  let found = this.binfmts.get(scope);
16654
17462
  if (!found) {
16655
17463
  found = this.roots(scope).then(
16656
- (roots) => scanBinfmts(this.base, roots, (message3) => {
17464
+ (roots) => scanBinfmts(this.scanned, roots, (message3) => {
16657
17465
  if (this.warned.has(message3)) return;
16658
17466
  this.warned.add(message3);
16659
17467
  console.warn(message3);
@@ -16761,21 +17569,26 @@ var Launcher = class {
16761
17569
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
16762
17570
  const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
16763
17571
  if (!words2) return void 0;
16764
- if (baseName(words2[0]) === "env") words2.shift();
16765
- const [interp, ...rest2] = words2;
16766
- const arg = rest2.join(" ");
16767
- const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
16768
- const found = interp ? await this.resolve(interp, interp, cwd, scope) : void 0;
17572
+ const { interp, args } = shebangCommand(words2);
17573
+ const passed = [...args, command ? script : file, ...argv.slice(1)];
17574
+ if (!interp) return void 0;
17575
+ const found = await this.resolve(interp, interp, cwd, scope);
16769
17576
  if (!found) {
16770
- if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp, cwd, scope)) {
17577
+ if (depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp, cwd, scope)) {
16771
17578
  return void 0;
16772
17579
  }
16773
17580
  const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
16774
17581
  const via = [interp, ...inner?.via ?? []];
16775
- return inner && { ...inner, target: withScriptEnv(inner.target, command), via };
17582
+ const sources = [script, ...inner?.sources ?? []];
17583
+ return inner && { ...inner, target: withScriptEnv(inner.target, command), via, sources };
16776
17584
  }
16777
17585
  const target = withScriptEnv(found, command);
16778
- return { target, args: [...target.prefix ?? [], ...passed], via: [interp] };
17586
+ return {
17587
+ target,
17588
+ args: [...target.prefix ?? [], ...passed],
17589
+ via: [interp],
17590
+ sources: [script]
17591
+ };
16779
17592
  }
16780
17593
  async plan(file, argv, cwd, scope, argv0) {
16781
17594
  const found = await this.resolve(file, argv[0] ?? file, cwd, scope);
@@ -16801,13 +17614,106 @@ var Launcher = class {
16801
17614
  await need(g, "exec", this.fs.resolvePath(req.cwd, file), want);
16802
17615
  }
16803
17616
  }
17617
+ async checkedPlan(req, { ppid, cred, scope }, fds) {
17618
+ const planned = await this.plan(req.file, req.argv, req.cwd, scope, req.argv0);
17619
+ try {
17620
+ await this.mayExec(cred, { ...req, pid: ppid }, planned);
17621
+ } catch (err) {
17622
+ await fds.closeAll();
17623
+ throw err.code === "EACCES" ? new SpawnError("EACCES") : err;
17624
+ }
17625
+ if (planned) return planned;
17626
+ await fds.closeAll();
17627
+ throw new SpawnError(await this.unrunnable(req.file, req.cwd, scope));
17628
+ }
17629
+ async ticketFile(req, fds) {
17630
+ if (req.file.startsWith("/")) return physicalPathAsync(this.base, req.file);
17631
+ await fds.closeAll();
17632
+ throw new SpawnError("EACCES");
17633
+ }
17634
+ takeTicket(req) {
17635
+ if (!(TICKET_ENV in req.env)) return void 0;
17636
+ return this.sealed.tickets.take(req.env[TICKET_ENV] ?? "") ?? null;
17637
+ }
17638
+ async redeemed(req, who, planned, fds) {
17639
+ if (who.booked === void 0) return { env: req.env };
17640
+ try {
17641
+ const redeemed = await this.redeem(req, who, planned);
17642
+ if (redeemed.scrub) scrubStdio(fds, redeemed.scrub);
17643
+ return redeemed;
17644
+ } catch {
17645
+ await fds.closeAll();
17646
+ throw new SpawnError("EACCES");
17647
+ }
17648
+ }
17649
+ async redeem(req, { ppid, cred, scope, booked }, planned) {
17650
+ const { [TICKET_ENV]: ticket = "", ...env } = req.env;
17651
+ const caller = this.jobs.shown(ppid);
17652
+ const named = typeof req.argv0 === "string" && !planned.script && !planned.target.fixed;
17653
+ const argv = named ? [req.argv0, ...req.argv.slice(1)] : req.argv;
17654
+ const exact = booked?.pid === caller && req.file === booked.path && await physicalPathAsync(this.base, req.cwd) === booked.cwd && await argvDigest(argv) === booked.argv;
17655
+ if (!exact) throw new SpawnError("EACCES");
17656
+ const { requested: path, requestedCwd: cwd, path: resolved2 } = booked;
17657
+ const pid = this.nextPid++;
17658
+ const ids = { pid: req.exec ? caller : pid, ppid: caller };
17659
+ const stamp = await this.sealed.stamp(ids, cred, this.tagOf(ppid));
17660
+ const interpreters = await this.interpreterChain(planned, booked.cwd, scope);
17661
+ const trusted = this.usersMode === "enforce" && await this.trustedFiles(planned, req.file, booked.cwd, scope);
17662
+ const swap = await this.sealed.consume({
17663
+ ticket,
17664
+ stamp,
17665
+ path,
17666
+ resolved: resolved2,
17667
+ argv,
17668
+ cwd,
17669
+ interpreters,
17670
+ trusted
17671
+ });
17672
+ if (!swap) throw new SpawnError("EACCES");
17673
+ const secrets = [];
17674
+ for (const [name2, placeholder] of Object.entries(swap.placeholders)) {
17675
+ const value = swap.values[name2];
17676
+ if (value === void 0 || env[name2] !== placeholder) continue;
17677
+ env[name2] = value;
17678
+ secrets.push({ name: name2, value, placeholder });
17679
+ }
17680
+ const scrub = scrubberOf(secrets);
17681
+ return { env, pid, cwd: booked.cwd, ...scrub ? { scrub } : {} };
17682
+ }
17683
+ async interpreterChain(planned, cwd, scope) {
17684
+ const out = [];
17685
+ for (const entry of planned.via ?? []) {
17686
+ if (entry.includes("/")) {
17687
+ out.push(await physicalPathAsync(this.base, this.fs.resolvePath(cwd, entry)));
17688
+ continue;
17689
+ }
17690
+ const script = (await this.scriptCommand(entry, cwd, scope))?.script;
17691
+ out.push(
17692
+ await physicalPathAsync(this.base, script ?? await this.commandFile(entry, cwd, scope))
17693
+ );
17694
+ }
17695
+ return out;
17696
+ }
17697
+ async commandFile(name2, cwd, scope) {
17698
+ const scoped = await this.resolve(name2, name2, cwd, scope);
17699
+ const system = scope === "" ? scoped : await this.resolve(name2, name2, cwd, "");
17700
+ if (!scoped || system?.glue === scoped.glue) return `/usr/bin/${name2}`;
17701
+ return scoped.glue;
17702
+ }
17703
+ async trustedFiles(planned, file, cwd, scope) {
17704
+ const files = await this.backingOf(planned, file, cwd, scope);
17705
+ for (const at of files) {
17706
+ if (!await rootOnly(this.base, await physicalPathAsync(this.base, at))) return false;
17707
+ }
17708
+ return true;
17709
+ }
16804
17710
  async grantedCwd(caller, cwd) {
16805
17711
  const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
16806
17712
  return real === cwd && await this.sudoer.reachable(caller, cwd);
16807
17713
  }
16808
17714
  async binfmtManifests(scripts) {
16809
17715
  const sources = /* @__PURE__ */ new Map();
16810
- const binfmts = await scanBinfmts(this.base, await this.roots(), void 0, sources);
17716
+ const binfmts = await scanBinfmts(this.scanned, await this.roots(), void 0, sources);
16811
17717
  const manifests = [];
16812
17718
  for (const script of scripts) {
16813
17719
  const head = await this.head(script);
@@ -16819,13 +17725,15 @@ var Launcher = class {
16819
17725
  }
16820
17726
  async backing(file, cwd) {
16821
17727
  const planned = await this.plan(file, [file], cwd, "");
16822
- if (!planned) return void 0;
17728
+ return planned && this.backingOf(planned, file, cwd, "");
17729
+ }
17730
+ async backingOf(planned, file, cwd, scope) {
16823
17731
  const { glue, wasm, imports } = planned.target;
16824
17732
  const files = [glue, wasm, ...imports ? [imports] : []];
16825
17733
  const scripts = [];
16826
17734
  for (const name2 of [file, ...planned.via ?? []]) {
16827
17735
  if (name2.includes("/") && !COMMAND2.test(name2)) scripts.push(this.fs.resolvePath(cwd, name2));
16828
- const script = (await this.scriptCommand(name2, cwd, ""))?.script;
17736
+ const script = (await this.scriptCommand(name2, cwd, scope))?.script;
16829
17737
  if (script) scripts.push(script);
16830
17738
  }
16831
17739
  files.push(...scripts, ...await this.binfmtManifests(scripts));
@@ -16842,25 +17750,72 @@ var Launcher = class {
16842
17750
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
16843
17751
  return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
16844
17752
  }
16845
- module(path) {
16846
- return this.base.stat(path).then((st) => {
16847
- const key = `${path}:${st.size}:${st.mtime.getTime()}`;
16848
- let module = this.compiled.get(key);
16849
- if (!module) {
16850
- module = this.base.readFileBuffer(path).then(async (bytes) => {
16851
- const memory = importedMemory(bytes);
16852
- const foreign = foreignImports(bytes);
16853
- const compiled = await WebAssembly.compile(bytes);
16854
- return {
16855
- module: compiled,
16856
- ...memory ? { memory } : {},
16857
- foreign
16858
- };
16859
- });
16860
- this.compiled.set(key, module);
16861
- module.catch(() => this.compiled.delete(key));
16862
- }
16863
- return module;
17753
+ verifiedFs(fs) {
17754
+ const read = (path) => this.verified(path, KERNEL_CALLER, fs);
17755
+ return Object.assign(Object.create(fs), {
17756
+ readFileBuffer: read,
17757
+ readFile: (path) => this.integrity ? this.text(path, KERNEL_CALLER) : fs.readFile(path)
17758
+ });
17759
+ }
17760
+ async cacheKey(path) {
17761
+ const verified = await this.integrity?.key(path);
17762
+ if (verified !== void 0) return verified;
17763
+ const st = await this.base.stat(path);
17764
+ return `${path}:${st.size}:${st.mtime.getTime()}`;
17765
+ }
17766
+ verified(path, caller, fs = this.base) {
17767
+ const plain = () => fs.readFileBuffer(path);
17768
+ return this.integrity ? this.integrity.read(path, caller, plain) : plain();
17769
+ }
17770
+ keyed(path, caller) {
17771
+ const plain = async () => this.base.readFileBuffer(path);
17772
+ return this.integrity ? this.integrity.readKeyed(path, caller, plain) : plain().then((bytes) => ({ bytes }));
17773
+ }
17774
+ cached(cache, key, load) {
17775
+ const hit = cache.get(key);
17776
+ if (hit !== void 0) return hit;
17777
+ const loading2 = load();
17778
+ const found = loading2.then(({ value }) => value);
17779
+ cache.set(key, found);
17780
+ loading2.then(
17781
+ (done4) => {
17782
+ if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
17783
+ if (cache.get(key) === found) cache.delete(key);
17784
+ if (done4.key !== void 0) cache.set(done4.key, found);
17785
+ },
17786
+ () => cache.delete(key)
17787
+ );
17788
+ return found;
17789
+ }
17790
+ async module(path, caller) {
17791
+ const key = await this.cacheKey(path);
17792
+ return this.cached(this.compiled, key, async () => {
17793
+ const { bytes, key: read } = await this.keyed(path, caller);
17794
+ const memory = importedMemory(bytes);
17795
+ const foreign = foreignImports(bytes);
17796
+ const compiled = await WebAssembly.compile(bytes);
17797
+ return { value: { module: compiled, ...memory ? { memory } : {}, foreign }, key: read };
17798
+ });
17799
+ }
17800
+ async checkedSources(planned, caller) {
17801
+ const { target } = planned;
17802
+ const files = [target.wasm, target.glue, target.imports, ...planned.sources ?? []];
17803
+ const manifests = /* @__PURE__ */ new Set();
17804
+ for (const file of files) {
17805
+ const root = file === void 0 ? void 0 : PACKAGE_ROOT.exec(file)?.[1];
17806
+ if (root !== void 0) manifests.add(`${root}/package.json`);
17807
+ }
17808
+ for (const source of planned.sources ?? []) await this.text(source, caller);
17809
+ for (const manifest of manifests) {
17810
+ if (await this.base.exists(manifest)) await this.text(manifest, caller);
17811
+ }
17812
+ }
17813
+ async text(path, caller) {
17814
+ if (!this.integrity) return this.base.readFile(path);
17815
+ const key = await this.cacheKey(path);
17816
+ return this.cached(this.texts, key, async () => {
17817
+ const { bytes, key: read } = await this.keyed(path, caller);
17818
+ return { value: new TextDecoder().decode(bytes), key: read };
16864
17819
  });
16865
17820
  }
16866
17821
  async names(path, module) {
@@ -16872,23 +17827,23 @@ var Launcher = class {
16872
17827
  const optional = `${root}-names${beside.slice(root.length)}`;
16873
17828
  return await this.base.exists(optional) ? optional : void 0;
16874
17829
  }
16875
- async program(target, env) {
17830
+ async program(target, env, caller) {
16876
17831
  if (target.abi === "js" && target.wasm === SUDO_PATH) {
16877
17832
  return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
16878
17833
  }
16879
17834
  if (target.abi === "js") {
16880
- return { abi: "js", glue: await this.base.readFile(target.wasm), path: target.wasm };
17835
+ return { abi: "js", glue: await this.text(target.wasm, caller), path: target.wasm };
16881
17836
  }
16882
17837
  if (target.abi !== "wasi") {
16883
17838
  const [glue, { module: module2 }] = await Promise.all([
16884
- this.base.readFile(target.glue),
16885
- this.module(target.wasm)
17839
+ this.text(target.glue, caller),
17840
+ this.module(target.wasm, caller)
16886
17841
  ]);
16887
17842
  return { glue, module: module2 };
16888
17843
  }
16889
- const { module, memory, foreign } = await this.module(target.wasm);
17844
+ const { module, memory, foreign } = await this.module(target.wasm, caller);
16890
17845
  const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
16891
- const imports = target.imports ? await this.base.readFile(target.imports) : void 0;
17846
+ const imports = target.imports ? await this.text(target.imports, caller) : void 0;
16892
17847
  return {
16893
17848
  abi: "wasi",
16894
17849
  glue: "",
@@ -16907,10 +17862,13 @@ var Launcher = class {
16907
17862
  const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
16908
17863
  const env = { ...expandDefaults(target.env, seen), ...req.env };
16909
17864
  for (const key of target.unset ?? []) delete env[key];
17865
+ delete env[TICKET_ENV];
16910
17866
  env.HOSTNAME = this.hostname;
16911
17867
  let program;
16912
17868
  try {
16913
- program = await this.program(target, env);
17869
+ const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tag };
17870
+ if (this.integrity) await this.checkedSources(planned, caller);
17871
+ program = await this.program(target, env, caller);
16914
17872
  } catch (err) {
16915
17873
  await req.fds.closeAll();
16916
17874
  throw err;
@@ -16920,16 +17878,23 @@ var Launcher = class {
16920
17878
  groupSession(pgid) {
16921
17879
  return this.jobs.sessionOf(pgid);
16922
17880
  }
17881
+ treeScrubs(pid, req) {
17882
+ const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
17883
+ const scrubs = req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
17884
+ if (scrubs) this.scrubs.set(pid, scrubs);
17885
+ return scrubs;
17886
+ }
16923
17887
  start(req) {
16924
17888
  this.sampling();
16925
17889
  if (req.pgid !== void 0 && this.jobs.sessionOf(req.pgid) === void 0) {
16926
17890
  throw new SpawnError("ESRCH");
16927
17891
  }
16928
- const pid = this.nextPid++;
17892
+ const pid = req.pid ?? this.nextPid++;
17893
+ const scrubs = this.treeScrubs(pid, req);
16929
17894
  const stack = this.stackFor(req.tag);
16930
17895
  const terminal = req.fds.stdioTerminal();
16931
17896
  const decided = req.decided ?? Promise.resolve(true);
16932
- const count = (byParent) => {
17897
+ const count2 = (byParent) => {
16933
17898
  this.identities.asked++;
16934
17899
  if (!byParent) this.identities.timedOut++;
16935
17900
  };
@@ -16937,7 +17902,7 @@ var Launcher = class {
16937
17902
  const handle = spawnWasmProcess({
16938
17903
  pid,
16939
17904
  identity: async () => {
16940
- await (asked ??= decided.then(count));
17905
+ await (asked ??= decided.then(count2));
16941
17906
  return this.identityOf(pid);
16942
17907
  },
16943
17908
  onSyscall: (call) => {
@@ -16951,6 +17916,7 @@ var Launcher = class {
16951
17916
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
16952
17917
  ...this.owners ? { owners: true } : {},
16953
17918
  sealed: this.sealed,
17919
+ ...scrubs ? { ttyFilter: (file) => scrubbedAll(file, scrubs) } : {},
16954
17920
  clipboard: this.clipboard,
16955
17921
  ...this.dac ? { dac: this.dac } : {},
16956
17922
  program: req.program,
@@ -17017,6 +17983,7 @@ var Launcher = class {
17017
17983
  if (spent) addTimes(this.imageTimes, this.jobs.shown(pid), spent);
17018
17984
  this.stopped.delete(pid);
17019
17985
  this.processes.delete(pid);
17986
+ this.scrubs.delete(pid);
17020
17987
  if (this.processes.size === 0) {
17021
17988
  clearInterval(this.sampler);
17022
17989
  this.sampler = void 0;
@@ -17188,6 +18155,7 @@ var Launcher = class {
17188
18155
  }
17189
18156
  spawner(ppid, report) {
17190
18157
  return async (req, fds, grant) => {
18158
+ const booked = this.takeTicket(req);
17191
18159
  if (grant && !await this.grantedCwd(grant.caller, req.cwd)) {
17192
18160
  await fds.closeAll();
17193
18161
  throw new SpawnError("EACCES");
@@ -17199,23 +18167,20 @@ var Launcher = class {
17199
18167
  }
17200
18168
  const cred = grant ? grant.cred : this.credOf(ppid);
17201
18169
  const scope = grant ? "" : await this.scopeOf(cred.euid);
17202
- const planned = await this.plan(req.file, req.argv, req.cwd, scope, req.argv0);
17203
- try {
17204
- await this.mayExec(cred, { ...req, pid: ppid }, planned);
17205
- } catch (err) {
17206
- await fds.closeAll();
17207
- throw err.code === "EACCES" ? new SpawnError("EACCES") : err;
17208
- }
17209
- if (!planned) {
17210
- await fds.closeAll();
17211
- throw new SpawnError(await this.unrunnable(req.file, req.cwd, scope));
17212
- }
18170
+ const file = booked === void 0 ? req.file : await this.ticketFile(req, fds);
18171
+ const planned = await this.checkedPlan({ ...req, file }, { ppid, cred, scope }, fds);
18172
+ const redeemed = await this.redeemed(
18173
+ { ...req, file },
18174
+ { ppid, cred, scope, booked },
18175
+ planned,
18176
+ fds
18177
+ );
17213
18178
  const exec = req.exec ? { exec: true } : {};
17214
18179
  const deciding = req.exec ? void 0 : this.undecided(ppid);
17215
18180
  const handle = await this.launch(planned, {
17216
18181
  ...deciding ? { decided: deciding.promise } : {},
17217
- env: req.env,
17218
18182
  cwd: req.cwd,
18183
+ ...redeemed,
17219
18184
  fds,
17220
18185
  report,
17221
18186
  ppid,
@@ -17268,11 +18233,11 @@ var Launcher = class {
17268
18233
  if (!waiting) return;
17269
18234
  this.deciding.delete(ppid);
17270
18235
  setTimeout(() => {
17271
- for (const resolve of waiting) resolve(true);
18236
+ for (const resolve2 of waiting) resolve2(true);
17272
18237
  }, 0);
17273
18238
  }
17274
18239
  forker(ppid, parent) {
17275
- const { exec: _exec, pgid: _pgid, group: _group, ...image } = parent;
18240
+ const { exec: _exec, pgid: _pgid, group: _group, pid: _pid, scrub: _scrub, ...image } = parent;
17276
18241
  return async (state, fds) => childHandle(
17277
18242
  this.start({
17278
18243
  ...image,
@@ -17402,11 +18367,14 @@ var Launcher = class {
17402
18367
  }
17403
18368
  };
17404
18369
  }
17405
- const module = (await scanFilesystems(this.base, await this.roots())).get(type);
18370
+ const module = (await scanFilesystems(this.scanned, await this.roots())).get(type);
17406
18371
  if (!module) throw fsError("ENODEV", `unknown file system type ${type}`);
17407
18372
  if (!this.createDriverWorker)
17408
18373
  throw fsError("ENODEV", `this kernel cannot start ${type} drivers`);
17409
- const code = await this.base.readFile(module);
18374
+ const root = PACKAGE_ROOT.exec(module)?.[1];
18375
+ if (this.integrity && root !== void 0)
18376
+ await this.text(`${root}/package.json`, KERNEL_CALLER);
18377
+ const code = new TextDecoder().decode(await this.verified(module, KERNEL_CALLER));
17410
18378
  const worker = this.createDriverWorker();
17411
18379
  const driver = new MessageChannel();
17412
18380
  const client = new MessageChannel();
@@ -17646,7 +18614,14 @@ var Launcher = class {
17646
18614
  );
17647
18615
  }
17648
18616
  environment(cwd, extra, account) {
17649
- return { ...this.env, ...userEnv(account, this.pnpmHome), PWD: cwd, ...extra };
18617
+ const env = {
18618
+ ...this.env,
18619
+ ...userEnv(account, this.pnpmHome),
18620
+ PWD: cwd,
18621
+ ...extra
18622
+ };
18623
+ delete env[TICKET_ENV];
18624
+ return env;
17650
18625
  }
17651
18626
  async account(user) {
17652
18627
  if (typeof user === "object") return user;
@@ -17742,7 +18717,7 @@ var Launcher = class {
17742
18717
  },
17743
18718
  account
17744
18719
  );
17745
- const report = (message3) => options.onData(encoder5.encode(`${message3}\r
18720
+ const report = (message3) => options.onData(encoder7.encode(`${message3}\r
17746
18721
  `));
17747
18722
  const handle = await this.launch(planned, {
17748
18723
  env,
@@ -17773,18 +18748,18 @@ var Launcher = class {
17773
18748
  const env = this.environment(cwd, options.env, account);
17774
18749
  const out = [];
17775
18750
  const err = [];
17776
- const keep = options.collect !== false;
18751
+ const keep2 = options.collect !== false;
17777
18752
  const collect = (chunks, tee) => (bytes) => {
17778
- if (keep) chunks.push(bytes);
18753
+ if (keep2) chunks.push(bytes);
17779
18754
  tee?.(bytes);
17780
18755
  };
17781
18756
  const stdout = collect(out, options.onStdout);
17782
18757
  const stderr = collect(err, options.onStderr);
17783
- const report = (message3) => stderr(encoder5.encode(`${message3}
18758
+ const report = (message3) => stderr(encoder7.encode(`${message3}
17784
18759
  `));
17785
18760
  if (!planned) {
17786
18761
  report(`${file}: command not found`);
17787
- return { status: NOT_FOUND, stdout: concat5(out), stderr: concat5(err) };
18762
+ return { status: NOT_FOUND, stdout: concat6(out), stderr: concat6(err) };
17788
18763
  }
17789
18764
  const fds = new FdTable();
17790
18765
  fds.installAt(0, options.stdin ? bytesSource(options.stdin) : deviceFile("null"));
@@ -17802,7 +18777,7 @@ var Launcher = class {
17802
18777
  options.onStarted?.(handle.pid);
17803
18778
  const status = await handle.exited;
17804
18779
  const signal = handle.termsig();
17805
- return { status, ...signal ? { signal } : {}, stdout: concat5(out), stderr: concat5(err) };
18780
+ return { status, ...signal ? { signal } : {}, stdout: concat6(out), stderr: concat6(err) };
17806
18781
  }
17807
18782
  };
17808
18783
 
@@ -17903,14 +18878,14 @@ function dial(l, req) {
17903
18878
  function sudoApprovals(port) {
17904
18879
  const waiting = /* @__PURE__ */ new Map();
17905
18880
  return {
17906
- ask: (req, { signal }) => new Promise((resolve) => {
17907
- waiting.set(req.id, resolve);
18881
+ ask: (req, { signal }) => new Promise((resolve2) => {
18882
+ waiting.set(req.id, resolve2);
17908
18883
  signal.addEventListener(
17909
18884
  "abort",
17910
18885
  () => {
17911
18886
  waiting.delete(req.id);
17912
18887
  port.postMessage({ sudoAbort: { id: req.id, reason: String(signal.reason) } });
17913
- resolve("deny");
18888
+ resolve2("deny");
17914
18889
  },
17915
18890
  { once: true }
17916
18891
  );
@@ -17977,22 +18952,26 @@ function usersOp(l, req) {
17977
18952
  if (req.op === "groups-remove-member") return l.users.removeMember(req.group, req.user);
17978
18953
  return l.users.list();
17979
18954
  }
17980
- function hostOptions(req, port, sealed, clipboard) {
18955
+ function hostOptions(req, port, sealed, clipboard, lookup) {
17981
18956
  return {
17982
18957
  ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
17983
18958
  ...req.users !== void 0 ? { users: req.users } : {},
17984
18959
  ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
17985
- ...req.protect ? { protect: protectInit(req.protect) } : {},
18960
+ ...req.protect ? { protect: hostProtect(req.protect, lookup) } : {},
17986
18961
  ...req.sealed ? { sealed } : {},
17987
18962
  ...req.clipboard ? { clipboard } : {}
17988
18963
  };
17989
18964
  }
18965
+ function hostProtect(init, lookup) {
18966
+ const { integrity, ...rest2 } = protectInit(init);
18967
+ return { ...rest2, ...integrity ? { integrity: { ...integrity, lookup } } : {} };
18968
+ }
17990
18969
  function policyAsker(port) {
17991
18970
  const policies = /* @__PURE__ */ new Map();
17992
18971
  let nextPolicy = 0;
17993
- const askPolicy = (req) => new Promise((resolve) => {
18972
+ const askPolicy = (req) => new Promise((resolve2) => {
17994
18973
  const id2 = ++nextPolicy;
17995
- policies.set(id2, resolve);
18974
+ policies.set(id2, resolve2);
17996
18975
  port.postMessage({ mountPolicy: { id: id2, req } });
17997
18976
  });
17998
18977
  return {
@@ -18015,14 +18994,15 @@ function serveKernel(port, deps) {
18015
18994
  const clients = /* @__PURE__ */ new Set();
18016
18995
  const sealed = sealedAsker(port);
18017
18996
  const clipboard = clipboardAsker(port);
18997
+ const integrity = integrityAsker(port);
18018
18998
  const approvals = sudoApprovals(port);
18019
18999
  const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
18020
19000
  let holding;
18021
19001
  function holdLock(locks2) {
18022
- holding ??= new Promise((resolve) => {
19002
+ holding ??= new Promise((resolve2) => {
18023
19003
  const name2 = `slicc-kernel:${crypto.randomUUID()}`;
18024
19004
  void locks2.request(name2, () => {
18025
- resolve(name2);
19005
+ resolve2(name2);
18026
19006
  return new Promise(() => {
18027
19007
  });
18028
19008
  });
@@ -18081,7 +19061,7 @@ function serveKernel(port, deps) {
18081
19061
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
18082
19062
  ...req.modules ? { modules: req.modules } : {},
18083
19063
  ...req.env ? { env: req.env } : {},
18084
- ...hostOptions(req, port, sealed.hook, clipboard.hook),
19064
+ ...hostOptions(req, port, sealed.hook, clipboard.hook, integrity.lookup),
18085
19065
  ...req.approve ? { approve: approvals.ask } : {},
18086
19066
  ...net,
18087
19067
  ...media ? { media } : {},
@@ -18151,6 +19131,8 @@ function serveKernel(port, deps) {
18151
19131
  if (sealedReply) return sealed.answer(sealedReply);
18152
19132
  const clipboardReply = event.data.clipboardReply;
18153
19133
  if (clipboardReply) return clipboard.answer(clipboardReply);
19134
+ const integrityReply = event.data.integrityReply;
19135
+ if (integrityReply) return integrity.answer(integrityReply);
18154
19136
  const grant = event.data.hostfsGrant;
18155
19137
  if (grant) return granted2(grant);
18156
19138
  if (allowed(event.data)) return;