@ai-ecoverse/slicc-kernel 1.61.2 → 1.62.1
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 +20 -6
- package/dist/index.js +12 -9
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +691 -263
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +72 -19
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +786 -358
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +72 -19
- package/dist/process-worker.js.map +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -38,7 +38,8 @@ const { status, stdout, stderr } = await kernel.run(['bash', '-c', 'echo hi > he
|
|
|
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
40
|
| `hostname` | `'slicc'` | the kernel's node name (see [Node name](#node-name)) |
|
|
41
|
-
| `users` | `'off'` | `'off'`, `'report'` or `'enforce'`: whether files have owners (see [File owners](#file-owners)). `'off'` behaves exactly as kernels before owners did; any other value rejects. `createNodeKernel` takes it too |
|
|
41
|
+
| `users` | `'off'` | `'off'`, `'report'` or `'enforce'`: whether files have owners and their modes are checked (see [File owners](#file-owners) and [Permissions](#permissions)). `'off'` behaves exactly as kernels before owners did; any other value rejects. `createNodeKernel` takes it too |
|
|
42
|
+
| `onDenial` | none | `({ pid, uid, op, path, errno }) => void`: called for every permission check that fails, under `'report'` and `'enforce'` (see [Permissions](#permissions)). `createNodeKernel` takes it too |
|
|
42
43
|
| `cdp` | none | `({ runtime }) => Promise<CdpConnection>`: a browser-level DevTools connection for programs that drive a browser (see [Browser automation](#browser-automation-cdp)) |
|
|
43
44
|
|
|
44
45
|
### `kernel.run(argv, options?) → Promise<{ status, stdout, stderr }>`
|
|
@@ -160,7 +161,7 @@ const { status, stdout } = await kernel.run(['bash', '-c', 'echo hi'], { cwd: '/
|
|
|
160
161
|
kernel.terminate();
|
|
161
162
|
```
|
|
162
163
|
|
|
163
|
-
`createNodeKernel({ root, modules, env, network, worker, processMounts, fstabRetries, cdp, hostfs, hostfsOrigin, hostfsFetch })` takes the options of `createKernel` except `metadata`, and `fstabRetries`, the delays in ms between tries of a failing `/etc/fstab` line (default `[1000, 4000, 16000]`). `root` is an in-memory directory by default (`memoryRoot()` makes another), which keeps each file in 1 MiB chunks, and POSIX metadata stays in memory. Processes and threads run on `worker_threads`. The kernel has `run`, `openTerminal`, `users`, `setRoutes` and `terminate` as above, plus `root`, `writeFile(path, data)` (creating the parent directories) and `readFile(path)` to put files in place and read results; like the `fs` of an attached client, they go through the mounts, as the processes see them. `nodeTransport()` is `fetchTransport()` with Node's `fetch`, which no CORS binds (`crossOrigin: 'any'`). `connect({ user })` and `attachKernel` work as in the browser: the port is a `worker_threads` `MessagePort`, which a worker thread can attach with, and `terminate()` ends every attached client.
|
|
164
|
+
`createNodeKernel({ root, modules, env, network, worker, processMounts, fstabRetries, cdp, hostfs, hostfsOrigin, hostfsFetch, users, onDenial })` takes the options of `createKernel` except `metadata`, and `fstabRetries`, the delays in ms between tries of a failing `/etc/fstab` line (default `[1000, 4000, 16000]`). `root` is an in-memory directory by default (`memoryRoot()` makes another), which keeps each file in 1 MiB chunks, and POSIX metadata stays in memory. Processes and threads run on `worker_threads`. The kernel has `run`, `openTerminal`, `users`, `setRoutes` and `terminate` as above, plus `root`, `writeFile(path, data)` (creating the parent directories) and `readFile(path)` to put files in place and read results; like the `fs` of an attached client, they go through the mounts, as the processes see them. `nodeTransport()` is `fetchTransport()` with Node's `fetch`, which no CORS binds (`crossOrigin: 'any'`). `connect({ user })` and `attachKernel` work as in the browser: the port is a `worker_threads` `MessagePort`, which a worker thread can attach with, and `terminate()` ends every attached client.
|
|
164
165
|
|
|
165
166
|
## Commands
|
|
166
167
|
|
|
@@ -209,7 +210,7 @@ A descriptor a program inherits at one of those numbers (from bash's `3<file`, o
|
|
|
209
210
|
|
|
210
211
|
On top of preview1 a WASI program gets:
|
|
211
212
|
|
|
212
|
-
- **Threads**: `wasi.thread-spawn` (wasm32-wasip1-threads) and WASIX `thread_spawn_v2` start a worker per thread on the process's shared memory, sized from the module's own import (a 2 GiB maximum when the engine cannot reserve more). The threads share one descriptor table, and a file written, created, renamed or removed in one thread shows its new size and metadata to `stat` in every other thread at once, under any of its names (a symlink, a hard link, a new name after a rename in the same process); another process's changes show at close and after a `spawn` or `wait`, as with close-to-open caching, so a file another process renames while this one holds it open may show its old size under the new name until then; `exit` or a trap in any thread ends the process. At most 64 threads per process; `SLICC_WASM_THREADS` sets another cap, up to 256.
|
|
213
|
+
- **Threads**: `wasi.thread-spawn` (wasm32-wasip1-threads) and WASIX `thread_spawn_v2` start a worker per thread on the process's shared memory, sized from the module's own import (a 2 GiB maximum when the engine cannot reserve more). The threads share one descriptor table, and a file written, created, renamed or removed in one thread shows its new size and metadata to `stat` in every other thread at once, under any of its names (a symlink, a hard link, a new name after a rename in the same process), also when another thread renames a file over that name meanwhile, since the kernel reports which file an open or a change reached; this holds among the kernel's own processes, not against other tabs, `hostfs` writers or programs that aren't WASI; another process's changes show at close and after a `spawn` or `wait`, as with close-to-open caching, so a file another process renames while this one holds it open may show its old size under the new name until then; `exit` or a trap in any thread ends the process. At most 64 threads per process; `SLICC_WASM_THREADS` sets another cap, up to 256.
|
|
213
214
|
- **WASIX** (a module importing `wasix_32v1`): fork and setjmp/longjmp through Asyncify (the module is built with `wasm-opt --asyncify`), exec, `posix_spawn`, `waitpid` (with `WNOHANG`, and `WUNTRACED` for a child that stops; WASIX has no `WCONTINUED`), pipes, `dup2`, signal handlers and interval timers, terminal modes, and dynamic linking: a position-independent main module loads side modules with `dlopen` from `LD_LIBRARY_PATH`, its runtime path, `/lib`, `/usr/lib` and `/usr/local/lib`. Each thread has its own copy of the function table, so the kernel hands out its slots (a side module's range, a `dlsym`'d function, a closure): threads loading or looking up at the same time never get the same slot. As in glibc, one `dlopen` at a time runs in a process, constructors included: a `dlopen` from a library's constructor goes ahead in its own thread, and another thread's `dlopen` waits, then finds the library loaded and gets the same handle. A thread waiting there still ends or stops with its process, but a signal handler it has runs once that `dlopen` returns. The lock is let go when a `dlopen` fails (an exception, a trap or `pthread_exit` out of a constructor included) and when its thread ends; a forked child starts with it free. In such a program, `dlopen(NULL)`, and `dlopen` of the program's own file (also by a path that resolves to it through symlinks), give a handle for the program that is already running (no second copy is loaded); a program that isn't position-independent gets an error from `dlopen(NULL)`, as from any `dlopen`; `dlsym` on it searches the program, then the loaded libraries, and `dlclose` of it does nothing. A function already in the table (the program's own, or a library's) keeps its slot, so `dlsym(handle, "strlen") == strlen`.
|
|
214
215
|
- **Foreign calls** (libffi's WASIX backend, so Python's `ctypes`): `call_dynamic` calls a function through the program's indirect function table (a static module's exported `__indirect_function_table`, or the dynamic linker's), its arguments and results packed in memory one after another (`i32`/`f32` 4 bytes, `i64`/`f64` 8). With `strict` set, the buffers must fit the function exactly (else `EINVAL`, before the function is called); without it, missing arguments are zero (one cut short keeps the bytes given, with zeros above them) and results that do not fit are left out. A trap or exception of the callee goes through to the caller. JS cannot ask a function for its type, so the kernel reads the types from the module files (the program's and each side module's, once, when a program first calls through `call_dynamic`): a table slot that is empty, past the end, holds a function of no module the kernel knows of, or takes or returns a `v128`, gives `EINVAL`. For closures (callbacks such as `CFUNCTYPE`), a dynamically linked program gets table slots from `closure_allocate`, and `closure_free` gives them back for reuse and empties the slot in every thread; a static program gets `ENOTSUP`. `closure_prepare` puts a trampoline with the requested signature (any of `i32`, `i64`, `f32`, `f64`, `v128`) into such a slot, in every thread: each call stores its arguments below the stack pointer and calls the backing function as `backing(values, results, user_data)`, then returns what it wrote to `results`. The trampolines are small wasm modules made and compiled at run time, once per signature in each thread (an instance per closure), so a page whose Content Security Policy forbids compiling WebAssembly (no `'wasm-unsafe-eval'`) cannot run them; it cannot run the program either. A `fork` or a `longjmp` (both through Asyncify) inside a function reached through `call_dynamic` cannot unwind through it.
|
|
215
216
|
- **Job control:** `^Z`, `SIGTSTP`, `SIGTTIN` and `SIGTTOU` stop a WASIX program at once, unless it has a handler for them. A stopped program that is busy computing is stopped for job control (`jobs` shows it, `fg`, `bg` and `SIGCONT` work), but its worker keeps a core busy until its next system call, since a worker's running code cannot be frozen from outside. Agents should `kill` such a job rather than leave it stopped. For the same reason, a signal with a handler or a terminating default (`^C`, `SIGTERM`) reaches a program that is busy computing only at its next system call, so a CPU-bound loop may not end on `^C`; `kill -9` always ends it. A pending signal is delivered at the program's next WASI or WASIX call, including the calls its worker answers without the kernel (`random_get`, `args_get`, …), except the exempt ones: `clock_time_get` (a tight loop of it measured up to 30% slower), `proc_exit`, `proc_exit2`, `thread_exit`, `thread_spawn_v2` and `wasi.thread-spawn` (wasi-threads), the calls that install handlers (`callback_signal`, `slicc.sigaction_set`), and calls made while Asyncify unwinds or rewinds the stack for `fork` or `longjmp`. A thread blocked in the kernel or waiting on a lock (`pthread_cond_wait`, a futex) is woken by a signal for it (a futex wait checks for signals every 50 ms, so each waiting thread wakes briefly that often), and `pthread_kill` runs the handler on the thread it names. A sleep, or a clock in `poll_oneoff`, never ends before the time asked (it can end later, never earlier). A signal that ends a sleep or a `poll`/`select` with a timeout makes the call fail with `EINTR` in a program whose libc says it is wasix-sysroot 2025.9.30-22 or later (its `slicc.libc` custom section). Older libcs turn that answer into `ENOTSUP` from `nanosleep` (up to -18), so for a program without the section the wait ends as if it had timed out, unless it imports `fd_fdflags_set` (then `EINTR`, as before).
|
|
@@ -228,7 +229,8 @@ A WASI command can name an ES module of its package that provides imports of its
|
|
|
228
229
|
- `spawn({ argv, env, cwd, stdin, stdout, stderr })` starts a command (resolved like `kernel.run`) as a child and returns `{ pid, stdin, stdout, stderr }`. `env` is the child's whole environment (the program's own by default), `cwd` the program's by default. Each stdio is `'inherit'` (the default: the program's own), `'null'`, or `'pipe'`: a kernel pipe whose end the result holds as a descriptor, read and written with `fd-read` (`{ op: 'fd-read', fd, max }`, an empty read at the end), `fd-write` (`{ op: 'fd-write', fd, body }`) and `fd-close`, through `syscall` or `async.submit`; the child's ends are closed in the program. A command that does not exist throws `ENOENT`;
|
|
229
230
|
- `wait(pid)` blocks until a child ends and returns `{ status }`, or `{ status: 128 + signal, signal }` when a signal ended it; `kill(pid, sig)` signals it;
|
|
230
231
|
- `async`: operations of the process that any of its threads can wait for: `submit(req)` runs a kernel syscall without blocking the caller, `resolve(value)` completes at once, `hold()` never completes, and each returns an id; `wait(timeoutMs?)` returns the id of a completed one (0 after the timeout, -1 once `close()` was called), `take(id)` its `{ value }` or `{ error }` (`undefined` while it is pending), and `cancel(id)` drops one;
|
|
231
|
-
- `errno(err)`: the WASI errno of an error
|
|
232
|
+
- `errno(err)`: the WASI errno of an error;
|
|
233
|
+
- `uid` and `gid`, with `users: 'report'` or `'enforce'`: the process's effective uid and gid, read again at each use, so they follow a `setresuid`. `fstat` then shows a file's real owner, and the process's ids for a descriptor with no path. With `users: 'off'`, `ctx` has no `uid` or `gid`, and `fstat` answers 1000 for both, as before.
|
|
232
234
|
|
|
233
235
|
### JS programs
|
|
234
236
|
|
|
@@ -520,7 +522,7 @@ Lowering a soft resource limit (`ulimit -S -n 64`) still fails with `EPERM` in E
|
|
|
520
522
|
|
|
521
523
|
### Users
|
|
522
524
|
|
|
523
|
-
Every process has credentials in the kernel, as on Linux: real, effective and saved user and group ids, and supplementary groups. A process gets them from the user it was started as (`run`, `openTerminal`, a client's `spawn`, or the client's own user; root by default) and keeps its parent's across fork, spawn and exec. Setuid and setgid bits are never honoured on exec. Files may have owners (see [File owners](#file-owners))
|
|
525
|
+
Every process has credentials in the kernel, as on Linux: real, effective and saved user and group ids, and supplementary groups. A process gets them from the user it was started as (`run`, `openTerminal`, a client's `spawn`, or the client's own user; root by default) and keeps its parent's across fork, spawn and exec. Setuid and setgid bits are never honoured on exec. Files may have owners (see [File owners](#file-owners)), checked against the credentials with `users: 'enforce'` (see [Permissions](#permissions)); the credentials also decide who may signal whom, and what `/proc` and `ps` show.
|
|
524
526
|
|
|
525
527
|
- **The database:** `/etc/passwd` and `/etc/group` are real files. On first start the kernel writes them when they're missing, with `root` (uid 0, home `/root`, created `0700`) and `nobody` (65534), and the groups `root`, `sudo` (27), `users` (100) and `nogroup` (65534); files already there are kept as they are. The kernel reads them for every lookup, so an edit counts at once. [`kernel.users`](#kernelusers) adds and removes users.
|
|
526
528
|
- **Environment:** a process started as a user gets `HOME`, `USER`, `LOGNAME`, `PNPM_HOME`, `NPM_CONFIG_PREFIX` and `PATH`. A user's `PNPM_HOME` is `~/.local/share/pnpm` and its npm prefix `~/.local`; root's are `/usr/local/share/pnpm` (or the embedder's `env.PNPM_HOME`) and `/usr/local`. `PATH` is `$PNPM_HOME/bin:/usr/local/bin:/usr/bin:/bin`.
|
|
@@ -536,7 +538,7 @@ Programs ask the kernel for their ids:
|
|
|
536
538
|
|
|
537
539
|
### File owners
|
|
538
540
|
|
|
539
|
-
With `createKernel({ users: 'report' })` or `'enforce'`, files have owners
|
|
541
|
+
With `createKernel({ users: 'report' })` or `'enforce'`, files have owners, and [Permissions](#permissions) checks them against a process's credentials. With `'off'`, the default, nothing below happens: the kernel stores no owner, and every file looks owned by the caller as described under [Metadata](#metadata).
|
|
540
542
|
|
|
541
543
|
- **Where owners live:** on the root file system, in the sidecar record's `uid` and `gid` (see [Metadata](#metadata)). A path with no owner of its own has the owner of its nearest ancestor that has one, and `/` belongs to root, so a root written before owners existed, or by the BIOS through the OPFS API, is root's throughout. A directory whose owner changes keeps the owner its unowned contents had, in `heir`, so `chown cone /old` gives `cone` the directory and leaves the files below it to their old owner. A rename keeps the owner of what it moves.
|
|
542
544
|
- **New files:** a file, directory or symlink a process creates belongs to its effective uid, and to its effective gid, or to the directory's group when the directory has the setgid bit; a new directory in a setgid directory gets the setgid bit too. Every file and directory a process creates gets the mode it asked for (`0666` and `0777` when it can't ask, as a JS program's `ctx.fs` can't) less the process's umask, whichever runtime made it; an Emscripten program's file no longer gets `0644` whatever it asked for. A file written through a client's `fs` belongs to the client's user (root for a page) and gets `0666`, a directory `0777`, less the umask `kernel.users.add` gave that user (`022` for root and by default); the kernel's own files, such as `/etc/passwd`, have no owner of their own.
|
|
@@ -545,6 +547,18 @@ With `createKernel({ users: 'report' })` or `'enforce'`, files have owners. Noth
|
|
|
545
547
|
- **What programs see:** `stat` shows the owners to Emscripten programs, whatever their libc reports for their own ids, so a program built before H1 sees real owners too, and its owner checks can fail. WASI programs read them with `slicc_fs.fd_owner` and `path_owner`. The commands in `/usr/bin` and `/bin` are root's, and a user's own commands in `~/.local/share/pnpm/bin` belong to the owner of the closest real directory above them, normally the user.
|
|
546
548
|
- **Mounts:** the options `uid=` and `gid=` (decimal ids) give a mount its owner; without them it belongs to whoever mounted it: root for `kernel.mount`, `/etc/fstab` and a client, the process's effective ids for `mount(2)`, which then shows them in its options. A mount whose driver keeps owners (`tmpfs`) starts with its root owned so, and its files get owners as on the root file system, `chown` included. On `hostfs`, `fsa` and any driver without `owners`, every file shows as the mount's owner and `chown` fails with `EPERM`, root's too, as on a FAT drive. An embedder that mounts a folder for a user (bios's linked and picked folders) passes that user's `uid` and `gid`, or the user's programs see root's files there, and git calls the repository's ownership dubious.
|
|
547
549
|
|
|
550
|
+
### Permissions
|
|
551
|
+
|
|
552
|
+
With `users: 'enforce'` the kernel checks every file call that reaches it against the caller's effective uid, effective gid and supplementary groups, as Linux does: the owner bits for the file's owner, else the group bits for a member of its group, else the other bits. Root (effective uid 0) passes every check except executing a file with no `x` bit at all. A failed check is `EACCES`; changing what only the owner may change is `EPERM`. With `users: 'report'` the kernel makes the same checks and refuses nothing: it logs each denial once per user, operation, errno and path, with `console.warn`, up to 10,000 of them, then says that more were suppressed. With both, `onDenial` gets each denial as `{ pid, uid, op, path, errno }` (`pid` 0 for a client's `fs`); the shape is stable. With `users: 'off'` nothing is checked and no call is added.
|
|
553
|
+
|
|
554
|
+
- **Paths:** a path needs `x` on every directory it goes through, the targets of symlinks on the way included, resolved as the file system resolves them (a `..` after a symlinked directory goes to the target's parent). `stat`, `lstat`, `readlink` and `chown` need only that; `exists` answers `false` where it's refused. Listing a directory needs `r` and `x` on it.
|
|
555
|
+
- **Creating and removing:** `mkdir`, creating a file and `symlink` need `w` and `x` on the directory; `mkdir -p` needs them on the closest directory that exists, and nothing for one that exists already. Unlinking, `rmdir` and `rename` need `w` and `x` on both directories, and a directory moved to another needs `w` on itself. A recursive remove (`ctx.fs.rm`, a client's `rm`) needs `r`, `w` and `x` on every directory in the tree.
|
|
556
|
+
- **The sticky bit:** in a directory with mode `1000` set, such as `/tmp` at `1777`, only the file's owner, the directory's owner or root may unlink or rename a file (`EPERM` otherwise).
|
|
557
|
+
- **Hard links:** `link` needs `w` and `x` on the target's directory. As with Linux's `protected_hardlinks`, a user who doesn't own a file may link it only when it may read and write it (`EPERM` otherwise); root may link anything.
|
|
558
|
+
- **Modes and times:** only the owner or root may `chmod` (`EPERM`), and a `chmod` by a user that isn't in the file's group clears its setgid bit. Only the owner or root may set a file's times (`EPERM`).
|
|
559
|
+
- **A client's `fs`:** checked as the client's user; a page's client (root) passes.
|
|
560
|
+
- **Not yet checked:** opening a file and reading or writing it, executing it, and `access(2)`; until they are, `users: 'enforce'` isn't a security boundary, and no release turns it on by default before `sudo` exists.
|
|
561
|
+
|
|
548
562
|
### `/proc`
|
|
549
563
|
|
|
550
564
|
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client. A process that exits while a program reads the `/proc` directory doesn't fail or cut short the listing: its pid is still listed, and opening its files fails with `ENOENT`, which `ps`, `pgrep` and `pidof` skip, as on Linux; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times, load and `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) are placeholders. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing, or linking one of its files elsewhere, fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod`, a hard link from it or into it, and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). Neither has `/proc/<pid>/fd` yet.
|
package/dist/index.js
CHANGED
|
@@ -1798,6 +1798,7 @@ function initCall(options, transport) {
|
|
|
1798
1798
|
...options.cdp ? { cdp: true } : {},
|
|
1799
1799
|
...options.hostname !== void 0 ? { hostname: options.hostname } : {},
|
|
1800
1800
|
...options.users !== void 0 ? { users: options.users } : {},
|
|
1801
|
+
...options.onDenial ? { denials: true } : {},
|
|
1801
1802
|
...options.processMounts !== void 0 ? {
|
|
1802
1803
|
processMounts: typeof options.processMounts === "function" ? "ask" : options.processMounts
|
|
1803
1804
|
} : {}
|
|
@@ -1843,6 +1844,15 @@ function kernelUsers(call, mode = "off") {
|
|
|
1843
1844
|
list: async () => await call({ op: "users-list" })
|
|
1844
1845
|
};
|
|
1845
1846
|
}
|
|
1847
|
+
async function mountPolicy(options, worker, { id, req }) {
|
|
1848
|
+
const decide = options.processMounts;
|
|
1849
|
+
let allowed = false;
|
|
1850
|
+
try {
|
|
1851
|
+
allowed = typeof decide === "function" ? await decide(req) : decide !== false;
|
|
1852
|
+
} catch {
|
|
1853
|
+
}
|
|
1854
|
+
worker.postMessage({ mountAllowed: { id, allowed } });
|
|
1855
|
+
}
|
|
1846
1856
|
async function createKernel(options = {}) {
|
|
1847
1857
|
if (!globalThis.crossOriginIsolated) throw new Error(ISOLATION);
|
|
1848
1858
|
if (options.hostname !== void 0 && !isHostname(options.hostname)) {
|
|
@@ -1886,15 +1896,7 @@ async function createKernel(options = {}) {
|
|
|
1886
1896
|
worker.postMessage({ hostfsGrant: { id, error: err.message ?? String(err) } });
|
|
1887
1897
|
}
|
|
1888
1898
|
}
|
|
1889
|
-
|
|
1890
|
-
const decide = options.processMounts;
|
|
1891
|
-
let allowed = false;
|
|
1892
|
-
try {
|
|
1893
|
-
allowed = typeof decide === "function" ? await decide(req) : decide !== false;
|
|
1894
|
-
} catch {
|
|
1895
|
-
}
|
|
1896
|
-
worker.postMessage({ mountAllowed: { id, allowed } });
|
|
1897
|
-
}
|
|
1899
|
+
const policy = (asked) => mountPolicy(options, worker, asked);
|
|
1898
1900
|
const pendingMedium = ({ target, source, handle }) => options.onMountPending?.({
|
|
1899
1901
|
target,
|
|
1900
1902
|
source,
|
|
@@ -1905,6 +1907,7 @@ async function createKernel(options = {}) {
|
|
|
1905
1907
|
({
|
|
1906
1908
|
data
|
|
1907
1909
|
}) => {
|
|
1910
|
+
if ("denial" in data) return void options.onDenial?.(data.denial);
|
|
1908
1911
|
if ("net" in data) return bridge?.answer(data);
|
|
1909
1912
|
if ("uplink" in data) return uplinks?.answer(data);
|
|
1910
1913
|
if ("cdpOpen" in data) return cdp.open(data);
|