@ai-ecoverse/slicc-kernel 1.81.0 → 1.82.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 +7 -0
- package/dist/index.js +44 -3
- package/dist/index.js.map +3 -3
- package/dist/kernel-worker.js +263 -41
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +226 -37
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -659,6 +659,13 @@ A character device (`0666`, major 10, minor 251), there in every `users` mode, t
|
|
|
659
659
|
- **Clients:** a client's (`kernel.connect`) file calls, mounts and unmounts see the paths read-only, as its processes do.
|
|
660
660
|
- **Not affected:** the page's own writes to OPFS.
|
|
661
661
|
- **Writers:** `protect: { paths, writers: ['bios-installer'] }` names process tags whose processes may change the paths, such as the embedder's own installs (`kernel.run(['pnpm', 'install'], { tag: 'bios-installer' })`). A tag matches exactly, with no prefix or case folding. A process gets its tag only from the embedder, through `kernel.run`, `kernel.openTerminal` or `kernel.connect({ tag })`, and passes it to its children, forks and `exec`s; a program or a client can't choose one. Writers work on their own view of the root file system, so a file a writer changes is seen by other processes as between two tabs: close-to-open, at their next open after the writer closes it.
|
|
662
|
+
- **Integrity:** `protect: { paths, integrity: { lookup, unknown } }` also checks what a program is made of before it runs. That covers a wasm module, an Emscripten program's glue, a WASI program's imports module, a JS program's module, the `package.json` of each package under a path (the kernel builds its commands, `binfmt` suffixes and drivers only from manifests that pass, so a package whose manifest changed has no commands, 127, and no drivers, `ENODEV`), a script started through its `#!` line or a `binfmt` suffix, a package driver's module and the TLS engine, when the file lies under a path.
|
|
663
|
+
- **The check:** the kernel hashes the exact bytes it is about to compile or hand to a worker (SHA-256, hex), and asks `lookup(path)` (the real path; on the page, it may be async) for the hash it expects. On a mismatch the program doesn't start: `kernel.run` rejects and a spawn fails with `EACCES` ("integrity check failed"; 126 in a shell, not `ENOEXEC`, which would make a shell run the file as a script), a driver fails to mount, TLS fails, and `onDenial` gets `{ op: 'exec-integrity', path, errno: 'EACCES', pid, uid, tag?, expected, actual }`. A `lookup` that throws counts as a mismatch.
|
|
664
|
+
- **No hash known:** when `lookup` has none (`undefined`), `unknown: 'allow'` (the default) runs the file and reports `{ op: 'exec-unverified', errno: 'ENODATA', … }` once per path, while `unknown: 'deny'` refuses it as a mismatch.
|
|
665
|
+
- **The cache:** a result is kept per real path and the version of the file OPFS itself reports (its `lastModified` and `size`), not the kernel's metadata, so a change made around the kernel is checked again at the next start. The compiled module is cached by the same key, so a program that hasn't changed costs nothing more to start.
|
|
666
|
+
- **Where `lookup` gets its hashes:** from a source independent of the installed files, such as the per-file hashes the embedder derives from the certified package tarball when it builds its catalog. Hashes read back from the installed files prove nothing.
|
|
667
|
+
- **Scripts** are checked when they're executed; the interpreter reads the file again afterwards, so a change in between isn't caught ([#454](https://github.com/ai-ecoverse/slicc-kernel/issues/454)).
|
|
668
|
+
- **Not covered:** what a running program reads itself, such as a JS runtime's modules, python's `.py` files and `dlopen`'s side modules ([#454](https://github.com/ai-ecoverse/slicc-kernel/issues/454)). Protected paths still keep those unwritable.
|
|
662
669
|
- **Where it is a boundary:** it's one in a browser, where programs' own code can't reach OPFS around the kernel (see [JS programs](#js-programs)). Under the Node entry it isn't, until [#407](https://github.com/ai-ecoverse/slicc-kernel/issues/407), and there `kernel.writeFile` under a path fails too, so write those files into `root` before creating the kernel.
|
|
663
670
|
|
|
664
671
|
### `/proc`
|
package/dist/index.js
CHANGED
|
@@ -332,7 +332,12 @@ var DEFAULT_USERS = "enforce";
|
|
|
332
332
|
// src/fs/protect.ts
|
|
333
333
|
function protectInit(protection) {
|
|
334
334
|
const writers = [...writerTags(protection)];
|
|
335
|
-
|
|
335
|
+
const { integrity } = protection;
|
|
336
|
+
return {
|
|
337
|
+
paths: [...protection.paths],
|
|
338
|
+
...writers.length > 0 ? { writers } : {},
|
|
339
|
+
...integrity ? { integrity: { unknown: integrityUnknown(integrity) } } : {}
|
|
340
|
+
};
|
|
336
341
|
}
|
|
337
342
|
function writerTags(protection) {
|
|
338
343
|
const writers = protection?.writers ?? [];
|
|
@@ -342,6 +347,40 @@ function writerTags(protection) {
|
|
|
342
347
|
return new Set(writers);
|
|
343
348
|
}
|
|
344
349
|
|
|
350
|
+
// src/fs/integrity.ts
|
|
351
|
+
function integrityOptions(options) {
|
|
352
|
+
if (options && typeof options.lookup !== "function") {
|
|
353
|
+
throw new TypeError("protect.integrity.lookup must be a function");
|
|
354
|
+
}
|
|
355
|
+
integrityUnknown(options);
|
|
356
|
+
}
|
|
357
|
+
function integrityUnknown(options) {
|
|
358
|
+
const unknown = options?.unknown ?? "allow";
|
|
359
|
+
if (unknown !== "allow" && unknown !== "deny") {
|
|
360
|
+
throw new TypeError("protect.integrity.unknown must be 'allow' or 'deny'");
|
|
361
|
+
}
|
|
362
|
+
return unknown;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/integrity-relay.ts
|
|
366
|
+
function integrityAnswerer(port, lookup) {
|
|
367
|
+
const ask = async ({ id, path }) => {
|
|
368
|
+
try {
|
|
369
|
+
const hash = lookup ? await lookup(path) : void 0;
|
|
370
|
+
port.postMessage({ integrityReply: { id, ...typeof hash === "string" ? { hash } : {} } });
|
|
371
|
+
} catch (err) {
|
|
372
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
373
|
+
port.postMessage({ integrityReply: { id, error } });
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
return (data) => {
|
|
377
|
+
const asked = data?.integrityAsk;
|
|
378
|
+
if (!asked) return false;
|
|
379
|
+
void ask(asked);
|
|
380
|
+
return true;
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
345
384
|
// src/kernel/net/loopback-names.ts
|
|
346
385
|
var HOST_LOOPBACK = "host.slicc.internal";
|
|
347
386
|
var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
|
|
@@ -2099,12 +2138,15 @@ function checkOptions(options) {
|
|
|
2099
2138
|
if (options.hostname !== void 0 && !isHostname(options.hostname)) {
|
|
2100
2139
|
throw new Error(`not a host name: ${String(options.hostname)}`);
|
|
2101
2140
|
}
|
|
2141
|
+
if (options.protect) protectInit(options.protect);
|
|
2142
|
+
integrityOptions(options.protect?.integrity);
|
|
2102
2143
|
}
|
|
2103
2144
|
function hookRelays(worker, options) {
|
|
2104
2145
|
const sealed = sealedAnswerer(worker, options.sealed);
|
|
2105
2146
|
const clipboard = clipboardAnswerer(worker, options.clipboard);
|
|
2147
|
+
const integrity = integrityAnswerer(worker, options.protect?.integrity?.lookup);
|
|
2106
2148
|
return {
|
|
2107
|
-
handle: (data) => sealed.handle(data) || clipboard.handle(data),
|
|
2149
|
+
handle: (data) => sealed.handle(data) || clipboard.handle(data) || integrity(data),
|
|
2108
2150
|
close: () => sealed.close()
|
|
2109
2151
|
};
|
|
2110
2152
|
}
|
|
@@ -2133,7 +2175,6 @@ function traitsUpdater(transport, call) {
|
|
|
2133
2175
|
}
|
|
2134
2176
|
async function createKernel(options = {}) {
|
|
2135
2177
|
checkOptions(options);
|
|
2136
|
-
writerTags(options.protect);
|
|
2137
2178
|
const url = options.worker ?? new URL("./kernel-worker.js", import.meta.url);
|
|
2138
2179
|
const worker = new Worker(url, { type: "module", name: "slicc-kernel" });
|
|
2139
2180
|
const pending = /* @__PURE__ */ new Map();
|