@ai-ecoverse/slicc-kernel 1.90.0 → 1.92.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.
@@ -2299,8 +2299,8 @@ async function access(fs, cred, path, want, options = {}) {
2299
2299
  if (await present(fs, walked.path, false)) return { ...walked, denied: walked.path };
2300
2300
  return { ...walked, missing: true };
2301
2301
  }
2302
- function refused(path, errno = "EACCES") {
2303
- return fsError(errno, path);
2302
+ function refused(path, errno2 = "EACCES") {
2303
+ return fsError(errno2, path);
2304
2304
  }
2305
2305
 
2306
2306
  // src/fs/dac.ts
@@ -2360,9 +2360,9 @@ var Created = class {
2360
2360
  function linked(g, options) {
2361
2361
  return g.links === void 0 ? options : { ...options, links: g.links };
2362
2362
  }
2363
- function deny(g, op, path, errno = "EACCES") {
2363
+ function deny(g, op, path, errno2 = "EACCES") {
2364
2364
  const tag = g.tag === void 0 ? {} : { tag: g.tag };
2365
- g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
2365
+ g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno: errno2, ...tag });
2366
2366
  }
2367
2367
  var TTY_MODE = 400;
2368
2368
  function ttyOpenable(g, tty) {
@@ -3029,7 +3029,7 @@ var WASI_SIGNAL_TO_POSIX = Object.fromEntries(
3029
3029
 
3030
3030
  // src/process/wasi-errno.ts
3031
3031
  var WASI_ERRNO = {
3032
- ...Object.fromEntries(Object.entries(E).map(([name2, errno]) => [`E${name2}`, errno])),
3032
+ ...Object.fromEntries(Object.entries(E).map(([name2, errno2]) => [`E${name2}`, errno2])),
3033
3033
  EWOULDBLOCK: E.AGAIN,
3034
3034
  ENOTSUP: 138,
3035
3035
  EOPNOTSUPP: 138,
@@ -3126,7 +3126,7 @@ function dialError(e, timedOut, closed) {
3126
3126
  if (timedOut) return new KernelError("ETIMEDOUT");
3127
3127
  if (closed) return new KernelError("ECONNABORTED");
3128
3128
  const code = e?.code;
3129
- const known = DIAL_ERRORS.find((errno) => errno === code);
3129
+ const known = DIAL_ERRORS.find((errno2) => errno2 === code);
3130
3130
  return new KernelError(known ?? "EHOSTUNREACH");
3131
3131
  }
3132
3132
  function isLoopback(host) {
@@ -3793,11 +3793,11 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
3793
3793
  };
3794
3794
  const sockets = /* @__PURE__ */ new Map();
3795
3795
  const credit = /* @__PURE__ */ new Map();
3796
- const refill = (nid, n, closing = false) => {
3796
+ const refill = (nid, n, closing2 = false) => {
3797
3797
  const flow = credit.get(nid);
3798
3798
  if (!flow) return;
3799
3799
  flow.inflight -= n;
3800
- if (closing && !flow.closing) {
3800
+ if (closing2 && !flow.closing) {
3801
3801
  flow.closing = true;
3802
3802
  flow.deadline();
3803
3803
  }
@@ -4928,6 +4928,33 @@ function toHostLoopback(href) {
4928
4928
  url.hostname = "127.0.0.1";
4929
4929
  return url.href;
4930
4930
  }
4931
+ var LOOPBACK_V4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
4932
+ var LOOPBACK_V6 = /* @__PURE__ */ new Set(["::1", "::", "0:0:0:0:0:0:0:1"]);
4933
+ var MAPPED_V4 = /^::ffff:7f[0-9a-f]{2}:[0-9a-f]{1,4}$/;
4934
+ var KERNEL_PORT = /^(\d{1,5})\.kernel\.localhost$/;
4935
+ var SCHEMES = {
4936
+ "http:": 80,
4937
+ "ws:": 80,
4938
+ "https:": 443,
4939
+ "wss:": 443
4940
+ };
4941
+ function isKernelLoopbackHost(hostname) {
4942
+ const host = hostname.replace(/^\[(.*)\]$/, "$1").replace(/\.$/, "").toLowerCase();
4943
+ return host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0" || LOOPBACK_V4.test(host) || LOOPBACK_V6.has(host) || MAPPED_V4.test(host);
4944
+ }
4945
+ function kernelLoopbackPort(href) {
4946
+ let url;
4947
+ try {
4948
+ url = new URL(href);
4949
+ } catch {
4950
+ return void 0;
4951
+ }
4952
+ const fallback = SCHEMES[url.protocol];
4953
+ if (fallback === void 0 || !isKernelLoopbackHost(url.hostname)) return void 0;
4954
+ const named = KERNEL_PORT.exec(url.hostname.replace(/\.$/, "").toLowerCase())?.[1];
4955
+ const port = Number(named ?? (url.port || fallback));
4956
+ return port >= 1 && port <= 65535 ? port : void 0;
4957
+ }
4931
4958
  var DEFAULT_HOSTNAME = "slicc";
4932
4959
  var HOSTNAME_ADDRESS = "127.0.1.1";
4933
4960
  var LIBC_HOSTNAMES = ["emscripten", "wasmer.sh"];
@@ -5233,6 +5260,9 @@ async function acceptKey(headers) {
5233
5260
  }
5234
5261
  const key = field(headers, "sec-websocket-key") ?? "";
5235
5262
  if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
5263
+ return acceptFor(key);
5264
+ }
5265
+ async function acceptFor(key) {
5236
5266
  const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
5237
5267
  return btoa(String.fromCharCode(...new Uint8Array(digest)));
5238
5268
  }
@@ -5256,6 +5286,19 @@ function frame(opcode, payload) {
5256
5286
  out.set(payload, size);
5257
5287
  return out;
5258
5288
  }
5289
+ function clientFrame(opcode, payload) {
5290
+ const plain = frame(opcode, payload);
5291
+ const head = plain.length - payload.length;
5292
+ const mask = crypto.getRandomValues(new Uint8Array(4));
5293
+ const out = new Uint8Array(plain.length + 4);
5294
+ out.set(plain.subarray(0, head));
5295
+ out[1] = out[1] | 128;
5296
+ out.set(mask, head);
5297
+ for (let i = 0; i < payload.length; i++) {
5298
+ out[head + 4 + i] = payload[i] ^ mask[i & 3];
5299
+ }
5300
+ return out;
5301
+ }
5259
5302
  function closeFrame(code, reason = "") {
5260
5303
  let bytes = new TextEncoder().encode(reason);
5261
5304
  if (bytes.length > 123) {
@@ -5295,10 +5338,12 @@ var WsReader = class {
5295
5338
  continuing = false;
5296
5339
  binaryMessage = false;
5297
5340
  binary;
5298
- constructor(incoming, max = MAX_MESSAGE, binary) {
5341
+ fromServer;
5342
+ constructor(incoming, max = MAX_MESSAGE, binary, fromServer = false) {
5299
5343
  this.incoming = incoming;
5300
5344
  this.max = max;
5301
5345
  this.binary = binary;
5346
+ this.fromServer = fromServer;
5302
5347
  }
5303
5348
  async next(signal) {
5304
5349
  for (; ; ) {
@@ -5310,7 +5355,7 @@ var WsReader = class {
5310
5355
  const fin = (head[0] & 128) !== 0;
5311
5356
  const opcode = head[0] & 15;
5312
5357
  if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
5313
- if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
5358
+ const masked = this.masking(head[1]);
5314
5359
  const length2 = await this.length(head[1] & 127, signal);
5315
5360
  const control = opcode >= 8;
5316
5361
  if (control && (length2 > 125 || !fin)) {
@@ -5319,15 +5364,19 @@ var WsReader = class {
5319
5364
  if (!control && this.size + length2 > this.max) {
5320
5365
  throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
5321
5366
  }
5322
- const mask = new Uint8Array(4);
5323
- await fill(this.incoming, mask, signal);
5324
- const payload = new Uint8Array(length2);
5325
- await fill(this.incoming, payload, signal);
5326
- for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
5367
+ const payload = await this.payload(length2, masked, signal);
5327
5368
  const event = this.take(opcode, fin, payload);
5328
5369
  if (event) return event;
5329
5370
  }
5330
5371
  }
5372
+ masking(second) {
5373
+ const masked = (second & 128) !== 0;
5374
+ if (masked !== this.fromServer) return masked;
5375
+ throw new WsError(
5376
+ CLOSE.protocol,
5377
+ this.fromServer ? "server frames must not be masked" : "client frames must be masked"
5378
+ );
5379
+ }
5331
5380
  data(opcode, fin, payload) {
5332
5381
  if (opcode === OP.continuation !== this.continuing) {
5333
5382
  throw new WsError(CLOSE.protocol, "unexpected continuation frame");
@@ -5342,6 +5391,14 @@ var WsReader = class {
5342
5391
  this.size = 0;
5343
5392
  return this.binaryMessage ? { kind: "binary", bytes: whole } : { kind: "text", text: decode(whole) };
5344
5393
  }
5394
+ async payload(length2, masked, signal) {
5395
+ const mask = new Uint8Array(4);
5396
+ if (masked) await fill(this.incoming, mask, signal);
5397
+ const payload = new Uint8Array(length2);
5398
+ await fill(this.incoming, payload, signal);
5399
+ if (masked) for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
5400
+ return payload;
5401
+ }
5345
5402
  async length(short, signal) {
5346
5403
  if (short < 126) return short;
5347
5404
  const ext = new Uint8Array(short === 126 ? 2 : 8);
@@ -5777,10 +5834,10 @@ var RealmProxy = class {
5777
5834
  encodedBodies: this.options.transport.traits.encodedBodies,
5778
5835
  bodiless: bodiless2
5779
5836
  });
5780
- const chunked = !bodiless2 && minor >= 1;
5781
- const keep2 = keepAlive && (bodiless2 || chunked);
5837
+ const chunked2 = !bodiless2 && minor >= 1;
5838
+ const keep2 = keepAlive && (bodiless2 || chunked2);
5782
5839
  headers.push(["Connection", keep2 ? "keep-alive" : "close"]);
5783
- if (chunked) headers.push(["Transfer-Encoding", "chunked"]);
5840
+ if (chunked2) headers.push(["Transfer-Encoding", "chunked"]);
5784
5841
  const signal = this.stop.signal;
5785
5842
  await conn.write(
5786
5843
  responseHead(status, response.statusText || REASON[status] || "", headers),
@@ -5792,13 +5849,13 @@ var RealmProxy = class {
5792
5849
  }
5793
5850
  try {
5794
5851
  for await (const piece of response.body) {
5795
- if (piece.length > 0) await conn.write(chunked ? chunk(piece) : piece, signal);
5852
+ if (piece.length > 0) await conn.write(chunked2 ? chunk(piece) : piece, signal);
5796
5853
  }
5797
5854
  } catch {
5798
5855
  await response.cancel().catch(() => void 0);
5799
5856
  return false;
5800
5857
  }
5801
- if (chunked) await conn.write(LAST_CHUNK, signal);
5858
+ if (chunked2) await conn.write(LAST_CHUNK, signal);
5802
5859
  return keep2;
5803
5860
  }
5804
5861
  async connect(conn, incoming, req, idle) {
@@ -8225,15 +8282,15 @@ var UnlinkedKeeper = class {
8225
8282
  wrap(fs = this.fs) {
8226
8283
  const keeper = this;
8227
8284
  const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
8228
- const counted = (st) => {
8285
+ const counted2 = (st) => {
8229
8286
  const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
8230
8287
  return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
8231
8288
  };
8232
8289
  return {
8233
8290
  ...fs,
8234
8291
  links: (path) => mayLink(fs, path),
8235
- stat: async (path) => counted(await fs.stat(path)),
8236
- lstat: async (path) => counted(await fs.lstat(path)),
8292
+ stat: async (path) => counted2(await fs.stat(path)),
8293
+ lstat: async (path) => counted2(await fs.lstat(path)),
8237
8294
  async rm(path, options) {
8238
8295
  const target = fs.resolvePath("/", path);
8239
8296
  const pinned = await keeper.pin(target);
@@ -8247,7 +8304,7 @@ var UnlinkedKeeper = class {
8247
8304
  await keeper.settle(pinned, () => fs.rename(from, to));
8248
8305
  },
8249
8306
  readdir: async (path) => visible(await fs.readdir(path)),
8250
- readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted(st)])
8307
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted2(st)])
8251
8308
  };
8252
8309
  }
8253
8310
  async settle(pinned, op) {
@@ -10174,6 +10231,7 @@ async function storageUsage() {
10174
10231
  if (typeof estimate?.quota !== "number") return null;
10175
10232
  return { quota: estimate.quota, usage: estimate.usage ?? 0 };
10176
10233
  }
10234
+ var PLAIN_DIR_MODE = 493;
10177
10235
  var done = { ok: true, kind: "void" };
10178
10236
  function json2(value) {
10179
10237
  return { ok: true, kind: "json", json: value };
@@ -10183,6 +10241,11 @@ function ownerFor2(entry, mode) {
10183
10241
  if (!cred) return void 0;
10184
10242
  return ownerOf(cred, mode === void 0 ? void 0 : mode & ~(entry.umask?.() ?? 18));
10185
10243
  }
10244
+ function plainDirMode(entry, mode) {
10245
+ if (entry.cred || mode === void 0) return void 0;
10246
+ const wanted = mode & 4095 & ~(entry.umask?.() ?? 18);
10247
+ return wanted === PLAIN_DIR_MODE ? void 0 : wanted;
10248
+ }
10186
10249
  function mkdirOptions(entry, mode = 511) {
10187
10250
  const owner = ownerFor2(entry, mode);
10188
10251
  if (!owner) return { recursive: true };
@@ -10224,9 +10287,13 @@ async function run(entry, path, req) {
10224
10287
  return json2(await fs.readdir(path));
10225
10288
  case "readdir-stat":
10226
10289
  return json2(await readdirStat(fs, path));
10227
- case "mkdir":
10290
+ case "mkdir": {
10291
+ const plain = plainDirMode(entry, req.mode);
10292
+ const fresh2 = plain !== void 0 && !await fs.exists(path);
10228
10293
  await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
10294
+ if (fresh2) await fs.chmod(path, plain);
10229
10295
  return done;
10296
+ }
10230
10297
  case "rm":
10231
10298
  await fs.rm(path, { recursive: true });
10232
10299
  return done;
@@ -11079,7 +11146,7 @@ function mountTable(mounts = []) {
11079
11146
  }
11080
11147
 
11081
11148
  // src/version.ts
11082
- var KERNEL_VERSION = true ? "1.90.0" : "0.0.0-dev";
11149
+ var KERNEL_VERSION = true ? "1.92.0" : "0.0.0-dev";
11083
11150
  var OS_RELEASE = "6.0.0-slicc";
11084
11151
  function procVersion() {
11085
11152
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -11667,6 +11734,339 @@ async function hostSyscall(req, ctx) {
11667
11734
  }
11668
11735
  }
11669
11736
 
11737
+ // src/kernel/net/loopback-transport.ts
11738
+ var LOOPBACK_TRAITS = {
11739
+ manualRedirects: true,
11740
+ encodedBodies: true,
11741
+ maxRequestBody: 64 * 1024 * 1024,
11742
+ crossOrigin: "any",
11743
+ websocket: true
11744
+ };
11745
+ var MAX_HEAD2 = 64 * 1024;
11746
+ var OWN_HEADERS = /* @__PURE__ */ new Set([
11747
+ "host",
11748
+ "connection",
11749
+ "content-length",
11750
+ "transfer-encoding",
11751
+ "upgrade",
11752
+ "sec-websocket-key",
11753
+ "sec-websocket-version",
11754
+ "sec-websocket-protocol",
11755
+ "sec-websocket-extensions"
11756
+ ]);
11757
+ var OP_CLOSE = 8;
11758
+ var OP_PONG = 10;
11759
+ var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
11760
+ var SAFE_VALUE = /^[^\r\n\0]*$/;
11761
+ var CLOSE_WAIT_MS3 = 5e3;
11762
+ var MAX_UNREAD = 16 * 1024 * 1024;
11763
+ function errno(code, message3) {
11764
+ return Object.assign(new Error(`${code}: ${message3}`), { code });
11765
+ }
11766
+ function requestHead(method, url, headers, extra) {
11767
+ if (!TOKEN3.test(method)) throw errno("EINVAL", `method ${method}`);
11768
+ const lines2 = [`${method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
11769
+ for (const [name2, value] of headers) {
11770
+ if (OWN_HEADERS.has(name2.toLowerCase())) continue;
11771
+ if (!TOKEN3.test(name2) || !SAFE_VALUE.test(value)) throw errno("EINVAL", `header ${name2}`);
11772
+ lines2.push(`${name2}: ${value}`);
11773
+ }
11774
+ for (const [name2, value] of extra) lines2.push(`${name2}: ${value}`);
11775
+ return latin1Bytes(`${lines2.join("\r\n")}\r
11776
+ \r
11777
+ `);
11778
+ }
11779
+ function parseHead(bytes) {
11780
+ const [statusLine = "", ...lines2] = latin1(bytes).split(/\r?\n/);
11781
+ const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
11782
+ if (!match) throw errno("EPROTO", `not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
11783
+ const headers = [];
11784
+ for (const line of lines2) {
11785
+ const colon = line.indexOf(":");
11786
+ if (colon > 0) headers.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()]);
11787
+ }
11788
+ return { status: Number(match[1]), statusText: match[2] ?? "", headers };
11789
+ }
11790
+ async function readHead(incoming, signal) {
11791
+ for (; ; ) {
11792
+ let raw;
11793
+ try {
11794
+ raw = await incoming.head(MAX_HEAD2, signal);
11795
+ } catch (err) {
11796
+ if (err instanceof KernelError) throw err;
11797
+ throw errno("EPROTO", "the response head is malformed or too large");
11798
+ }
11799
+ if (!raw) throw errno("ECONNRESET", "the connection closed before a response");
11800
+ const head = parseHead(raw);
11801
+ if (head.status < 100 || head.status >= 200 || head.status === 101) return head;
11802
+ }
11803
+ }
11804
+ async function* chunked(incoming, signal) {
11805
+ for (; ; ) {
11806
+ const line = await incoming.line(4096, signal);
11807
+ const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(line);
11808
+ if (!size) throw errno("EPROTO", "a malformed chunk size");
11809
+ const n = Number.parseInt(size[1], 16);
11810
+ if (n === 0) {
11811
+ while (await incoming.line(MAX_HEAD2, signal) !== "") ;
11812
+ return;
11813
+ }
11814
+ yield await incoming.exactly(n, signal);
11815
+ if (await incoming.line(2, signal) !== "") throw errno("EPROTO", "a malformed chunk");
11816
+ }
11817
+ }
11818
+ async function* counted(incoming, length2, signal) {
11819
+ for (let left = length2; left > 0; ) {
11820
+ const data = await incoming.some(Math.min(left, 64 * 1024), signal);
11821
+ if (data.length === 0) throw errno("EPROTO", "the body ended early");
11822
+ left -= data.length;
11823
+ yield data;
11824
+ }
11825
+ }
11826
+ async function* untilClose(incoming, signal) {
11827
+ for (; ; ) {
11828
+ const data = await incoming.some(64 * 1024, signal);
11829
+ if (data.length === 0) return;
11830
+ yield data;
11831
+ }
11832
+ }
11833
+ function bodyOf(incoming, head, method, signal) {
11834
+ if (method === "HEAD" || head.status === 204 || head.status === 304) return [];
11835
+ if (fieldTokens(head.headers, "transfer-encoding").includes("chunked")) {
11836
+ return chunked(incoming, signal);
11837
+ }
11838
+ const lengths = fieldValues(head.headers, "content-length");
11839
+ if (lengths.length === 0) return untilClose(incoming, signal);
11840
+ const length2 = lengths[0]?.trim() ?? "";
11841
+ if (!/^\d{1,15}$/.test(length2) || lengths.some((l) => l.trim() !== length2)) {
11842
+ throw errno("EPROTO", `a malformed Content-Length: ${lengths.join(", ")}`);
11843
+ }
11844
+ return counted(incoming, Number(length2), signal);
11845
+ }
11846
+ var KernelLoopback = class {
11847
+ net;
11848
+ cred;
11849
+ constructor(net, cred) {
11850
+ this.net = net;
11851
+ this.cred = cred;
11852
+ }
11853
+ port(url) {
11854
+ return kernelLoopbackPort(url);
11855
+ }
11856
+ connect(url, port, signal) {
11857
+ if (url.protocol === "https:" || url.protocol === "wss:") {
11858
+ throw errno("ENOTSUP", `TLS to the kernel's loopback is not supported: ${url.origin}`);
11859
+ }
11860
+ signal.throwIfAborted();
11861
+ const socket = this.net.socket("inet");
11862
+ try {
11863
+ void socket.connect({ family: "inet", host: "127.0.0.1", port }, false, this.cred());
11864
+ } catch (err) {
11865
+ socket.close();
11866
+ const refused2 = err instanceof KernelError && err.code === "ECONNREFUSED";
11867
+ throw refused2 ? errno("ECONNREFUSED", `nothing listens on the kernel's port ${port}`) : err;
11868
+ }
11869
+ signal.addEventListener("abort", () => socket.close(), { once: true });
11870
+ return socket;
11871
+ }
11872
+ async fetch(req, port) {
11873
+ const url = new URL(req.url);
11874
+ const body = req.body ?? (/^(POST|PUT|PATCH)$/i.test(req.method) ? new Uint8Array(0) : void 0);
11875
+ const length2 = body ? [["Content-Length", String(body.length)]] : [];
11876
+ const head = requestHead(req.method, url, req.headers, [...length2, ["Connection", "close"]]);
11877
+ const socket = this.connect(url, port, req.signal);
11878
+ try {
11879
+ await writeAll2(socket, body ? concat([head, body]) : head, req.signal);
11880
+ const incoming = new Incoming(socket);
11881
+ const response = await readHead(incoming, req.signal);
11882
+ const chunks = bodyOf(incoming, response, req.method, req.signal);
11883
+ return {
11884
+ url: req.url,
11885
+ status: response.status,
11886
+ statusText: response.statusText,
11887
+ headers: response.headers,
11888
+ body: closing(chunks, socket),
11889
+ cancel: async () => socket.close()
11890
+ };
11891
+ } catch (err) {
11892
+ socket.close();
11893
+ throw err;
11894
+ }
11895
+ }
11896
+ async websocket(req, port) {
11897
+ const url = new URL(req.url);
11898
+ const socket = this.connect(url, port, req.signal);
11899
+ try {
11900
+ const key = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));
11901
+ const extra = [
11902
+ ["Upgrade", "websocket"],
11903
+ ["Connection", "Upgrade"],
11904
+ ["Sec-WebSocket-Key", key],
11905
+ ["Sec-WebSocket-Version", "13"]
11906
+ ];
11907
+ if (req.protocols.length > 0)
11908
+ extra.push(["Sec-WebSocket-Protocol", req.protocols.join(", ")]);
11909
+ await writeAll2(socket, requestHead("GET", url, req.headers, extra), req.signal);
11910
+ const incoming = new Incoming(socket);
11911
+ const head = await readHead(incoming, req.signal);
11912
+ if (head.status !== 101) {
11913
+ throw errno(
11914
+ "EPROTO",
11915
+ `the server answered ${head.status} ${head.statusText}, not an upgrade`
11916
+ );
11917
+ }
11918
+ const upgraded = fieldTokens(head.headers, "upgrade").includes("websocket") && fieldTokens(head.headers, "connection").includes("upgrade");
11919
+ const accept2 = fieldValues(head.headers, "sec-websocket-accept")[0];
11920
+ if (!upgraded || accept2 !== await acceptFor(key)) {
11921
+ throw errno("EPROTO", "the server did not accept the WebSocket key");
11922
+ }
11923
+ const protocol = fieldValues(head.headers, "sec-websocket-protocol")[0] ?? "";
11924
+ return new LoopbackWebSocket(socket, incoming, protocol);
11925
+ } catch (err) {
11926
+ socket.close();
11927
+ throw err;
11928
+ }
11929
+ }
11930
+ };
11931
+ async function writeAll2(socket, bytes, signal) {
11932
+ for (let at = 0; at < bytes.length; ) at += await socket.write(bytes.subarray(at), signal);
11933
+ }
11934
+ async function* closing(chunks, socket) {
11935
+ try {
11936
+ yield* chunks;
11937
+ } catch (err) {
11938
+ if (typeof err?.code === "string") throw err;
11939
+ throw errno("EPROTO", err instanceof Error ? err.message : String(err));
11940
+ } finally {
11941
+ socket.close();
11942
+ }
11943
+ }
11944
+ var LoopbackWebSocket = class {
11945
+ protocol;
11946
+ messages;
11947
+ closed;
11948
+ pending = 0;
11949
+ writes = Promise.resolve();
11950
+ sentClose = false;
11951
+ stop = new AbortController();
11952
+ socket;
11953
+ settle;
11954
+ unread = 0;
11955
+ room;
11956
+ constructor(socket, incoming, protocol) {
11957
+ this.socket = socket;
11958
+ this.protocol = protocol;
11959
+ this.closed = new Promise((resolve2) => {
11960
+ this.settle = resolve2;
11961
+ });
11962
+ this.stop.signal.addEventListener("abort", () => this.room?.(), { once: true });
11963
+ const queue = [];
11964
+ let wake;
11965
+ let ended = false;
11966
+ void this.pump(new WsReader(incoming, void 0, true, true), (message3) => {
11967
+ queue.push(message3);
11968
+ this.unread += message3.length;
11969
+ wake?.();
11970
+ }).finally(() => {
11971
+ ended = true;
11972
+ wake?.();
11973
+ });
11974
+ const taken = (message3) => {
11975
+ this.unread -= message3.length;
11976
+ const room = this.room;
11977
+ this.room = void 0;
11978
+ room?.();
11979
+ };
11980
+ this.messages = {
11981
+ [Symbol.asyncIterator]: async function* () {
11982
+ for (; ; ) {
11983
+ const next2 = queue.shift();
11984
+ if (next2 !== void 0) {
11985
+ taken(next2);
11986
+ yield next2;
11987
+ continue;
11988
+ }
11989
+ if (ended) return;
11990
+ await new Promise((resolve2) => {
11991
+ wake = resolve2;
11992
+ });
11993
+ wake = void 0;
11994
+ }
11995
+ }
11996
+ };
11997
+ }
11998
+ async space() {
11999
+ while (this.unread > MAX_UNREAD && !this.stop.signal.aborted) {
12000
+ await new Promise((resolve2) => {
12001
+ this.room = resolve2;
12002
+ });
12003
+ }
12004
+ }
12005
+ get buffered() {
12006
+ return this.pending;
12007
+ }
12008
+ write(bytes) {
12009
+ this.pending += bytes.length;
12010
+ const previous = this.writes;
12011
+ this.writes = (async () => {
12012
+ await previous;
12013
+ try {
12014
+ await writeAll2(this.socket, bytes, this.stop.signal);
12015
+ } catch {
12016
+ this.stop.abort();
12017
+ }
12018
+ this.pending -= bytes.length;
12019
+ })();
12020
+ }
12021
+ async pump(reader, deliver) {
12022
+ let close = { code: 1006, reason: "" };
12023
+ try {
12024
+ for (; ; ) {
12025
+ await this.space();
12026
+ const event = await reader.next(this.stop.signal);
12027
+ if (!event) break;
12028
+ if (event.kind === "text") deliver(event.text);
12029
+ else if (event.kind === "binary") deliver(event.bytes);
12030
+ else if (event.kind === "ping") this.write(clientFrame(OP_PONG, event.payload));
12031
+ else {
12032
+ close = { code: event.code, reason: event.reason };
12033
+ if (!this.sentClose) this.sendClose(event.code === 1005 ? 1e3 : event.code, "");
12034
+ break;
12035
+ }
12036
+ }
12037
+ } catch (err) {
12038
+ if (err instanceof WsError) this.sendClose(err.code, err.message);
12039
+ close = { code: err instanceof WsError ? err.code : 1006, reason: "" };
12040
+ } finally {
12041
+ const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
12042
+ await this.writes;
12043
+ clearTimeout(late);
12044
+ this.socket.close();
12045
+ this.settle(close);
12046
+ }
12047
+ }
12048
+ sendClose(code, reason) {
12049
+ if (this.sentClose) return;
12050
+ this.sentClose = true;
12051
+ const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
12052
+ void this.closed.then(() => clearTimeout(late));
12053
+ const bytes = new TextEncoder().encode(reason).subarray(0, 123);
12054
+ const payload = new Uint8Array(2 + bytes.length);
12055
+ new DataView(payload.buffer).setUint16(0, sendableCode(code) ? code : CLOSE.error);
12056
+ payload.set(bytes, 2);
12057
+ this.write(clientFrame(OP_CLOSE, payload));
12058
+ }
12059
+ send(data) {
12060
+ if (this.sentClose) throw errno("EPIPE", "the websocket is closing");
12061
+ const text = typeof data === "string";
12062
+ const payload = text ? new TextEncoder().encode(data) : data;
12063
+ this.write(clientFrame(text ? OP_TEXT : OP_BINARY, payload));
12064
+ }
12065
+ close(code = 1e3, reason = "") {
12066
+ this.sendClose(code, reason);
12067
+ }
12068
+ };
12069
+
11670
12070
  // src/kernel/net/ws-syscalls.ts
11671
12071
  var WS_OPS = [
11672
12072
  "net-ws-open",
@@ -11676,7 +12076,7 @@ var WS_OPS = [
11676
12076
  "net-ws-send",
11677
12077
  "net-ws-close"
11678
12078
  ];
11679
- var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
12079
+ var TOKEN4 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
11680
12080
  function message(e) {
11681
12081
  return e instanceof Error ? e.message : String(e);
11682
12082
  }
@@ -11685,16 +12085,18 @@ function errnoOf(e) {
11685
12085
  const code = e?.code;
11686
12086
  return typeof code === "string" && ERRNO.test(code) ? code : "ECONNREFUSED";
11687
12087
  }
11688
- function fail(errno, text) {
11689
- return { ok: false, errno, message: text };
12088
+ function fail(errno2, text) {
12089
+ return { ok: false, errno: errno2, message: text };
11690
12090
  }
11691
12091
  var WsHandles = class {
11692
12092
  transport;
11693
12093
  open = /* @__PURE__ */ new Map();
11694
12094
  next;
11695
- constructor(transport, next2) {
12095
+ local;
12096
+ constructor(transport, next2, local) {
11696
12097
  this.transport = transport;
11697
12098
  this.next = next2;
12099
+ this.local = local;
11698
12100
  }
11699
12101
  owns(handle) {
11700
12102
  return this.open.has(handle);
@@ -11723,16 +12125,18 @@ var WsHandles = class {
11723
12125
  return { ok: true, kind: "json", json: handle };
11724
12126
  }
11725
12127
  async connect(handle, req, entry) {
11726
- const { transport } = this;
11727
- if (!transport.traits.websocket || !transport.websocket) return fail("ENOTSUP", NO_WEBSOCKET2);
12128
+ const { transport, local } = this;
12129
+ const port = local?.port(req.url);
12130
+ const open = { protocols: req.protocols, headers: req.headers, signal: entry.abort.signal };
12131
+ if (port === void 0 && (!transport.traits.websocket || !transport.websocket)) {
12132
+ return fail("ENOTSUP", NO_WEBSOCKET2);
12133
+ }
11728
12134
  let socket;
11729
12135
  try {
11730
- socket = await transport.websocket({
12136
+ socket = port === void 0 ? await transport.websocket({
11731
12137
  url: toHostLoopback(req.url),
11732
- protocols: req.protocols,
11733
- headers: req.headers,
11734
- signal: entry.abort.signal
11735
- });
12138
+ ...open
12139
+ }) : await local.websocket({ url: req.url, ...open }, port);
11736
12140
  } catch (e) {
11737
12141
  return fail(errnoOf(e), message(e));
11738
12142
  }
@@ -11740,7 +12144,7 @@ var WsHandles = class {
11740
12144
  socket.close(1001, "the program went away");
11741
12145
  return fail("EBADF", `no websocket ${handle}`);
11742
12146
  }
11743
- if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN3.test(socket.protocol))) {
12147
+ if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN4.test(socket.protocol))) {
11744
12148
  socket.close(1002, "subprotocol not offered");
11745
12149
  return fail("EPROTO", "the far end chose a subprotocol the program did not offer");
11746
12150
  }
@@ -11803,6 +12207,10 @@ var EMPTY = new Uint8Array(0);
11803
12207
  function message2(e) {
11804
12208
  return e instanceof Error ? e.message : String(e);
11805
12209
  }
12210
+ function codeOf2(e) {
12211
+ const code = e?.code;
12212
+ return typeof code === "string" && /^E[A-Z]+$/.test(code) ? code : "EIO";
12213
+ }
11806
12214
  async function* once(bytes) {
11807
12215
  yield bytes;
11808
12216
  }
@@ -11812,10 +12220,12 @@ var HttpHandles = class {
11812
12220
  nextHandle = 0;
11813
12221
  ws;
11814
12222
  owner;
11815
- constructor(transport, owner) {
12223
+ local;
12224
+ constructor(transport, owner, local) {
11816
12225
  this.transport = transport;
11817
12226
  this.owner = owner;
11818
- this.ws = new WsHandles(transport, () => ++this.nextHandle);
12227
+ this.local = local;
12228
+ this.ws = new WsHandles(transport, () => ++this.nextHandle, local);
11819
12229
  }
11820
12230
  syscall(req) {
11821
12231
  if (req.op.startsWith("net-ws-")) return this.ws.syscall(req);
@@ -11823,10 +12233,13 @@ var HttpHandles = class {
11823
12233
  if (req.op === "net-open") return Promise.resolve(this.opened(req));
11824
12234
  if (req.op === "net-head") return this.head(req.handle);
11825
12235
  if (req.op === "net-read") return this.read(req.handle, req.max);
11826
- if (req.op === "net-traits") return Promise.resolve(this.traits());
12236
+ if (req.op === "net-traits") return Promise.resolve(this.traits(req.url));
11827
12237
  return this.close(req.handle);
11828
12238
  }
11829
- traits() {
12239
+ traits(url) {
12240
+ if (url !== void 0 && this.local?.port(url) !== void 0) {
12241
+ return { ok: true, kind: "json", json: LOOPBACK_TRAITS };
12242
+ }
11830
12243
  const { unavailable, crossOrigin = "any", ...rest2 } = this.transport.traits;
11831
12244
  if (unavailable) return { ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT };
11832
12245
  return { ok: true, kind: "json", json: { ...rest2, crossOrigin } };
@@ -11857,28 +12270,30 @@ var HttpHandles = class {
11857
12270
  return { handle, head: entry.head };
11858
12271
  }
11859
12272
  async respond(handle, req, entry) {
12273
+ const port = this.local?.port(req.url);
11860
12274
  try {
11861
- const response = await this.transport.fetch(
11862
- {
11863
- url: req.url,
11864
- method: req.method,
11865
- headers: req.headers,
11866
- ...req.body ? { body: req.body } : {},
11867
- signal: entry.abort.signal
11868
- },
11869
- this.owner
11870
- );
12275
+ const target = port === void 0 ? toHostLoopback(req.url) : req.url;
12276
+ const request2 = {
12277
+ url: target,
12278
+ method: req.method,
12279
+ headers: req.headers,
12280
+ ...req.body ? { body: req.body } : {},
12281
+ signal: entry.abort.signal
12282
+ };
12283
+ const response = port === void 0 ? await this.transport.fetch(request2, this.owner) : await this.local.fetch(request2, port);
11871
12284
  entry.body = response.body[Symbol.asyncIterator]();
11872
12285
  entry.cancel = () => response.cancel();
11873
12286
  if (!this.open.has(handle)) await entry.cancel().catch(() => void 0);
11874
- const head = this.headOf(handle, response.url ?? req.url, response);
12287
+ const shown = response.url && response.url !== target ? response.url : req.url;
12288
+ const head = this.headOf(handle, shown, response);
11875
12289
  return response.redirected ? { ...head, json: { ...head.json, redirected: true } } : head;
11876
12290
  } catch (e) {
11877
12291
  const text = message2(e);
12292
+ if (port !== void 0) return { ok: false, errno: codeOf2(e), message: text };
11878
12293
  const status = e.status;
11879
12294
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
11880
- const errno = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
11881
- return { ok: false, errno, message: text };
12295
+ const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
12296
+ return { ok: false, errno: errno2, message: text };
11882
12297
  }
11883
12298
  entry.body = once(new TextEncoder().encode(text))[Symbol.asyncIterator]();
11884
12299
  return this.headOf(handle, req.url, {
@@ -13347,6 +13762,7 @@ function aborted(signal) {
13347
13762
  signal.addEventListener("abort", () => reject(new KernelError("EINTR")), { once: true });
13348
13763
  });
13349
13764
  }
13765
+ var PLAIN_FILE_MODE = 420;
13350
13766
  var JOB_OPS = /* @__PURE__ */ new Set([
13351
13767
  "proc-setpgid",
13352
13768
  "proc-getpgid",
@@ -13801,15 +14217,21 @@ var WasmProcess = class {
13801
14217
  }
13802
14218
  if (this.writesAt(req) && this.options.readonlyAt?.(path)) throw new KernelError("EROFS");
13803
14219
  const open = { ...req, path };
14220
+ const plain = this.plainMode(req.mode);
13804
14221
  if (req.exclusive) {
13805
- await nodes.createExclusive(path, this.newOwner());
14222
+ await nodes.createExclusive(path, this.newOwner(req.mode));
14223
+ if (plain !== void 0) await this.options.fs.chmod?.(path, plain);
13806
14224
  this.fds.installAt(fd, this.openVfsFile(open));
13807
14225
  } else if (req.create) {
13808
14226
  await nodes.onEntry(path, async () => {
13809
- const fresh2 = this.options.owners && !await present2(this.options.fs, path, true);
14227
+ const marks = this.options.owners || plain !== void 0;
14228
+ const fresh2 = marks && !await present2(this.options.fs, path, true);
13810
14229
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
13811
14230
  await this.fds.get(fd).file.stat?.();
13812
- if (fresh2) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
14231
+ if (fresh2 && plain !== void 0) await this.options.fs.chmod?.(path, plain);
14232
+ else if (fresh2) {
14233
+ await this.options.fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
14234
+ }
13813
14235
  });
13814
14236
  } else {
13815
14237
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
@@ -13922,9 +14344,14 @@ var WasmProcess = class {
13922
14344
  if (!tty) throw new KernelError("ENOENT");
13923
14345
  return tty;
13924
14346
  }
13925
- newOwner() {
14347
+ plainMode(mode) {
14348
+ if (this.options.owners || mode === void 0) return void 0;
14349
+ const wanted = mode & 4095 & ~this.umask;
14350
+ return wanted === PLAIN_FILE_MODE ? void 0 : wanted;
14351
+ }
14352
+ newOwner(mode = 438) {
13926
14353
  if (!this.options.owners) return void 0;
13927
- return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
14354
+ return { uid: this.cred.euid, gid: this.cred.egid, mode: mode & 4095 & ~this.umask };
13928
14355
  }
13929
14356
  async openProc(req, path) {
13930
14357
  return this.fds.install(await this.procFile(req, path), 3);
@@ -14664,7 +15091,7 @@ function inherited(opts) {
14664
15091
  }
14665
15092
  function holdingToken(opts, holds, caller) {
14666
15093
  return mintSyncFsToken({
14667
- ...opts.owners ? caller : {},
15094
+ ...opts.owners ? caller : { umask: caller.umask },
14668
15095
  ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
14669
15096
  ...opts.links !== void 0 ? { links: opts.links } : {},
14670
15097
  ...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
@@ -15614,7 +16041,7 @@ function scrubberOf(secrets) {
15614
16041
  const usable = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
15615
16042
  return usable.length > 0 ? compile(usable, { idleMs: SCRUB_IDLE_MS }) : void 0;
15616
16043
  }
15617
- async function writeAll2(file, bytes) {
16044
+ async function writeAll3(file, bytes) {
15618
16045
  const write = file.write;
15619
16046
  for (let at = 0; at < bytes.length; ) at += await write.call(file, bytes.subarray(at));
15620
16047
  }
@@ -15648,7 +16075,7 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
15648
16075
  const later = (bytes) => {
15649
16076
  if (bytes.length === 0) return;
15650
16077
  pending += 1;
15651
- tail = tail.then(() => writeAll2(file, bytes)).catch((err) => {
16078
+ tail = tail.then(() => writeAll3(file, bytes)).catch((err) => {
15652
16079
  if (failed3 === void 0) failed3 = err;
15653
16080
  }).finally(() => {
15654
16081
  pending -= 1;
@@ -16279,8 +16706,8 @@ var FSA_CAPABILITIES = {
16279
16706
  ranges: true,
16280
16707
  sessions: true
16281
16708
  };
16282
- var codeOf2 = (err) => err?.code;
16283
- var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
16709
+ var codeOf3 = (err) => err?.code;
16710
+ var denied = (err) => codeOf3(err) === "EACCES" || err?.name === "NotAllowedError";
16284
16711
  function attrOf(st) {
16285
16712
  return {
16286
16713
  kind: st.isDirectory ? "directory" : st.isSymbolicLink ? "symlink" : "file",
@@ -16302,7 +16729,7 @@ function kernelFsHandlers(fs) {
16302
16729
  try {
16303
16730
  return await fs.stat(path);
16304
16731
  } catch (err) {
16305
- if (codeOf2(err) === "ENOENT") return void 0;
16732
+ if (codeOf3(err) === "ENOENT") return void 0;
16306
16733
  throw err;
16307
16734
  }
16308
16735
  }
@@ -16495,7 +16922,7 @@ function parseFstab(text) {
16495
16922
  });
16496
16923
  return lines2;
16497
16924
  }
16498
- function codeOf3(err) {
16925
+ function codeOf4(err) {
16499
16926
  const code = err?.code;
16500
16927
  return typeof code === "string" ? code : "EIO";
16501
16928
  }
@@ -16533,18 +16960,18 @@ async function mountLine({ line, spec, invalid }, mount, signal, retries, discar
16533
16960
  break;
16534
16961
  } catch (err) {
16535
16962
  failed3 = err;
16536
- if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
16963
+ if (FINAL.has(codeOf4(err)) || attempt === retries.length) break;
16537
16964
  await wait(retries[attempt], signal);
16538
16965
  }
16539
16966
  }
16540
- const code = codeOf3(failed3);
16967
+ const code = codeOf4(failed3);
16541
16968
  return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
16542
16969
  }
16543
16970
 
16544
16971
  // src/mount/hostfs.ts
16545
16972
  var HOSTFS_IO = 16 * 1024 * 1024;
16546
- var codeOf4 = (err) => err?.code;
16547
- var unreachable = (err) => codeOf4(err) === "ENOTCONN";
16973
+ var codeOf5 = (err) => err?.code;
16974
+ var unreachable = (err) => codeOf5(err) === "ENOTCONN";
16548
16975
  function rel(path) {
16549
16976
  return path.replace(/^\/+/, "");
16550
16977
  }
@@ -16604,9 +17031,9 @@ var HostfsClient = class {
16604
17031
  throw fsError2("ESTALE", "the folder was granted again, so its open files are gone");
16605
17032
  return this.send(path, init, false, true);
16606
17033
  }
16607
- const errno = response.headers.get("X-Hostfs-Errno") ?? "EIO";
17034
+ const errno2 = response.headers.get("X-Hostfs-Errno") ?? "EIO";
16608
17035
  const body = await response.json().catch(() => ({}));
16609
- throw fsError2(errno, typeof body.message === "string" ? body.message : errno);
17036
+ throw fsError2(errno2, typeof body.message === "string" ? body.message : errno2);
16610
17037
  }
16611
17038
  async call(op, body = {}, handle = false) {
16612
17039
  const response = await this.send(
@@ -16721,7 +17148,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
16721
17148
  try {
16722
17149
  return { opened: await opening2(), restore: void 0 };
16723
17150
  } catch (err) {
16724
- if (!flags2.write || codeOf4(err) !== "EACCES") throw err;
17151
+ if (!flags2.write || codeOf5(err) !== "EACCES") throw err;
16725
17152
  const was = await client.call("stat", { path: rel(path) });
16726
17153
  const mode = was.mode ?? 0;
16727
17154
  if (mode & 128) throw err;
@@ -17634,8 +18061,8 @@ var RULE = new RegExp(
17634
18061
  var TERM = /^[A-Za-z0-9._+-]+$/;
17635
18062
  var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
17636
18063
  var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
17637
- function failed2(errno, error) {
17638
- return { error, errno };
18064
+ function failed2(errno2, error) {
18065
+ return { error, errno: errno2 };
17639
18066
  }
17640
18067
  function unescaped(double) {
17641
18068
  return double.replace(/\\(["\\$`])/g, "$1");
@@ -18929,6 +19356,12 @@ var Launcher = class {
18929
19356
  groupSession(pgid) {
18930
19357
  return this.jobs.sessionOf(pgid);
18931
19358
  }
19359
+ loopbackFor(pid) {
19360
+ return new KernelLoopback(this.net, () => {
19361
+ const cred = this.credOf(pid);
19362
+ return { pid, uid: cred.euid, gid: cred.egid };
19363
+ });
19364
+ }
18932
19365
  treeScrubs(req) {
18933
19366
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
18934
19367
  return req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
@@ -19006,7 +19439,7 @@ var Launcher = class {
19006
19439
  jobs: this.jobs,
19007
19440
  ptys: this.ptys,
19008
19441
  net: this.net,
19009
- http: new HttpHandles(this.transport, pid),
19442
+ http: new HttpHandles(this.transport, pid, this.loopbackFor(pid)),
19010
19443
  locks: this.locks,
19011
19444
  resolver: this.resolver,
19012
19445
  onReap: (child) => this.reaped(child, pid),