@ai-ecoverse/slicc-kernel 1.135.2 → 1.135.4
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 +3 -3
- package/dist/driver-worker.js +16 -4
- package/dist/driver-worker.js.map +2 -2
- package/dist/kernel-worker.js +1 -1
- package/dist/node-driver-worker.js +18 -9
- package/dist/node-driver-worker.js.map +2 -2
- package/dist/node-process-worker.js +19 -10
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +1 -1
- package/dist/process-worker.js +17 -5
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -531,9 +531,9 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
531
531
|
|
|
532
532
|
A program that throws, or leaves an error or a rejected promise unhandled with no listener for it, ends with status 1 and the message on stderr; one whose module cannot be loaded, or exports no function, ends with 126. A write to a pipe whose reader is gone ends the program with 141, unless it ignores or catches `SIGPIPE` (an ignore inherited from its parent counts), in which case the write fails with `EPIPE`. Errors carry a POSIX `code`. Reads and writes of pipes, terminals and sockets go through the kernel's asynchronous operations, so a program blocked reading stdin can still write and handle signals; other calls of one process are made one at a time. A JS program cannot `fork`, and threads it starts itself are not the kernel's.
|
|
533
533
|
|
|
534
|
-
The module has the trust of an Emscripten program's glue or a WASI program's imports module: it comes from a package that was installed, and it runs in the worker of its own process. The kernel withholds the worker's own ways out before it loads the module: `XMLHttpRequest`, `WebSocketStream`, `WebTransport`, `EventSource`, `
|
|
534
|
+
The module has the trust of an Emscripten program's glue or a WASI program's imports module: it comes from a package that was installed, and it runs in the worker of its own process. The kernel withholds the worker's own ways out before it loads the module: `XMLHttpRequest`, `WebSocketStream`, `WebTransport`, `EventSource`, `indexedDB`, `caches`, `cookieStore`, `SharedWorker`, `close`, Chrome's old file system API (`webkitRequestFileSystem`, `webkitRequestFileSystemSync`, `webkitResolveLocalFileSystemURL` and `webkitResolveLocalFileSystemSyncURL`, which reach the origin's OPFS), and `navigator.storage`, `navigator.storageBuckets`, `navigator.locks`, `navigator.serviceWorker`, `navigator.usb`, `navigator.hid` and `navigator.serial` are `undefined`, also through their prototypes, and a worker where one of them cannot be removed refuses to run the program (126). `fetch`, `WebSocket`, `BroadcastChannel`, `importScripts`, `Worker` and `postMessage` are `undefined` too, but stay writable and configurable, so a program can install its own over `ctx.fetch` and `ctx.websocket`, a `BroadcastChannel` within the program (as slicc-jsh does), or the globals napi-rs's wasm32-wasi worker template assigns (`Object.assign(globalThis, { self, require, Worker, importScripts, postMessage })`); the worker's own are gone before the program runs, so deleting the program's brings back `undefined`. `self` stays the global, but as an own writable slot of the scope, so a program can assign its own (the template does) without reaching the worker's. The platform's `BroadcastChannel`, which would reach the origin's other contexts around the kernel, is gone from the scope and its prototypes before the program runs, so deleting the program's own brings back `undefined`, not it; a worker where it can't be removed refuses to run the program (126), as for the others. A program's files are the kernel's, through the context; it reaches the kernel only through its own process's system calls, as the context makes them, and no other process's. This is a guard against programs going around the kernel, not a sandbox: `import()` of a URL is a request the kernel cannot withhold. In a browser, every process worker gets the same lockdown before any program code runs, so an Emscripten program's glue and a WASI program's imports module (any user can write either) see the same globals `undefined`, and a worker where one cannot be removed ends the process with 126. Under the Node entry, every process worker gets the same lockdown before any program code runs: an Emscripten program's glue, a WASI program's imports module and a package driver all see `require`, `process`, `module`, `__dirname` and every Node builtin `undefined` (`require`, `module`, `exports`, `__filename` and `__dirname` writable and configurable, as `process` is, so a program can define its own, never Node's), and `import` of a builtin or a non-`data:` URL fails with `ERR_ACCESS_DENIED`.
|
|
535
535
|
|
|
536
|
-
Under the Node entry, the kernel also withholds the host's Node from the program before its module loads ([#297](https://github.com/ai-ecoverse/slicc-kernel/issues/297)). `
|
|
536
|
+
Under the Node entry, the kernel also withholds the host's Node from the program before its module loads ([#297](https://github.com/ai-ecoverse/slicc-kernel/issues/297)). `localStorage` and `sessionStorage` are `undefined` for good. `require`, `module`, `exports`, `__filename`, `__dirname`, `process`, `global`, `setImmediate` and `clearImmediate` are `undefined` too, but stay writable and configurable, so a program can install its own (as slicc-jsh and napi-rs's wasi worker template do); Node's are gone before the program runs, so deleting the program's brings back `undefined`. `Buffer` stays Node's own, since Node's `Response`, `Request`, `Blob` and stream bodies read it when they run, and a program can replace it. Process workers share Node's memory allocator, so its `allocUnsafe` and `allocUnsafeSlow` zero-fill and its pool is off (`Buffer.poolSize` is 0): a program never sees memory another process freed. A host can also start Node with `--zero-fill-buffers`. Imports of Node's builtin modules (`node:fs`, `fs`, `node:worker_threads`, …) and of anything but a `data:` URL, `file:` included, fail with `ERR_ACCESS_DENIED`, whether static, dynamic or through `import.meta.resolve`. A process worker that exits or crashes on its own ends its process with 70. This is the same guard, not a sandbox: a host that runs code it does not trust, such as an agent's, needs its own isolation around the Node process as well.
|
|
537
537
|
|
|
538
538
|
## Network
|
|
539
539
|
|
|
@@ -1239,7 +1239,7 @@ The tools in `tools/cert-review/`:
|
|
|
1239
1239
|
- **A gate's silence never reads as a pass:** every tool a workflow gates on (`pr.mjs plan`, `same`, `lookup` and `notice`, `downstream.mjs`, `same.mjs`, `pack.mjs`, `delta.mjs`, `suite.mjs`, `compat.mjs`, `findings.mjs`, `record.mjs`, `verify.mjs`) ends with one verdict line, `<tool>: PASS <what>` or `<tool>: FAIL <why>`, from the shared `verdict()` in `lib.mjs`. `tools/cert-review/gate.sh [--either|--evidence] <tool> <command>` runs a tool, passes its output through, and exits 70 with an error when the tool's last line starting `<tool>: ` isn't a PASS or FAIL verdict, so a tool replaced by `true`, an early exit 0 or an empty run fails its step. By default the step passes only on PASS with exit 0; a PASS with a non-zero exit, or a FAIL with exit 0, fails it. `--either` keeps the tool's own exit, for tools whose FAIL is evidence that record judges later (suite, compat). `--evidence` needs only the verdict, for the NEGATIVE run on main's tree, which is expected to fail. `suite: PASS` means exit 0, no failures and every expected file has results; `compat: PASS` means a run turned up with no regressions. `prompt.mjs` writes the review prompt to stdout, which goes into a step output, so it prints no verdict; a missing prompt leaves the review without `verdict.json`, and record marks that INCOMPLETE. The gate reads the tool's stdout, and no tool passes the pull request's output there: suite pipes the tests' output into its log file, and compat prints only its own lines, so a test that prints `suite: PASS` can't stand in for a silent suite. A tool that let such output through would need its verdict on a separate descriptor. Because the workflow of a pull request runs main's tools, the tools gain their verdicts first, and the workflows call them through `gate.sh` in the pull request after that.
|
|
1240
1240
|
- **Evidence comes by id from the job that made it:** the jobs that run the pull request's code (`pack candidate`, which builds it, and both `suite` legs, which run its tests) share the run's artifact store with the jobs that don't, and code in them can reach the runner's artifact token. So no consumer trusts a name or a pattern. Each producer job hands on its artifact's `artifact-id` and `artifact-digest` as job outputs, consumers download by `artifact-ids` with `digest-mismatch: error`, and no upload uses `overwrite`, so a second upload of a name fails instead of replacing it. The base build has its own job, `pack base`, which runs only main's code, so the candidate's job can't speak for it; it restores no Actions cache either, because the candidate job shares the run's cache scope (npm would refuse a tampered tarball against the lockfile's integrity anyway). `tools/cert-review/artifacts.mjs` lists the run's artifacts and fails when one in our namespaces (`kernel-tgz-`, `cert-review-`) wasn't uploaded by its producer, with that producer's id, name and digest, or when a producer's artifact is gone. `same` runs it on the two tarballs before it compares them, so the "not behaviour (identical dist)" skip can't be bought with a planted base tarball. `record` runs it on every artifact before it writes the line, and a failure there makes the record INCOMPLETE. **What this protects:** artifacts of jobs that run no pull-request code (the base build, the delta, kernel-compat, the same-dist evidence, the review's verdict and evidence) can't be forged or replaced. **What it doesn't:** the candidate's own artifacts, its tarball and both suites' results (the base leg runs the pull request's tests on main's tree, for the NEGATIVE), stay the pull request's by design, because code that runs on a runner can always lie about its own results. They're consumed by their exact names, at most one each. That's the same deliberate-malice residual as the workflow-rewrite one above.
|
|
1241
1241
|
- **Decide by what ships:** when a pull request's behaviour files are only `package.json` and/or `package-lock.json` (a Renovate devDependency bump, say), both sides are packed as usual, and then the `same dist` job (`same.mjs`, in code) compares the two tarballs before anything else runs. It checks the same paths, entry types (a symlink isn't a file) and modes. `package.json` must be equal once `devDependencies`, `version` and `gitHead` are removed, so `scripts` (they ship, a consumer's install runs `postinstall`, and `prepack`/`prepublishOnly` can make the published build differ from the packed one), runtime dependencies, `exports`, `bin`, `files` and `engines` must all match. Every other file must be byte-identical once each side's **exact** version string (build.mjs bakes it into `dist`) is replaced by one placeholder. A version string in one more place, or any looser match, is a difference. Source maps are compared as JSON without their `mappings`, whose columns the longer or shorter version string shifts; the code itself is compared byte for byte. If all of that matches, `cert-review` is green with `not behaviour (identical dist): <candidate sha256> = <base sha256> after normalising <v1>/<v2>`, and the suites, kernel-compat, the review and the record are skipped. `same.json` (uploaded as an artifact) names the rule and both tarballs' full sha256. Any difference, or a `same dist` job that doesn't finish, means the full review. The kernel has no runtime `dependencies`, since esbuild bundles everything into `dist`, so a devDependency that changes the product shows up as a byte difference. A test-only devDependency is checked by `ci / test`, on the pull request and on the merge group.
|
|
1242
|
-
- **When:** every same-repo pull request that isn't a draft, on `opened`, `ready_for_review`, `reopened` and every push (`synchronize`). Draft status replaces the old `merge-ok` label: **mark a pull request ready = it gets reviewed, and merges on green**; a pull request held for anything stays a draft. A draft that changes shipped behaviour gets a red status, `draft: not reviewed, mark it ready for review`, and no review; `converted_to_draft` cancels a running review (one concurrency group per pull request) and sets that red status, so an older result on the same head can't satisfy the check. Each pull request has its own concurrency group, and a newer push cancels the older review. Labels other than `cert-review-approved` don't start or cancel anything. Fork code never reaches the pools or the key.
|
|
1242
|
+
- **When:** every same-repo pull request that isn't a draft, on `opened`, `ready_for_review`, `reopened` and every push (`synchronize`). Draft status replaces the old `merge-ok` label: **mark a pull request ready = it gets reviewed, and merges on green**; a pull request held for anything stays a draft. A draft that changes shipped behaviour gets a red status, `draft: not reviewed, mark it ready for review`, and no review. The plan reads whether the pull request is a draft live from the API, not from the event: a push and `ready for review` within a second can leave only the push's run, whose payload still says draft (#1224, #1217). A draft flag it can't read is an `error`, never a review or a pass; `converted_to_draft` cancels a running review (one concurrency group per pull request) and sets that red status, so an older result on the same head can't satisfy the check. Each pull request has its own concurrency group, and a newer push cancels the older review. Labels other than `cert-review-approved` don't start or cancel anything. Fork code never reaches the pools or the key.
|
|
1243
1243
|
- **What:** the checkout is the event's default `refs/pull/N/merge`, the pull request merged onto `main` at review time, so a review is of what would land, without pushing to the branch. Both sides are packed in the same run, on a GitHub-hosted runner, with no secrets: the candidate (the merge ref, `x.y.z-pr.N.<run>`) and the base (its first parent, `main`). Then:
|
|
1244
1244
|
- the candidate's whole suite, and the base's;
|
|
1245
1245
|
- the pull request's new or changed test files on `main`'s tree, with the pull request's `test/**` applied, and `main`'s build (the NEGATIVE: a fix's test must fail on `main` and pass on the candidate);
|
package/dist/driver-worker.js
CHANGED
|
@@ -1275,13 +1275,10 @@ var BLOCKED_GLOBALS = [
|
|
|
1275
1275
|
"WebSocketStream",
|
|
1276
1276
|
"WebTransport",
|
|
1277
1277
|
"EventSource",
|
|
1278
|
-
"importScripts",
|
|
1279
1278
|
"indexedDB",
|
|
1280
1279
|
"caches",
|
|
1281
1280
|
"cookieStore",
|
|
1282
|
-
"Worker",
|
|
1283
1281
|
"SharedWorker",
|
|
1284
|
-
"postMessage",
|
|
1285
1282
|
"close",
|
|
1286
1283
|
"webkitRequestFileSystem",
|
|
1287
1284
|
"webkitRequestFileSystemSync",
|
|
@@ -1297,7 +1294,14 @@ var BLOCKED_NAVIGATOR = [
|
|
|
1297
1294
|
"hid",
|
|
1298
1295
|
"serial"
|
|
1299
1296
|
];
|
|
1300
|
-
var OPEN_SLOTS = [
|
|
1297
|
+
var OPEN_SLOTS = [
|
|
1298
|
+
"fetch",
|
|
1299
|
+
"WebSocket",
|
|
1300
|
+
"BroadcastChannel",
|
|
1301
|
+
"importScripts",
|
|
1302
|
+
"Worker",
|
|
1303
|
+
"postMessage"
|
|
1304
|
+
];
|
|
1301
1305
|
function chain(target) {
|
|
1302
1306
|
const out = [];
|
|
1303
1307
|
for (let at = target; at && at !== Object.prototype; ) {
|
|
@@ -1349,6 +1353,14 @@ function vacate(target, names) {
|
|
|
1349
1353
|
function lockdown(scope = globalThis) {
|
|
1350
1354
|
hide(scope, BLOCKED_GLOBALS);
|
|
1351
1355
|
vacate(scope, OPEN_SLOTS);
|
|
1356
|
+
if ("self" in scope) {
|
|
1357
|
+
Reflect.defineProperty(scope, "self", {
|
|
1358
|
+
value: scope,
|
|
1359
|
+
writable: true,
|
|
1360
|
+
enumerable: false,
|
|
1361
|
+
configurable: true
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1352
1364
|
const navigator = scope.navigator;
|
|
1353
1365
|
if (navigator && typeof navigator === "object") hide(navigator, BLOCKED_NAVIGATOR);
|
|
1354
1366
|
}
|