@ai-ecoverse/slicc-kernel 1.85.0 → 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 CHANGED
@@ -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
 
@@ -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.ttyFilter?.(slave) ?? slave, 3);
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 { ttyFilter } = this.options;
13013
- const ctx = { pid: this.pid, fds: this.fds, ptys, jobs, guard: guard2, owner, ttyFilter };
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.ttyFilter?.(file) ?? file, 3);
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
- return this.fds.install(broker.open(access2, await this.clipboardStamp()), 3);
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.ttyFilter ? { ttyFilter: opts.ttyFilter } : {},
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 ? { ttyFilter: (file) => scrubbedAll(file, 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,