@ai-ecoverse/slicc-kernel 1.181.0 → 1.182.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 +8 -6
- package/dist/driver-worker.js +1 -1
- package/dist/driver-worker.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel-worker.js +24 -10
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-driver-worker.js +1 -1
- package/dist/node-driver-worker.js.map +1 -1
- package/dist/node-process-worker.cjs +1 -1
- package/dist/node-process-worker.js +1 -1
- package/dist/node.js +24 -10
- package/dist/node.js.map +2 -2
- package/dist/process-worker.js +1 -1
- package/dist/types/cdp/types.d.ts +2 -0
- package/dist/types/kernel/socket.d.ts +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ TypeScript declarations ship in `dist/types/`, and each entry (`.`, `./node`, `.
|
|
|
46
46
|
| `clipboard` | none | `{ read(stamp) => Promise<string>, write(text, stamp) => Promise<void> }`: the clipboard behind [`/dev/clipboard`](#devclipboard). `createNodeKernel` takes it too |
|
|
47
47
|
| `requests` | none | `{ kinds, handle(request) }`: the page's answers to [`/dev/embedder`](#devembedder) for the processes it starts. `createNodeKernel` takes it too |
|
|
48
48
|
| `approve` | none | `(req, { signal }) => 'once' \| 'always' \| 'deny'` (or a promise of one), or `{ answer: 'once' \| 'always', timestamp: true }` when a person approved: decides a `sudo` request that needs approval (see [Sudo](#sudo) and [Sudo timestamps](#sudo-timestamps)); without it, such a request is denied. `createNodeKernel` takes it too |
|
|
49
|
-
| `cdp` | none | `({ runtime }) => Promise<CdpConnection>`: a browser-level DevTools connection for programs that drive a browser (see [Browser automation](#browser-automation-cdp)) |
|
|
49
|
+
| `cdp` | none | `({ runtime, stamp }) => Promise<CdpConnection>`: a browser-level DevTools connection for programs that drive a browser (see [Browser automation](#browser-automation-cdp)) |
|
|
50
50
|
| `facades` | none | `{ link: { port, hook? } }`: kernel-only loopback ports whose connections a hook on the page serves, with the connecting program's identity (see [Page facades](#page-facades)) |
|
|
51
51
|
| `protect` | none | `{ paths, writers? }`: directories no process may change, root included, in every `users` mode, except processes with one of the `writers` tags (see [Protected paths](#protected-paths)). `createNodeKernel` takes it too |
|
|
52
52
|
|
|
@@ -189,7 +189,7 @@ Attaches to a kernel over a port from `kernel.connect()`, in any realm: a worker
|
|
|
189
189
|
| `fs` | `readFile`, `readText`, `writeFile(path, string \| bytes)`, `stat`, `lstat`, `readdir`, `mkdir` (with parents), `rm(path, { force })` (recursive), `rename`, `realpath`, `symlink(target, path)`, `readlink`, `exists`, on the processes' file system, mounts included (a path under a `tmpfs`, `fsa` or `hostfs` mount reads and writes the mounted file system, as `cat` in a process would); failures reject with `KernelCallError` and a POSIX `code` |
|
|
190
190
|
| `fetch({ url, method, headers, body, redirect, signal })` | through the kernel's network transport (the one the page passed to `createKernel`), with a streaming body; `redirect` is the per-request policy (see [Network](#network)); `transport` is the same as a `NetworkTransport` |
|
|
191
191
|
| `fs.watch(paths, { recursive }, onChange)` | resolves with `{ close() }`; `onChange` gets `{ paths }`, the changed paths at, below (or, without `recursive`, directly in) the watched ones, batched per task, or `{ overflow: true }` when there were too many to list, to rescan; it covers every change made through this kernel, by its processes and its clients, and a removal or rename that failed or found nothing to do is none. It also covers what another kernel on the same OPFS root changes (another tab, a worker page) and what a writer outside the kernel reports with [`postFsNotice`](#metadata), as those notices name them: a directory another kernel creates, removes or renames, the file it writes back, and the records of its symlinks, modes, owners and times, and a notice's `trees` as their roots. A peer's notices come only once the kernels share the root (`/proc/sys/kernel/slicc_shared_root` reads `1` from the join handshake on) and only with the shared metadata (not with `metadata: false`), and a writer outside the kernel that posts no notice is never heard. So a client that has to catch a page writing OPFS directly needs that page to call `postFsNotice` after each write. Kernels before this one didn't pass peer and outside changes to a client's watch; they reached only `ctx.fs.watch`. A watch is on names, not files: a watched path need not exist, nor its directories, and one under a regular file is a watch too (every kernel with `watch` does this, protocol 1.1 on), so its creation, by any `mkdir -p` above it, a write or a rename to it, is reported as its path, and so are a removal or rename of it or of a directory above it; the watch goes on hearing the name after that. So a client that waits for a directory to appear watches it, rather than polling `stat` and `readdir`. Under `users`, a watched path the client may not read is `EACCES`, and so is a missing one under a directory the client may not search. Changes are reported by the name they were made under (a file written through a symlink also by its real path), so a client that watches a path under a symlink watches its real path too, for a missing path the real path of its nearest existing directory with the rest of the path after it |
|
|
192
|
-
| `serveCdp(hook, { runtime })` | offers this client's browser to the kernel's programs (protocol 1.5): `hook` is the `cdp` hook of `createKernel`, asked once per program connection; resolves with `{ close() }`, and closing or detaching takes the offer back and closes its connections (see [Browser automation](#browser-automation-cdp)) |
|
|
192
|
+
| `serveCdp(hook, { runtime })` | offers this client's browser to the kernel's programs (protocol 1.5): `hook` is the `cdp` hook of `createKernel`, asked once per program connection with its `runtime` and the connecting process's `stamp` (protocol 1.17); resolves with `{ close() }`, and closing or detaching takes the offer back and closes its connections (see [Browser automation](#browser-automation-cdp)) |
|
|
193
193
|
| `serveFacade(name, hook) → { close() }` | serves a facade that `createKernel({ facades })` declared, the same way as its `hook` (protocol 1.11); the latest registration wins, over the embedder's hook too, and `close()` hands it back. An undeclared name is `EINVAL` |
|
|
194
194
|
| `trace(tag, onEvents, { limit }?)` | reports, as events, every program that a process carrying `tag` runs and every such process's end, with nothing missed (protocol 1.15; against an older kernel the client rejects with `code: 'ENOSYS'`); resolves with `{ close() }` (see [below](#clienttracetag-onevents--limit---promise-close-)) |
|
|
195
195
|
| `sudoRun(request, { stdout, stderr, tag }?)` | runs a `sudo` request again, as `kernel.sudoRun` does, and resolves with `{ pid, pgid, ended }` once it has started (protocol 1.16; against an older kernel the client rejects with `code: 'ENOSYS'` before sending anything). Only a root client may: a client bound to a user rejects with `code: 'EPERM'` (`sudo: only a root client may run a sudo request`). `ended` is tied to the client, as `spawn`'s is, while the process is not (see [Running a request later](#running-a-request-later-sudorun)) |
|
|
@@ -198,7 +198,7 @@ Attaches to a kernel over a port from `kernel.connect()`, in any realm: a worker
|
|
|
198
198
|
| `closed` | resolves with the error that ended the client |
|
|
199
199
|
| `users` | the kernel's `users` mode, `'off'`, `'report'` or `'enforce'`; `'off'` from kernels that don't say |
|
|
200
200
|
|
|
201
|
-
When the kernel goes away (the page closed or reloaded, `terminate()`), pending calls and `exited` reject with `KernelGoneError`, and so do later calls. Each side holds a Web Lock and waits on the other's, since a `MessagePort` reports no close in browsers; in Node, the port's `close` event does the same. The first message is a handshake on the protocol version, `1.
|
|
201
|
+
When the kernel goes away (the page closed or reloaded, `terminate()`), pending calls and `exited` reject with `KernelGoneError`, and so do later calls. Each side holds a Web Lock and waits on the other's, since a `MessagePort` reports no close in browsers; in Node, the port's `close` event does the same. The first message is a handshake on the protocol version, `1.17` (`1.0` had no `watch`, `1.1` no mounts, `1.2` no `spawn` into a group, `1.3` no `dial` and `1.4` no `serveCdp`, which reject with `code: 'ENOSYS'` on such a kernel; a `1.5` kernel has no [uplink](#uplink), a `1.6` kernel resolves names through one but connects nothing to it, a `1.7` kernel has no users, a `1.8` kernel has no `sudo` and no tags, a `1.9` kernel never announces new transport traits, a `1.11` kernel has no per-spawn `tag`, a `1.12` kernel takes no `requests` at attach, a `1.13` kernel has no `cwdMissing`, a `1.14` kernel has no `trace` and takes one tag per spawn, not a list, a `1.15` kernel has no `sudoRun`, which the client rejects with `code: 'ENOSYS'` before sending anything, and a `1.16` kernel asks a `serveCdp` hook with no `stamp`): a client or kernel of another major version is refused with an error naming both.
|
|
202
202
|
|
|
203
203
|
#### `client.trace(tag, onEvents, { limit }?) → Promise<{ close() }>`
|
|
204
204
|
|
|
@@ -790,9 +790,11 @@ interface CdpConnection {
|
|
|
790
790
|
onclose: ((reason?: string) => void) | null;
|
|
791
791
|
close(): void;
|
|
792
792
|
}
|
|
793
|
-
type CdpHook = (request: { runtime?: string }) => Promise<CdpConnection>;
|
|
793
|
+
type CdpHook = (request: { runtime?: string; stamp?: ConnectorStamp }) => Promise<CdpConnection>;
|
|
794
794
|
```
|
|
795
795
|
|
|
796
|
+
- **The stamp** is the connecting process's identity, the same [`ConnectorStamp`](#page-facades) a facade's hook gets: `{ pid, ppid, uid, gid, user?, groups, users, tag?, tags? }`. The kernel takes it at `connect()` from the process's credentials and tags, never from anything the program sends or sets (its URL, headers, environment or arguments), so a program cannot forge it. `tag` is the first tag, and `tags` lists them all when there are more than one, so a host that treats a run's processes differently (slicc-bios drops the `Slicc.action` intent of a process tagged `slicc-taint:<id>`) checks every one of `tags ?? [tag]`. Every hook call for a program's connection to `9222` (`/json/list` and the WebSocket alike) has it, a copy of its own, for the embedder's `cdp` hook and a client's `serveCdp` hook. A kernel from before the field gives no `stamp` (to a client's hook: a kernel before protocol 1.17), so a host should treat a request without one as untrusted. As with a facade, a socket handed to another process keeps the stamp of the process that connected.
|
|
797
|
+
|
|
796
798
|
A connection is one browser-level CDP session; a host with only per-tab debugging emulates the `Target` domain behind it. A connection asking for a `runtime` goes to the newest client that offered that name; any other goes to the `cdp` option, or else to the newest client offer. In the browser the hook runs on the page, with a `MessagePort` per connection to the kernel worker.
|
|
797
799
|
|
|
798
800
|
Process workers can also use the transport directly, without a socket: the `net-request`, `net-read` and `net-close` syscalls (`Module.sliccKernel.http` in a process) open a request and read its body in pieces. `net-open` starts a request and answers its handle at once, and `net-head` waits for its response head, so `net-close` can abort a request whose head has not come yet. Requests are per process and closed when it exits, and a request body has no size limit of its own (the transport's `maxRequestBody` applies to the proxy). `net-open` with `stream: true` instead of `body` (only on a transport with `streamingBodies`, else `ENOTSUP`) takes the body in pieces: `net-write { handle, bytes }` returns once the transport has taken the piece (`EPIPE` once the request is closed or failed), and `net-end { handle }` ends it. JS programs' `fetch` uses it for stream bodies. `net-traits` answers the transport's `traits` with `crossOrigin`: `'cors'` when it is bound by CORS like `fetchTransport()`, `'any'` (the default for a transport that does not say) when it is not; without a transport it fails with `ENETUNREACH`.
|
|
@@ -813,12 +815,12 @@ interface FacadeConnection {
|
|
|
813
815
|
close(reason?: string): void;
|
|
814
816
|
}
|
|
815
817
|
|
|
816
|
-
interface ConnectorStamp { pid: number; ppid: number; uid: number; gid: number; user?: string; groups: number[]; users: 'off' | 'report' | 'enforce'; tag?: string }
|
|
818
|
+
interface ConnectorStamp { pid: number; ppid: number; uid: number; gid: number; user?: string; groups: number[]; users: 'off' | 'report' | 'enforce'; tag?: string; tags?: string[] }
|
|
817
819
|
```
|
|
818
820
|
|
|
819
821
|
- **Declared at boot:** `facades` names each facade (`[a-z][a-z0-9-]*`) and its port, from 1 to 65535. A port may not be 9222 (the CDP facade), 3128 (the realm proxy) or another facade's; anything else throws a `TypeError`. `createNodeKernel` takes `facades` too. The hook may be left out and supplied later by an attached client's `client.serveFacade(name, hook)`, which works only for a declared name.
|
|
820
822
|
- **Programs** find each facade in their environment as `SLICC_<NAME>_URL`, upper-cased with `-` as `_`, e.g. `SLICC_LINK_URL=tcp://127.0.0.1:7400`, and connect to it with an ordinary TCP `connect()`: a JS program's `ctx.net.connect`, an Emscripten program's socket, or a WASIX program's `sock_connect`. Every ABI ends in the same kernel call.
|
|
821
|
-
- **The stamp** is the connecting process's identity, taken by the kernel at `connect()` from its credentials, as Linux's `SO_PEERCRED` is: its pid and parent pid as `ps` shows them, effective uid and gid, user name, groups, the `users` mode and the process's tag (see `kernel.run`), the same fields as a `/dev/sealed` request's `caller`. A program cannot forge it. The hook gets it and `{ name }`, so one function can serve several facades, and decides: it resolves with a connection, whose bytes then pass both ways until either side closes, or it rejects, and the program's connection is closed. A connection while no hook is attached is closed at once, the same as a refusal. Bytes from the hook wait in the kernel until the program reads them, up to 16 MiB per connection: past that, the connection is closed and the hook's `close(reason)` gets `slicc-kernel: more than 16777216 bytes are waiting for the program to read`. A program that connects to anything else pays nothing for this: the stamp is built only for a facade's port, and for the realm proxy's when the kernel has a fetch hook (see **The fetch hook** under [`/dev/sealed`](#devsealed)).
|
|
823
|
+
- **The stamp** is the connecting process's identity, taken by the kernel at `connect()` from its credentials, as Linux's `SO_PEERCRED` is: its pid and parent pid as `ps` shows them, effective uid and gid, user name, groups, the `users` mode and the process's tag (see `kernel.run`), the same fields as a `/dev/sealed` request's `caller`. A program cannot forge it. The hook gets it and `{ name }`, so one function can serve several facades, and decides: it resolves with a connection, whose bytes then pass both ways until either side closes, or it rejects, and the program's connection is closed. A connection while no hook is attached is closed at once, the same as a refusal. Bytes from the hook wait in the kernel until the program reads them, up to 16 MiB per connection: past that, the connection is closed and the hook's `close(reason)` gets `slicc-kernel: more than 16777216 bytes are waiting for the program to read`. A program that connects to anything else pays nothing for this: the stamp is built only for a facade's port, the [CDP facade](#browser-automation-cdp)'s, and for the realm proxy's when the kernel has a fetch hook (see **The fetch hook** under [`/dev/sealed`](#devsealed)).
|
|
822
824
|
- **Handing a connection on hands over the identity:** a facade connection that a process passes to another (a fork, an exec, an inherited descriptor) still speaks as the process that connected, as on Linux. So a privileged process should not pass one to a less trusted one.
|
|
823
825
|
- **Reserved:** a facade's port is reserved from the start. A program's `bind()` to it fails with `EADDRINUSE`, so no program can squat it and impersonate the page, and ephemeral ports skip it. Port 9222 is reserved the same way. Like 9222, a facade's port is kernel-only: the page's `kernel.dial`, a client's `dial` and `loopbackFetch` (and so `<port>.kernel.localhost`) are refused with `ECONNREFUSED`.
|
|
824
826
|
|
package/dist/driver-worker.js
CHANGED
|
@@ -824,7 +824,7 @@ async function loopbackFetch(dial, input, options) {
|
|
|
824
824
|
}
|
|
825
825
|
|
|
826
826
|
// src/client/protocol.ts
|
|
827
|
-
var PROTOCOL = [1,
|
|
827
|
+
var PROTOCOL = [1, 17];
|
|
828
828
|
function locksOf(scope = globalThis) {
|
|
829
829
|
const locks = scope.navigator?.locks;
|
|
830
830
|
return typeof locks?.request === "function" ? locks : void 0;
|