@ai-ecoverse/slicc-kernel 1.30.1 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,6 +37,7 @@ const { status, stdout, stderr } = await kernel.run(['bash', '-c', 'echo hi > he
37
37
  | `hostfs` | none | `(source, { readonly }) => Promise<{ url, token, capabilities? }>`: a grant for a `hostfs` mount from the local proxy (see [Host folders](#host-folders-hostfs)) |
38
38
  | `onMountPending` | none | `({ target, source, insert }) => void`: an `fsa` mount needs a folder; call `insert()` from a user gesture |
39
39
  | `processMounts` | `true` | whether programs may call `mount(2)` and `umount2(2)`: `false`, or `(req) => boolean \| Promise<boolean>` to decide each call (see [Mounting from a program](#mounting-from-a-program)) |
40
+ | `hostname` | `'slicc'` | the kernel's node name (see [Node name](#node-name)) |
40
41
  | `cdp` | none | `({ runtime }) => Promise<CdpConnection>`: a browser-level DevTools connection for programs that drive a browser (see [Browser automation](#browser-automation-cdp)) |
41
42
 
42
43
  ### `kernel.run(argv, options?) → Promise<{ status, stdout, stderr }>`
@@ -199,13 +200,22 @@ await kernel.run(['curl', '-sS', 'https://registry.npmjs.org/@ai-ecoverse/wasm-b
199
200
  ```
200
201
 
201
202
  - **Sockets**: `AF_INET` stream sockets on `127.x` and `AF_UNIX` sockets are kernel descriptors on one loopback network per kernel, so processes can serve and connect to each other. They survive `dup`, `fork` and `exec` and work with `select` and `poll`, like pipe ends. A bind of an `AF_UNIX` socket creates its path in OPFS. Other addresses are unreachable (`ENETUNREACH`) unless the [uplink](#uplink) routes them: otherwise programs reach the outside world only through the proxy.
202
- - **The proxy** listens on `127.0.0.1:3128` from the first connection on. It takes absolute-form `http:` requests and `CONNECT` tunnels, which it terminates with a certificate for the requested host, issued by the kernel's own CA, then hands each request to the transport. Programs start with `http_proxy`, `https_proxy` (and the upper-case names) pointing at it, `no_proxy` covering loopback, and `SSL_CERT_FILE`, `CURL_CA_BUNDLE` and `GIT_SSL_CAINFO` pointing at the CA bundle in `/etc/ssl/certs/slicc-kernel-ca.pem`; variables in `env` override them. The CA's key never leaves WebCrypto; it is kept in IndexedDB, in `<metadata>-ca`, or in `slicc-kernel-ca` when `metadata` is the default or `false`, so kernels on one origin share one CA whether or not they keep metadata. `ca` names another database, and `ca: false` keeps a CA in memory for the kernel's lifetime. Each kernel adds its CA to the bundle instead of replacing it, so every kernel on the origin stays trusted; the bundle keeps the newest 16.
203
+ - **The proxy** listens on `127.0.0.1:3128` from the first connection on. It takes absolute-form `http:` requests and `CONNECT` tunnels, which it terminates with a certificate for the requested host, issued by the kernel's own CA, then hands each request to the transport. Programs start with `http_proxy`, `https_proxy` (and the upper-case names) pointing at it, `no_proxy` covering loopback and the [node name](#node-name), and `SSL_CERT_FILE`, `CURL_CA_BUNDLE` and `GIT_SSL_CAINFO` pointing at the CA bundle in `/etc/ssl/certs/slicc-kernel-ca.pem`; variables in `env` override them. The CA's key never leaves WebCrypto; it is kept in IndexedDB, in `<metadata>-ca`, or in `slicc-kernel-ca` when `metadata` is the default or `false`, so kernels on one origin share one CA whether or not they keep metadata. `ca` names another database, and `ca: false` keeps a CA in memory for the kernel's lifetime. Each kernel adds its CA to the bundle instead of replacing it, so every kernel on the origin stays trusted; the bundle keeps the newest 16.
203
204
  - **TLS** needs [`@ai-ecoverse/wasm-tls-engine`](https://www.npmjs.com/package/@ai-ecoverse/wasm-tls-engine) installed under `modules`, like a command. Without it, `CONNECT` is answered `501` with the reason.
204
205
  - **The transport** is an object on the page with `traits` (`manualRedirects`, `encodedBodies`, `maxRequestBody`, and `crossOrigin`: `'cors'` or `'any'`) and `fetch(request)`, which answers `{ status, statusText, headers, body, cancel }` with `body` an async iterable of `Uint8Array`. The kernel pulls the body one chunk at a time, so a slow program slows the download. A rejection with a numeric `status` is answered with that status; any other with `502`. `fetchTransport()` is the plain `fetch` of the page, bound by CORS; its `hint` option is appended to the message of a request it could not make, so a program's 502 can name a way around CORS (`registry.npmjs.org` and jsDelivr allow it); an embedder with a way around CORS passes its own. A response body that delivers no data for `bodyIdleMs` (default 5 minutes; `0` or `Infinity` turns it off) fails with `ETIMEDOUT` instead of waiting forever, which matters because Chrome under heavy load can stop handing a page the rest of a body. A slow body that keeps delivering is never cut off.
205
206
  - **`localProxyTransport({ url, key })`** sends every request to a local proxy on loopback, such as [slicc-node](https://github.com/ai-ecoverse/slicc-node) or slicc-swift, which fetches it without CORS. Redirects reach the program unfollowed, with every `Set-Cookie`; bodies arrive decoded. `probeLocalProxy({ url, key })` resolves with the proxy's capabilities, or `null` when it is gone or refuses the key, so a page can fall back to `fetchTransport()`. `checkLocalProxy({ url, key })` says why: it resolves with `{ state }`, which is `ready` (with `probe`), `blocked` (the user denied Chrome's Local Network Access permission, `loopback-network`), `unreachable` (nothing answered; its `permission` is `granted`, `prompt` or `unknown`, and with `prompt` the page cannot tell a dismissed prompt from a proxy that is not running, since Chrome asks only once it has a connection), `refused` (with `status` and `error`, such as a stale key) or `incompatible`. The permission is not consulted when the page itself is on loopback. Both take a `fetch` option, and the transport `maxRequestBody` (default 64 MiB) and `bodyIdleMs` like `fetchTransport`.
206
207
  - **The host machine** is `host.slicc.internal` (`SLICC_HOST_LOOPBACK`), as `host.docker.internal` is in Docker, while `localhost` and `127.0.0.1` stay the kernel's own loopback. The proxy forwards `http(s)://host.slicc.internal:<port>/…` to the transport as `http(s)://127.0.0.1:<port>/…`, so it reaches the real machine's loopback through the local proxy or Node's `fetch` (through the page's own `fetch`, a request needs CORS from that server and Chrome's Local Network Access permission); other loopback addresses are still refused. This is HTTP and HTTPS through the proxy only: `/etc/hosts` and WASIX name resolution give the name `10.0.2.2`, QEMU's address for its host, so a program that connects a socket to it directly fails with `ENETUNREACH` instead of reaching the kernel's loopback.
207
208
  - **Without a transport**, every request is answered `502` with `slicc-kernel: no network transport (createKernel({ network: { transport } }))`.
208
209
 
210
+ ### Node name
211
+
212
+ The kernel has one node name, `slicc` unless `createKernel({ hostname })` (or `createNodeKernel`) names another; it must be a valid host name, and not `localhost`, a `*.localhost` name, `host.slicc.internal`, `wasmer.sh` or an IPv4 address, or `createKernel` rejects. `no_proxy` lists it (and `emscripten`), so HTTP clients reach the kernel's own servers by it directly.
213
+ - **Where programs see it:** every process gets it in `HOSTNAME`, which the kernel sets on every start whatever the caller's environment says. Emscripten programs also see it in `/etc/hostname` (next to the synthetic `/etc/hosts` and `/etc/passwd`, which only they have; WASI programs resolve names through the kernel instead) and in `Module.sliccKernel.hostname`.
214
+ - **What `uname` says:** `uname -n` and `gethostname()` still report what each program's C library compiled in. That's `emscripten` for Emscripten programs and `wasmer.sh` for WASIX ones; no runtime can change it.
215
+ - **Resolving it:** `/etc/hosts` maps the node name and both of those names to `127.0.1.1` (Debian's convention), and name resolution answers them, and `localhost.localdomain`, itself, as `127.0.1.1` and `::1`. So a program that looks up its own host name, as git does for every commit, never asks the [uplink](#uplink), and `127.0.1.1` is the kernel's loopback.
216
+ - **The trade-off:** a raw connection to the real `wasmer.sh` resolves to loopback too. HTTP(S) to it still goes through the realm proxy and the transport, which resolve names themselves; `wasmer.sh` is deliberately not in `no_proxy`.
217
+ - **A real file wins:** a root that has its own `/etc/hostname` (or `/etc/hosts`, `/etc/passwd`) keeps it, and programs read that file rather than the synthetic one. The kernel's name still drives `HOSTNAME` and name resolution.
218
+
209
219
  ### Uplink
210
220
 
211
221
  `createKernel({ network: { uplink } })` (or `createNodeKernel`) gives the kernel a page-owned way into another network, such as a tailnet. It is the whole kernel's: while the page passes an uplink, every process gets its names. The uplink is an object with:
package/dist/index.js CHANGED
@@ -287,6 +287,15 @@ var HOST_LOOPBACK = "host.slicc.internal";
287
287
  var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
288
288
  var PAGE_LOOPBACK = "kernel.localhost";
289
289
  var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
290
+ var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
291
+ function reserved(name) {
292
+ const lower = name.toLowerCase();
293
+ return lower === "localhost" || lower.endsWith(".localhost") || lower === HOST_LOOPBACK || lower === "wasmer.sh" || /^\d+(\.\d+){3}$/.test(lower);
294
+ }
295
+ function isHostname(name) {
296
+ if (typeof name !== "string" || name.length > 253 || reserved(name)) return false;
297
+ return name.split(".").every((l) => LABEL.test(l));
298
+ }
290
299
 
291
300
  // src/kernel/net/routes.ts
292
301
  var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
@@ -1352,6 +1361,7 @@ function initCall(options, transport) {
1352
1361
  ...uplinkInit(options.network?.uplink),
1353
1362
  ...options.hostfs ? { hostfs: true } : {},
1354
1363
  ...options.cdp ? { cdp: true } : {},
1364
+ ...options.hostname !== void 0 ? { hostname: options.hostname } : {},
1355
1365
  ...options.processMounts !== void 0 ? {
1356
1366
  processMounts: typeof options.processMounts === "function" ? "ask" : options.processMounts
1357
1367
  } : {}
@@ -1377,6 +1387,9 @@ function cdpBridge(hook) {
1377
1387
  }
1378
1388
  async function createKernel(options = {}) {
1379
1389
  if (!globalThis.crossOriginIsolated) throw new Error(ISOLATION);
1390
+ if (options.hostname !== void 0 && !isHostname(options.hostname)) {
1391
+ throw new Error(`not a host name: ${String(options.hostname)}`);
1392
+ }
1380
1393
  const url = options.worker ?? new URL("./kernel-worker.js", import.meta.url);
1381
1394
  const worker = new Worker(url, { type: "module", name: "slicc-kernel" });
1382
1395
  const pending = /* @__PURE__ */ new Map();