@ai-ecoverse/slicc-kernel 1.84.1 → 1.85.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 +2 -2
- package/dist/kernel-worker.js +9 -8
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-process-worker.js +14 -5
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +9 -8
- package/dist/node.js.map +2 -2
- package/dist/process-worker.js +14 -5
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -628,7 +628,7 @@ With `users: 'enforce'` the kernel checks every file call that reaches it agains
|
|
|
628
628
|
|
|
629
629
|
### `/dev/sealed`
|
|
630
630
|
|
|
631
|
-
A character device (`0666`, major 10, minor 250) through which a program talks to the embedder's `sealed` hook (`createKernel({ sealed })`), the secrets vault on the page. Emscripten
|
|
631
|
+
A character device (`0666`, major 10, minor 250) through which a program talks to the embedder's `sealed` hook (`createKernel({ sealed })`), the secrets vault on the page. Emscripten programs, WASI programs (`std::fs`) and JS programs (`ctx.open`, `ctx.sync.open`, listed by `ctx.fs.readdir('/dev')`) all open it, through the same kernel device, with the same framing, errnos and stamp: a JS program's stamp is built in the kernel from its own process, as for any other. A JS descriptor opened read-only refuses writes with `EBADF`. Emscripten programs see it in `/dev`. JS `stat` has no device numbers, and WASI's has none either.
|
|
632
632
|
- **State per open:** each `open()` is independent; `dup` and fork share one, as on Linux.
|
|
633
633
|
- **One write is one request:** a single UTF-8 JSON object of at most 64 KiB with `v` 1 and a known `op` (`seal`, `placeholder`, `list`, `test`, `request`, `status`, `cancel`, `materialize`). The write takes all the bytes or fails: `EMSGSIZE` over 64 KiB, `EINVAL` for anything else that isn't such an object. These are the only errnos; every other outcome is a JSON reply. The kernel zeroes the bytes it was given once it has parsed them.
|
|
634
634
|
- **The stamp:** the hook gets the request with `caller: { pid, ppid, uid, gid, user?, groups, users, tag? }`, from the process that writes (at the write; not the one that opened it): `uid` and `gid` the effective ids, `user` the `/etc/passwd` name of `uid` read at that moment (left out when there is none), `groups` the process's supplementary groups (only root can change them), `users` the kernel's `users` mode, since a uid means more under `'enforce'`, and `tag` the process's tag (see `kernel.run`) when it has one. A `caller` in the request is replaced.
|
|
@@ -648,7 +648,7 @@ The kernel then asks the hook `{ v: 1, op: 'materializeConsume', ticket, stamp,
|
|
|
648
648
|
- `interpreters` is the chain of `#!` and `binfmt` interpreters the kernel will really run, outermost first, each absolute with its symlinks resolved, and empty for a program of the catalog (Emscripten, WASI or JS) and the `/usr/bin/<name>` entries for one. A plain `#!/usr/bin/env X` (or `env -S X …`) is reported as `X`: a command of the catalog as `/usr/bin/X`, a script command by its file. `env` with any other option or a `NAME=value` assignment is run and reported as `/usr/bin/env` itself;
|
|
649
649
|
- `trusted` is true when the program and every interpreter are regular files owned by root and not writable by group or others, under directories that are all the same, up to `/` (for a command of the catalog, its package files). It's false unless `users` is `'enforce'`: under `'off'` files have no owners, and under `'report'` owners don't stop a write. The reply must be `{ values, placeholders }` (string maps) within 10 s; anything else is `EACCES`. Each variable of the child's environment whose value is exactly `placeholders[NAME]` gets `values[NAME]`; nothing else changes. The kernel keeps no copy and logs nothing of either. Like any environment, the values are inherited by whatever the child itself spawns or execs, as on Linux. A ticket isn't carried across `sudo`, which builds its own environment: a `sudo` run with `SLICC_SEALED_TICKET` set fails with `EACCES`, and burns the ticket.
|
|
650
650
|
|
|
651
|
-
**The child's output is scrubbed (S2).** The kernel compiles a [slicc-scrub](https://github.com/ai-ecoverse/slicc-scrub) scrubber (1.0.0, bundled; the published package has no runtime dependency) over exactly the values it swapped in, and gives the child new open file descriptions for fd 1 and fd 2, for every other descriptor it holds on the same descriptions (a `dup` of them), and for every descriptor on a terminal (fd 0 of a terminal session is one), that pass every write through it into the originals (one wrapper per original description, so `2>&1` keeps its order). A value appears as its placeholder, and a fragment or encoded form of it (JSON, percent, HTML, base64, hex, wrapped) as `[redacted:NAME]`. Bytes go through at once unless they may still be the start of a match; such a tail is let out after 50 ms without a write when it's shorter than 16 bytes, and otherwise at the next write or at close. The child's descendants inherit the scrubbed descriptions through fork, spawn, exec and `dup`, and a descendant that opens its controlling terminal (`/dev/tty`, or a terminal by name) gets one scrubbed too, as does a pseudo-terminal slave (`/dev/pts/N`) it opens. Files, sockets and pipes the child or a descendant opens itself are not scrubbed, so `printf %s "$T" > f` writes the value. A scrubbed fd 1 or 2 is a stream: it doesn't seek. A descendant that redeems a ticket of its own adds its values: its output is scrubbed for both sets. A value under 8 bytes is swapped but can't be scrubbed. The scrubber goes when the last process and description holding it do.
|
|
651
|
+
**The child's output is scrubbed (S2).** The kernel compiles a [slicc-scrub](https://github.com/ai-ecoverse/slicc-scrub) scrubber (1.0.0, bundled; the published package has no runtime dependency) over exactly the values it swapped in, and gives the child new open file descriptions for fd 1 and fd 2, for every other descriptor it holds on the same descriptions (a `dup` of them), and for every descriptor on a terminal (fd 0 of a terminal session is one), that pass every write through it into the originals (one wrapper per original description, so `2>&1` keeps its order). A value appears as its placeholder, and a fragment or encoded form of it (JSON, percent, HTML, base64, hex, wrapped) as `[redacted:NAME]`. Bytes go through at once unless they may still be the start of a match; such a tail is let out after 50 ms without a write when it's shorter than 16 bytes, and otherwise at the next write or at close. The child's descendants inherit the scrubbed descriptions through fork, spawn, exec and `dup`, and a descendant that opens its controlling terminal (`/dev/tty`, or a terminal by name) gets one scrubbed too, as does a pseudo-terminal slave (`/dev/pts/N`) or [`/dev/clipboard`](#devclipboard) it opens, and a `/dev/clipboard` descriptor it inherited is wrapped at the redeem like fd 1 and 2, so the page's clipboard gets placeholders, never the values. Files, sockets and pipes the child or a descendant opens itself are not scrubbed, so `printf %s "$T" > f` writes the value. A scrubbed fd 1 or 2 is a stream: it doesn't seek. A descendant that redeems a ticket of its own adds its values: its output is scrubbed for both sets. A value under 8 bytes is swapped but can't be scrubbed. The scrubber goes when the last process and description holding it do.
|
|
652
652
|
|
|
653
653
|
### `/dev/clipboard`
|
|
654
654
|
|
package/dist/kernel-worker.js
CHANGED
|
@@ -11843,7 +11843,7 @@ function ptySyscall(req, ctx) {
|
|
|
11843
11843
|
const pair = ctx.ptys.get(req.n);
|
|
11844
11844
|
ttyOpenable(ctx.guard, pair.slave);
|
|
11845
11845
|
const slave = pair.openSlave();
|
|
11846
|
-
const fd = ctx.fds.install(ctx.
|
|
11846
|
+
const fd = ctx.fds.install(ctx.outputFilter?.(slave) ?? slave, 3);
|
|
11847
11847
|
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
11848
11848
|
return json4(fd);
|
|
11849
11849
|
}
|
|
@@ -13009,8 +13009,8 @@ var WasmProcess = class {
|
|
|
13009
13009
|
const { ptys, jobs } = this.options;
|
|
13010
13010
|
const owner = this.options.owners ? this.ttyOwner() : void 0;
|
|
13011
13011
|
const guard2 = this.guarded();
|
|
13012
|
-
const {
|
|
13013
|
-
const ctx = { pid: this.pid, fds: this.fds, ptys, jobs, guard: guard2, owner,
|
|
13012
|
+
const { outputFilter } = this.options;
|
|
13013
|
+
const ctx = { pid: this.pid, fds: this.fds, ptys, jobs, guard: guard2, owner, outputFilter };
|
|
13014
13014
|
return ptySyscall(req, ctx);
|
|
13015
13015
|
}
|
|
13016
13016
|
return await this.procSyscall(req, cancelled);
|
|
@@ -13187,7 +13187,7 @@ var WasmProcess = class {
|
|
|
13187
13187
|
if (!tty) throw new KernelError("ENXIO");
|
|
13188
13188
|
if (name2 !== void 0) ttyOpenable(this.guarded(), tty);
|
|
13189
13189
|
const file = tty.file();
|
|
13190
|
-
return this.fds.install(this.options.
|
|
13190
|
+
return this.fds.install(this.options.outputFilter?.(file) ?? file, 3);
|
|
13191
13191
|
}
|
|
13192
13192
|
openSealed() {
|
|
13193
13193
|
const broker = this.options.sealed;
|
|
@@ -13199,7 +13199,8 @@ var WasmProcess = class {
|
|
|
13199
13199
|
if (!broker) throw new KernelError("ENOENT");
|
|
13200
13200
|
const mode = flags2 & WRITE_ACCESS;
|
|
13201
13201
|
const access2 = mode === 0 ? "read" : mode === 1 ? "write" : "both";
|
|
13202
|
-
|
|
13202
|
+
const file = broker.open(access2, await this.clipboardStamp());
|
|
13203
|
+
return this.fds.install(this.options.outputFilter?.(file) ?? file, 3);
|
|
13203
13204
|
}
|
|
13204
13205
|
async clipboardStamp() {
|
|
13205
13206
|
const shown = await this.options.identity?.();
|
|
@@ -13998,7 +13999,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
13998
13999
|
...inherited(opts),
|
|
13999
14000
|
...opts.owners ? { owners: true } : {},
|
|
14000
14001
|
...opts.sealed ? { sealed: opts.sealed } : {},
|
|
14001
|
-
...opts.
|
|
14002
|
+
...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
|
|
14002
14003
|
...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
|
|
14003
14004
|
...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
|
|
14004
14005
|
...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
|
|
@@ -15000,7 +15001,7 @@ function scrubStdio(fds, scrubber) {
|
|
|
15000
15001
|
const stdio = stdioFiles(fds);
|
|
15001
15002
|
for (const fd of fds.numbers()) {
|
|
15002
15003
|
const original = fds.get(fd);
|
|
15003
|
-
if (!stdio.has(original) && !original.file.tty) continue;
|
|
15004
|
+
if (!stdio.has(original) && !original.file.tty && !original.file.clipboard) continue;
|
|
15004
15005
|
const known = wrapped.get(original);
|
|
15005
15006
|
const flags2 = fds.statusFlags(fd);
|
|
15006
15007
|
const wrapper = known ? known.retain() : scrubbed(original.retain(), scrubber);
|
|
@@ -18079,7 +18080,7 @@ var Launcher = class {
|
|
|
18079
18080
|
...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
|
|
18080
18081
|
...this.owners ? { owners: true } : {},
|
|
18081
18082
|
sealed: this.sealed,
|
|
18082
|
-
...scrubs ? {
|
|
18083
|
+
...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
|
|
18083
18084
|
clipboard: this.clipboard,
|
|
18084
18085
|
...this.dac ? { dac: this.dac } : {},
|
|
18085
18086
|
program: req.program,
|