@ai-ecoverse/slicc-kernel 1.128.2 → 1.129.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 +8 -2
- package/dist/kernel-worker.js +260 -83
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +2 -1
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +272 -95
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +2 -1
- package/dist/process-worker.js.map +2 -2
- package/dist/testing.js +10 -1
- package/dist/testing.js.map +3 -3
- package/dist/types/kernel/children.d.ts +1 -0
- package/dist/types/kernel/net/sealed-http1.d.ts +4 -0
- package/dist/types/kernel/net/sealed-smtp.d.ts +15 -0
- package/dist/types/kernel/process.d.ts +1 -0
- package/dist/types/kernel/test-hooks.d.ts +7 -0
- package/dist/types/launcher.d.ts +3 -0
- package/dist/types/testing.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -225,7 +225,7 @@ A WASI preview1 or WASIX program (Zig, Rust, Go, wasi-libc or wasix-libc C) has
|
|
|
225
225
|
{ "slicc": { "abi": "wasi", "commands": { "rg": { "wasm": "bin/rg.wasm" }, "rustc": { "script": "bin/rustc" } } } }
|
|
226
226
|
```
|
|
227
227
|
|
|
228
|
-
A command's name is any single path component without whitespace or control characters, so coreutils' `[` is in `/usr/bin` too; a name that is empty, `.` or `..`, or has a `/`, a space, a tab, a newline, NUL or another control character is skipped. Entries may also set `argv0`, `args` and `env`, and a package can set `slicc.env` for all of its commands. A WASI entry with `"preopenRoot": true` also gets `/` as a preopen (see [WASI programs](#wasi-programs)). An entry with `"argv0Path": true` gets the absolute path it was run by as `argv[0]` when that path is a link outside `/bin` and `/usr/bin` to the command, as a venv's `bin/python` is (the `#!` line of a script naming such a link included), so it can find what sits beside that link; other entries keep their `argv0`. In `env`, `${package}` is the package directory, `${cwd}` the process's working directory, and `${NAME}` the caller's `NAME` when the program starts (a default naming an unset variable is left out); a relative path that exists in the package (`lib/python3.14`, `./`) becomes that absolute path. The caller's environment wins over these defaults; a `null` value removes the variable for the command, after the caller's environment is applied (a host-networked program can drop the kernel's proxy settings that way), and a command entry can set a variable its package's `slicc.env` removes. The catalog reads its roots in order, and the first root that provides a name wins it: a user's own global packages (for that user's processes), `/node_modules`, root's global packages, then each appended root. Within a root, packages are read by name and the first wins (compared as strings by UTF-16 code unit, so a scoped `@scope/name` comes before names that start with a letter, but after those that start with a digit), whatever order the file system lists them in; a `PNPM_HOME`'s install groups count as one root, read group by group (by the group directory's name, effectively arbitrary for pnpm's hashed names), each by package name. A variant can take over from the package it extends with `"slicc": { "replaces": ["@ai-ecoverse/wasm-imagemagick"] }`: when it and a package it names are in the same root, it wins every command name they share, whatever the order; names only the other provides stay the other's, a third package that provides the name and sorts first still wins it, and packages that replace each other fall back to the name order. A replace never reaches into an earlier root, so root's global packages can't take a name from `/node_modules` or a user's own packages. The field must be an array of package names; anything else is ignored. `sh` runs bash as `sh` unless a package provides its own. A package's `engines.slicc-kernel` is a floor the kernel holds it to: when the running kernel's version doesn't satisfy that npm range (comparators, `^`, `~`, x-ranges, hyphen ranges, `||`), or the range can't be read, its commands are refused. They stay in `/usr/bin`, and running one by name, by path or as a `#!` interpreter exits 126 with `<cmd>: <pkg>@<version> needs slicc-kernel <range>; this kernel is <version>` on stderr. Each refused package is logged once to the console. A non-string `engines.slicc-kernel`, or none, is no constraint. A development build reports the newest release tag as `<tag>+dev` and checks against that; a build of unknown version (`0.0.0`) checks nothing. Only commands are refused: a package without commands (a `py-*` library, a side module) isn't, and a refused package that replaces base commands makes those commands fail with 126 rather than fall back to the package it replaces. The installed commands appear as executables in a virtual `/usr/bin` and `/bin`, so `PATH` lookups, `command -v` and `ls /usr/bin` work without writing anything to OPFS. An entry reads as its program, as a PATH search that opens what it finds expects (`ruby -S rake`, `perl -S`, Pod::Usage looking itself up, bundler checking that `/usr/bin/ruby` isn't empty). A script command reads as its script, a JS command as its module, and a WASI or Emscripten command as its `.wasm`, so `cat /usr/bin/hello` prints the script and `head -c4 /usr/bin/cat` gives `\0asm`. The entry is a plain file and no link: it stays a root-owned `0755` regular file with its own inode, `readlink` of it is `EINVAL`, and `which` and `command -v` print `/usr/bin/<name>`. Its size and times are the program's when the caller may read the program (anyone, for a kernel-internal look or with users off, only when everyone may), and 0 and the epoch otherwise. Running it still runs the catalog command, with its `env`, `args` and `argv0`. The read is the caller's: with users on, reading an entry also needs `r` on its program, and that includes a user's own commands in `~/.local/share/pnpm/bin`, so an entry whose program the caller can't read is `EACCES`. A process can also run a program by the path of its glue when the `.wasm` sits next to it, a wasm module by its own path (as a WASI program; when it is the `wasm` of a WASI command in the caller's catalog, the user's own packages included, it runs as that command, with its `env` defaults, `args` and `argv0`, the command whose name matches the file's, without `.wasm`, if several share it, while a copy elsewhere runs bare), and a script through its `#!` line (including `#!/usr/bin/env name`). Executing any other file fails with `ENOEXEC`, so bash runs it as a shell script, as POSIX shells do. A `..` in a path that is run is resolved physically, as the file system resolves it: `pkg/../tools/x` after a symlinked `pkg` is the `tools/x` beside the link's target, and its exec permission is checked there. The walk is the caller's: with users on, each directory it passes needs search (`x`) permission (`EACCES`, 126 in bash, as on Linux), and a sudo process follows only the links it trusts, as for any other path. Commands also come from pnpm's global directory. Every process gets a `PNPM_HOME` and has `$PNPM_HOME/bin` on `PATH`: root's is `/usr/local/share/pnpm` (unless the embedder sets its own), a user's `~/.local/share/pnpm` (see [Users](#users)). The kernel scans each of pnpm's global install groups (`$PNPM_HOME/global/*/<group>/node_modules`) after `/node_modules`, which wins when both provide a command. The catalog is re-read whenever anything under either changes, so `pnpm add -g @ai-ecoverse/wasi-ripgrep` makes `rg` work in the same shell, and `pnpm remove -g` removes it again.
|
|
228
|
+
A command's name is any single path component without whitespace or control characters, so coreutils' `[` is in `/usr/bin` too; a name that is empty, `.` or `..`, or has a `/`, a space, a tab, a newline, NUL or another control character is skipped. Entries may also set `argv0`, `args` and `env`, and a package can set `slicc.env` for all of its commands. A WASI entry with `"preopenRoot": true` also gets `/` as a preopen (see [WASI programs](#wasi-programs)). An entry with `"argv0Path": true` gets the absolute path it was run by as `argv[0]` when that path is a link outside `/bin` and `/usr/bin` to the command, as a venv's `bin/python` is (the `#!` line of a script naming such a link included), so it can find what sits beside that link; other entries keep their `argv0`. In `env`, `${package}` is the package directory, `${cwd}` the process's working directory, and `${NAME}` the caller's `NAME` when the program starts (a default naming an unset variable is left out); a relative path that exists in the package (`lib/python3.14`, `./`) becomes that absolute path. The caller's environment wins over these defaults; a `null` value removes the variable for the command, after the caller's environment is applied (a host-networked program can drop the kernel's proxy settings that way), and a command entry can set a variable its package's `slicc.env` removes. The catalog reads its roots in order, and the first root that provides a name wins it: a user's own global packages (for that user's processes), `/node_modules`, root's global packages, then each appended root. Within a root, packages are read by name and the first wins (compared as strings by UTF-16 code unit, so a scoped `@scope/name` comes before names that start with a letter, but after those that start with a digit), whatever order the file system lists them in; a `PNPM_HOME`'s install groups count as one root, read group by group (by the group directory's name, effectively arbitrary for pnpm's hashed names), each by package name. A variant can take over from the package it extends with `"slicc": { "replaces": ["@ai-ecoverse/wasm-imagemagick"] }`: when it and a package it names are in the same root, it wins every command name they share, whatever the order; names only the other provides stay the other's, a third package that provides the name and sorts first still wins it, and packages that replace each other fall back to the name order. A replace never reaches into an earlier root, so root's global packages can't take a name from `/node_modules` or a user's own packages. The field must be an array of package names; anything else is ignored. `sh` runs bash as `sh` unless a package provides its own. A package's `engines.slicc-kernel` is a floor the kernel holds it to: when the running kernel's version doesn't satisfy that npm range (comparators, `^`, `~`, x-ranges, hyphen ranges, `||`), or the range can't be read, its commands are refused. They stay in `/usr/bin`, and running one by name, by path or as a `#!` interpreter exits 126 with `<cmd>: <pkg>@<version> needs slicc-kernel <range>; this kernel is <version>` on stderr. Each refused package is logged once to the console. A non-string `engines.slicc-kernel`, or none, is no constraint. A development build reports the newest release tag as `<tag>+dev` and checks against that; a build of unknown version (`0.0.0`) checks nothing. Only commands are refused: a package without commands (a `py-*` library, a side module) isn't, and a refused package that replaces base commands makes those commands fail with 126 rather than fall back to the package it replaces. The installed commands appear as executables in a virtual `/usr/bin` and `/bin`, so `PATH` lookups, `command -v` and `ls /usr/bin` work without writing anything to OPFS. An entry reads as its program, as a PATH search that opens what it finds expects (`ruby -S rake`, `perl -S`, Pod::Usage looking itself up, bundler checking that `/usr/bin/ruby` isn't empty). A script command reads as its script, a JS command as its module, and a WASI or Emscripten command as its `.wasm`, so `cat /usr/bin/hello` prints the script and `head -c4 /usr/bin/cat` gives `\0asm`. The entry is a plain file and no link: it stays a root-owned `0755` regular file with its own inode, `readlink` of it is `EINVAL`, and `which` and `command -v` print `/usr/bin/<name>`. Its size and times are the program's when the caller may read the program (anyone, for a kernel-internal look or with users off, only when everyone may), and 0 and the epoch otherwise. Running it still runs the catalog command, with its `env`, `args` and `argv0`. The read is the caller's: with users on, reading an entry also needs `r` on its program, and that includes a user's own commands in `~/.local/share/pnpm/bin`, so an entry whose program the caller can't read is `EACCES`. A process can also run a program by the path of its glue when the `.wasm` sits next to it, a wasm module by its own path (as a WASI program; when it is the `wasm` of a WASI command in the caller's catalog, the user's own packages included, it runs as that command, with its `env` defaults, `args` and `argv0`, the command whose name matches the file's, without `.wasm`, if several share it, while a copy elsewhere runs bare), and a script through its `#!` line (including `#!/usr/bin/env name`). Executing any other file fails with `ENOEXEC`, so bash runs it as a shell script, as POSIX shells do. A spawn by a bare name (no `/`) that the catalog doesn't have, and that didn't ask for no search (a WASIX `posix_spawn` or `execve`, as opposed to its `posix_spawnp`/`execvp`, keeps its bare name), searches the `PATH` of the spawn's own environment, in order, with an empty entry meaning the working directory; each candidate is run exactly as a spawn of that path would be. The first that runs wins; if one was found but refused and nothing else matched, the spawn fails with `EACCES` (126), otherwise `ENOENT` (127). A catalog command still wins over the same name on `PATH`, the reverse of a shell's order. The `PATH` searched is the new process's, from the spawn's environment; POSIX `posix_spawnp` uses the caller's, which is the same unless the caller hands the child another one. This is a stopgap for the Emscripten shim's `posix_spawnp`, which hands the kernel the bare name (`timeout 5 myscript`, [#1165](https://github.com/ai-ecoverse/slicc-kernel/issues/1165)); it goes once the shipped packages search `PATH` themselves ([#1168](https://github.com/ai-ecoverse/slicc-kernel/issues/1168)). A `..` in a path that is run is resolved physically, as the file system resolves it: `pkg/../tools/x` after a symlinked `pkg` is the `tools/x` beside the link's target, and its exec permission is checked there. The walk is the caller's: with users on, each directory it passes needs search (`x`) permission (`EACCES`, 126 in bash, as on Linux), and a sudo process follows only the links it trusts, as for any other path. Commands also come from pnpm's global directory. Every process gets a `PNPM_HOME` and has `$PNPM_HOME/bin` on `PATH`: root's is `/usr/local/share/pnpm` (unless the embedder sets its own), a user's `~/.local/share/pnpm` (see [Users](#users)). The kernel scans each of pnpm's global install groups (`$PNPM_HOME/global/*/<group>/node_modules`) after `/node_modules`, which wins when both provide a command. The catalog is re-read whenever anything under either changes, so `pnpm add -g @ai-ecoverse/wasi-ripgrep` makes `rg` work in the same shell, and `pnpm remove -g` removes it again.
|
|
229
229
|
|
|
230
230
|
**More roots:** `createKernel({ commands: { append: ['/opt/user/node_modules'] } })` (and `createNodeKernel`) scans more directories, each absolute and normalised (anything else throws a `TypeError`). A name is looked up in a user's own global packages first, then in `modules`, then in root's global packages, then in the appended roots in the order given, and the first match wins, so an appended root never shadows another command (or `sudo`). Their commands are in `/usr/bin` for every user, like root's global packages, and their `binfmt` and file system entries count too. A package in an appended root also gets a command for each `bin` entry of its `package.json` (a string `bin` is named after the package, without its scope), which runs as a `#!` script; a name its `slicc.commands` gives wins. A `bin` entry is skipped when its name isn't a command name (see above), or its file, with symlinks resolved, isn't a regular file inside the package. `/node_modules` and the global packages keep reading only `slicc.commands`. A missing root counts as empty until it appears, and an install or removal in a root is picked up as it happens. With `users: 'enforce'` a root counts only when it and every directory above it are root's and not writable by group or others; another root is skipped without a word. Under `'report'` such a root is still scanned, and reported once to `onDenial` and the report log as `{ pid: 0, uid: 0, op: 'commands', path, errno: 'EACCES' }`. Running a `bin` command needs `r` and `x` on its file under `users` (see [Permissions](#permissions)), checked when it runs, so a `0644` file is `EACCES` under `'enforce'`.
|
|
231
231
|
|
|
@@ -465,13 +465,17 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
465
465
|
|
|
466
466
|
Then:
|
|
467
467
|
- **HTTP/1.1:** if the **first request head** the program writes on the session (it starts with a standard method) has placeholders (`fnox_proxy_<24 hex>`) in exactly one header line, either in the value or in the `user:password` of `Basic` credentials, the kernel asks the vault once (`sessionMaterialize`, below) for that header's position.
|
|
468
|
+
- **SMTP:** a session whose first line is `EHLO` or `HELO` (implicit TLS, or after a STARTTLS upgrade; bytes written before the upgrade are never substituted) is read line by line, and only the `AUTH` exchange is a position:
|
|
469
|
+
- `AUTH PLAIN <blob>`, or the line after a bare `AUTH PLAIN`: the kernel decodes the blob, and asks the vault (`auth-plain`) only when the placeholders are in the **password field**. A placeholder in the user or authorization identity goes out as written. It substitutes there and re-encodes.
|
|
470
|
+
- `AUTH LOGIN`: the password line (the second line, or the first after `AUTH LOGIN <user>`) is decoded, substituted (`auth-login`) and re-encoded; the user line goes out as written.
|
|
471
|
+
- Every other line goes out as written. After the first `AUTH`, or once `MAIL`, `RCPT`, `DATA` or `BDAT` is seen, so is the rest of the session. A line still unfinished after 2 s, or longer than 4 KiB, is sent as written, and so is the rest.
|
|
468
472
|
- **What goes out:** the placeholders the vault answers with a value of at least 8 bytes go out as that value (`Basic` decoded, substituted and re-encoded). Everything else goes out as written:
|
|
469
473
|
- the request line and the body;
|
|
470
474
|
- other headers;
|
|
471
475
|
- a head with placeholders in two header lines;
|
|
472
476
|
- later requests on the same connection;
|
|
473
477
|
- anything once the head passes 16 KiB, or once the program pauses for 2 s in the middle of it;
|
|
474
|
-
-
|
|
478
|
+
- pg, which comes next.
|
|
475
479
|
- **What's read back:** once a process has had a value substituted for a host, everything its whole process tree reads from any A2 session to that host is scrubbed: the process itself, its ancestors and every descendant (forked, spawned or exec'd, before or after the substitution; a forked child's own new session included) with [slicc-scrub](https://github.com/ai-ecoverse/slicc-scrub), as S2 does: the value comes back as its placeholder, and a fragment or an encoded form (JSON, percent, HTML, base64, hex) as `[redacted:sealed]`. The scrub stays until the whole tree has ended. A process outside the tree isn't covered. A value shorter than 8 bytes is never substituted, as it couldn't be scrubbed.
|
|
476
480
|
- **The bound:** the server the value goes to is the one the vault rule names, verified against the kernel's own roots. Substitution trusts that server not to reflect the value back in a form the scrub doesn't know (encrypted under a key the program chose, say). That's the same residual as A1's.
|
|
477
481
|
- **Elsewhere:** a placeholder written into any other A2 session reaches the server as written. It isn't the secret, so nothing leaks.
|
|
@@ -603,6 +607,8 @@ In the browser, the page serves the uplink to the kernel worker over the same po
|
|
|
603
607
|
|
|
604
608
|
`fakeCronClock(start?, utc?)` is a clock for the kernel's cron to run on in tests, so a test moves time instead of waiting for it. It starts at `start` (default 2026-01-01T00:00Z) and reads schedules in UTC unless `utc` is `false`. `now()` is the fake time. `sleep(until, signal)` resolves when `advance(ms)` moves time to `until` or past it, or when `signal` aborts. `set(ms)` sets the wall clock, forward or back, and wakes every sleeper, as a scheduler's next periodic wake would after a real clock change. `sleepers` counts the sleeps still waiting. `flush()` resolves once every sleeper that a time move woke has gone back to sleep for a later time, which is the point where a scheduler has done what was due. It never resolves while nothing sleeps.
|
|
605
609
|
|
|
610
|
+
`setTestSyscallHook(hook)` is **test-only**: while set, every process kernel call (the `WasmSyscall` requests: process, fd, socket, signal and job calls; not the path-only file system requests such as `read`, `write` or `rename`) of a process in this Node process first awaits `hook({ pid, op })`. `pid` is the kernel's own pid of the process, which for an exec'd image is not the pid the program sees (`getpid()`, `ps` show the pid it took over). A test can then act at an exact point (write a ^C when a shell forks) or hold a call (delay a child's `proc-setpgid`). `setTestSyscallHook(undefined)` clears it. Unset, which is the default, it does nothing.
|
|
611
|
+
|
|
606
612
|
### Browser automation (CDP)
|
|
607
613
|
|
|
608
614
|
Programs drive a browser with the Chrome DevTools Protocol over a WebSocket, whichever host actually has the browser: slicc-extension's `chrome.debugger`, slicc-node's or slicc-swift's CDP proxy, or a test harness. The kernel relays; it does not launch a browser.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -544,14 +544,14 @@ var Integrity = class {
|
|
|
544
544
|
if (!this.root.version) return void 0;
|
|
545
545
|
return `${real}\0${await this.root.version(real)}`;
|
|
546
546
|
}
|
|
547
|
-
async read(path, caller,
|
|
548
|
-
return (await this.readKeyed(path, caller,
|
|
547
|
+
async read(path, caller, plain3, asked) {
|
|
548
|
+
return (await this.readKeyed(path, caller, plain3, asked)).bytes;
|
|
549
549
|
}
|
|
550
|
-
async readKeyed(path, caller,
|
|
550
|
+
async readKeyed(path, caller, plain3, asked) {
|
|
551
551
|
const found = await this.real(path, asked);
|
|
552
|
-
if (found === void 0) return { bytes: await
|
|
552
|
+
if (found === void 0) return { bytes: await plain3() };
|
|
553
553
|
const { real, name: name2 } = found;
|
|
554
|
-
const read = name2 !== real ? { bytes: await
|
|
554
|
+
const read = name2 !== real ? { bytes: await plain3(), version: void 0 } : this.root.readVersioned ? await this.root.readVersioned(real) : { bytes: await this.root.readFileBuffer(real), version: void 0 };
|
|
555
555
|
const { bytes } = read;
|
|
556
556
|
const keyed = read.version === void 0 ? { bytes } : { bytes, key: `${real}\0${read.version}` };
|
|
557
557
|
await this.verify(name2, bytes, caller, EXEC_OPS);
|
|
@@ -560,9 +560,9 @@ var Integrity = class {
|
|
|
560
560
|
get readsChecked() {
|
|
561
561
|
return this.sources.length > 0;
|
|
562
562
|
}
|
|
563
|
-
async readSource(path, caller,
|
|
563
|
+
async readSource(path, caller, plain3) {
|
|
564
564
|
const found = await this.source(path);
|
|
565
|
-
return found === void 0 ?
|
|
565
|
+
return found === void 0 ? plain3() : this.checked(found, caller, plain3);
|
|
566
566
|
}
|
|
567
567
|
async openSource(path, caller, real) {
|
|
568
568
|
const found = await this.source(path, real);
|
|
@@ -572,10 +572,10 @@ var Integrity = class {
|
|
|
572
572
|
}
|
|
573
573
|
await this.checked(found, caller, () => this.root.readFileBuffer(found.real));
|
|
574
574
|
}
|
|
575
|
-
async checked(found, caller,
|
|
575
|
+
async checked(found, caller, plain3) {
|
|
576
576
|
const { real, name: name2 } = found;
|
|
577
577
|
if (name2 !== real || !this.root.readVersioned) {
|
|
578
|
-
const bytes2 = await
|
|
578
|
+
const bytes2 = await plain3();
|
|
579
579
|
await this.verify(name2, bytes2, caller, READ_OPS);
|
|
580
580
|
return bytes2;
|
|
581
581
|
}
|
|
@@ -3232,11 +3232,11 @@ function frame(opcode, payload) {
|
|
|
3232
3232
|
return out;
|
|
3233
3233
|
}
|
|
3234
3234
|
function clientFrame(opcode, payload) {
|
|
3235
|
-
const
|
|
3236
|
-
const head =
|
|
3235
|
+
const plain3 = frame(opcode, payload);
|
|
3236
|
+
const head = plain3.length - payload.length;
|
|
3237
3237
|
const mask = crypto.getRandomValues(new Uint8Array(4));
|
|
3238
|
-
const out = new Uint8Array(
|
|
3239
|
-
out.set(
|
|
3238
|
+
const out = new Uint8Array(plain3.length + 4);
|
|
3239
|
+
out.set(plain3.subarray(0, head));
|
|
3240
3240
|
out[1] = out[1] | 128;
|
|
3241
3241
|
out.set(mask, head);
|
|
3242
3242
|
for (let i = 0; i < payload.length; i++) {
|
|
@@ -7020,7 +7020,7 @@ function satisfies(version, range2) {
|
|
|
7020
7020
|
}
|
|
7021
7021
|
|
|
7022
7022
|
// src/version.ts
|
|
7023
|
-
var KERNEL_VERSION = true ? "1.
|
|
7023
|
+
var KERNEL_VERSION = true ? "1.129.0" : "0.0.0-dev";
|
|
7024
7024
|
var OS_RELEASE = "6.0.0-slicc";
|
|
7025
7025
|
function procVersion() {
|
|
7026
7026
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -12378,13 +12378,13 @@ async function chown(entry, path, req) {
|
|
|
12378
12378
|
}
|
|
12379
12379
|
async function runMkdir(entry, path, req) {
|
|
12380
12380
|
const { fs } = entry;
|
|
12381
|
-
const
|
|
12381
|
+
const plain3 = plainDirMode(entry, req.mode);
|
|
12382
12382
|
const parentsMode = !req.exclusive ? plainParentsMode(entry) : void 0;
|
|
12383
12383
|
const fixParents = parentsMode !== void 0 ? await missingParents(fs, path, parentsMode) : void 0;
|
|
12384
|
-
const fresh2 =
|
|
12384
|
+
const fresh2 = plain3 !== void 0 && !await fs.exists(path);
|
|
12385
12385
|
await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
|
|
12386
12386
|
await fixParents?.();
|
|
12387
|
-
if (fresh2) await fs.chmod(path,
|
|
12387
|
+
if (fresh2) await fs.chmod(path, plain3);
|
|
12388
12388
|
return done2;
|
|
12389
12389
|
}
|
|
12390
12390
|
async function shownAs(entry, path, st) {
|
|
@@ -12392,8 +12392,8 @@ async function shownAs(entry, path, st) {
|
|
|
12392
12392
|
return callerStat(guardOf(entry), await real?.catch(() => path) ?? path, st);
|
|
12393
12393
|
}
|
|
12394
12394
|
function readSource(entry, path) {
|
|
12395
|
-
const
|
|
12396
|
-
return entry.source ? entry.source.read(path,
|
|
12395
|
+
const plain3 = () => entry.fs.readFileBuffer(path);
|
|
12396
|
+
return entry.source ? entry.source.read(path, plain3) : plain3();
|
|
12397
12397
|
}
|
|
12398
12398
|
async function run(entry, path, req) {
|
|
12399
12399
|
const { fs, cwd } = entry;
|
|
@@ -18490,6 +18490,95 @@ async function substituteHead(head, ask) {
|
|
|
18490
18490
|
`, values };
|
|
18491
18491
|
}
|
|
18492
18492
|
|
|
18493
|
+
// src/kernel/net/sealed-smtp.ts
|
|
18494
|
+
var SMTP_LINE_MAX = 4096;
|
|
18495
|
+
var GREETING = ["EHLO ", "HELO "];
|
|
18496
|
+
function sniffSmtp(start2) {
|
|
18497
|
+
const upper = start2.toUpperCase();
|
|
18498
|
+
for (const word of GREETING) {
|
|
18499
|
+
if (upper.startsWith(word)) return "yes";
|
|
18500
|
+
if (word.startsWith(upper)) return "more";
|
|
18501
|
+
}
|
|
18502
|
+
return "no";
|
|
18503
|
+
}
|
|
18504
|
+
function decoded(b64) {
|
|
18505
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(b64)) return void 0;
|
|
18506
|
+
try {
|
|
18507
|
+
return atob(b64);
|
|
18508
|
+
} catch {
|
|
18509
|
+
return void 0;
|
|
18510
|
+
}
|
|
18511
|
+
}
|
|
18512
|
+
async function swapped2(text, position, ask) {
|
|
18513
|
+
const placeholders = placeholdersIn(text);
|
|
18514
|
+
if (placeholders.length === 0 || placeholders.length > SESSION_PLACEHOLDERS) return void 0;
|
|
18515
|
+
const values = usable(await ask(position, placeholders));
|
|
18516
|
+
if (Object.keys(values).length === 0) return void 0;
|
|
18517
|
+
return { text: replaced(text, values), values };
|
|
18518
|
+
}
|
|
18519
|
+
async function plain2(b64, ask) {
|
|
18520
|
+
const text = decoded(b64);
|
|
18521
|
+
const fields = text?.split("\0");
|
|
18522
|
+
if (fields?.length !== 3) return { line: b64 };
|
|
18523
|
+
const [authz, user, pass2] = fields;
|
|
18524
|
+
if (placeholdersIn(authz + user).length > 0) return { line: b64 };
|
|
18525
|
+
const done5 = await swapped2(pass2, "auth-plain", ask);
|
|
18526
|
+
if (!done5) return { line: b64 };
|
|
18527
|
+
return { line: btoa(`${authz}\0${user}\0${done5.text}`), values: done5.values };
|
|
18528
|
+
}
|
|
18529
|
+
async function login(b64, ask) {
|
|
18530
|
+
const text = decoded(b64);
|
|
18531
|
+
const done5 = text === void 0 ? void 0 : await swapped2(text, "auth-login", ask);
|
|
18532
|
+
return done5 ? { line: btoa(done5.text), values: done5.values } : { line: b64 };
|
|
18533
|
+
}
|
|
18534
|
+
var SmtpClient = class {
|
|
18535
|
+
state = "command";
|
|
18536
|
+
ask;
|
|
18537
|
+
constructor(ask) {
|
|
18538
|
+
this.ask = ask;
|
|
18539
|
+
}
|
|
18540
|
+
get done() {
|
|
18541
|
+
return this.state === "done";
|
|
18542
|
+
}
|
|
18543
|
+
async line(text) {
|
|
18544
|
+
const state = this.state;
|
|
18545
|
+
if (state === "done") return { line: text };
|
|
18546
|
+
if (state === "plain" || state === "password") {
|
|
18547
|
+
this.state = "done";
|
|
18548
|
+
if (text === "*") return { line: text };
|
|
18549
|
+
return state === "plain" ? plain2(text, this.ask) : login(text, this.ask);
|
|
18550
|
+
}
|
|
18551
|
+
if (state === "user") {
|
|
18552
|
+
this.state = text === "*" ? "done" : "password";
|
|
18553
|
+
return { line: text };
|
|
18554
|
+
}
|
|
18555
|
+
return this.command(text);
|
|
18556
|
+
}
|
|
18557
|
+
async command(text) {
|
|
18558
|
+
const m = /^AUTH[ \t]+(PLAIN|LOGIN)(?:[ \t]+(\S+))?$/i.exec(text);
|
|
18559
|
+
if (!m) {
|
|
18560
|
+
if (/^(MAIL|RCPT|DATA|BDAT|AUTH)\b/i.test(text)) this.state = "done";
|
|
18561
|
+
return { line: text };
|
|
18562
|
+
}
|
|
18563
|
+
const mech = m[1].toUpperCase();
|
|
18564
|
+
const arg = m[2];
|
|
18565
|
+
if (mech === "LOGIN") {
|
|
18566
|
+
this.state = arg === void 0 ? "user" : "password";
|
|
18567
|
+
return { line: text };
|
|
18568
|
+
}
|
|
18569
|
+
if (arg === void 0) {
|
|
18570
|
+
this.state = "plain";
|
|
18571
|
+
return { line: text };
|
|
18572
|
+
}
|
|
18573
|
+
this.state = "done";
|
|
18574
|
+
const out = await plain2(arg, this.ask);
|
|
18575
|
+
return {
|
|
18576
|
+
line: `${text.slice(0, text.length - arg.length)}${out.line}`,
|
|
18577
|
+
...out.values ? { values: out.values } : {}
|
|
18578
|
+
};
|
|
18579
|
+
}
|
|
18580
|
+
};
|
|
18581
|
+
|
|
18493
18582
|
// src/kernel/net/sealed-conn.ts
|
|
18494
18583
|
var HEAD_IDLE_MS = 2e3;
|
|
18495
18584
|
function substitutes(s) {
|
|
@@ -18521,12 +18610,25 @@ function headEnd2(bytes) {
|
|
|
18521
18610
|
}
|
|
18522
18611
|
return -1;
|
|
18523
18612
|
}
|
|
18524
|
-
|
|
18613
|
+
function modeOf(start2) {
|
|
18614
|
+
const http = sniffHttp1(start2);
|
|
18615
|
+
const smtp = sniffSmtp(start2);
|
|
18616
|
+
if (http === "yes") return "http1";
|
|
18617
|
+
if (smtp === "yes") return "smtp";
|
|
18618
|
+
return http === "no" && smtp === "no" ? "pass" : void 0;
|
|
18619
|
+
}
|
|
18620
|
+
function crlf(bytes) {
|
|
18621
|
+
for (let i = 0; i + 1 < bytes.length; i++) if (bytes[i] === 13 && bytes[i + 1] === 10) return i;
|
|
18622
|
+
return -1;
|
|
18623
|
+
}
|
|
18624
|
+
var SessionWriter = class {
|
|
18525
18625
|
buffer = new Uint8Array(0);
|
|
18526
|
-
|
|
18626
|
+
mode = "sniff";
|
|
18527
18627
|
timer;
|
|
18528
18628
|
chain = Promise.resolve();
|
|
18529
18629
|
failed;
|
|
18630
|
+
session = sessionId();
|
|
18631
|
+
smtp;
|
|
18530
18632
|
conn;
|
|
18531
18633
|
s;
|
|
18532
18634
|
scope;
|
|
@@ -18534,6 +18636,9 @@ var Http1Writer = class {
|
|
|
18534
18636
|
this.conn = conn;
|
|
18535
18637
|
this.s = s;
|
|
18536
18638
|
this.scope = scope;
|
|
18639
|
+
this.smtp = new SmtpClient(
|
|
18640
|
+
(position, placeholders) => this.ask("smtp", position, placeholders)
|
|
18641
|
+
);
|
|
18537
18642
|
}
|
|
18538
18643
|
write(bytes) {
|
|
18539
18644
|
const run2 = this.chain.then(() => this.take(bytes));
|
|
@@ -18543,30 +18648,78 @@ var Http1Writer = class {
|
|
|
18543
18648
|
);
|
|
18544
18649
|
return run2;
|
|
18545
18650
|
}
|
|
18651
|
+
async ask(protocol, position, placeholders, head) {
|
|
18652
|
+
const s = this.s;
|
|
18653
|
+
const values = await s.broker.sessionMaterialize({
|
|
18654
|
+
stamp: await s.stamp(),
|
|
18655
|
+
session: this.session,
|
|
18656
|
+
host: s.host,
|
|
18657
|
+
port: s.port,
|
|
18658
|
+
protocol,
|
|
18659
|
+
position,
|
|
18660
|
+
verified: s.verified,
|
|
18661
|
+
handshakeAt: s.handshakeAt,
|
|
18662
|
+
exit: s.exit,
|
|
18663
|
+
placeholders,
|
|
18664
|
+
...head ? { head } : {}
|
|
18665
|
+
});
|
|
18666
|
+
return values;
|
|
18667
|
+
}
|
|
18668
|
+
remember(values) {
|
|
18669
|
+
this.scope.add(
|
|
18670
|
+
Object.entries(values).map(([placeholder, value2]) => ({ name: "sealed", value: value2, placeholder }))
|
|
18671
|
+
);
|
|
18672
|
+
}
|
|
18546
18673
|
async take(bytes) {
|
|
18547
18674
|
if (this.failed !== void 0) throw this.failed;
|
|
18548
|
-
if (this.
|
|
18675
|
+
if (this.mode === "pass") return this.conn.write(bytes);
|
|
18549
18676
|
clearTimeout(this.timer);
|
|
18550
18677
|
const joined4 = new Uint8Array(this.buffer.length + bytes.length);
|
|
18551
18678
|
joined4.set(this.buffer);
|
|
18552
18679
|
joined4.set(bytes, this.buffer.length);
|
|
18553
18680
|
this.buffer.fill(0);
|
|
18554
18681
|
this.buffer = joined4;
|
|
18555
|
-
|
|
18556
|
-
|
|
18557
|
-
if (
|
|
18558
|
-
|
|
18559
|
-
|
|
18560
|
-
|
|
18682
|
+
if (this.mode === "sniff") this.mode = modeOf(latin12(joined4.subarray(0, 8))) ?? "sniff";
|
|
18683
|
+
if (this.mode === "pass") await this.release();
|
|
18684
|
+
else if (this.mode === "http1") await this.http1();
|
|
18685
|
+
else if (this.mode === "smtp") await this.lines();
|
|
18686
|
+
if (this.mode !== "pass" && this.buffer.length > 0)
|
|
18687
|
+
this.timer = setTimeout(() => this.idle(), HEAD_IDLE_MS);
|
|
18561
18688
|
return bytes.length;
|
|
18562
18689
|
}
|
|
18690
|
+
async http1() {
|
|
18691
|
+
const end = headEnd2(this.buffer);
|
|
18692
|
+
if (end > HEAD_MAX || end < 0 && this.buffer.length > HEAD_MAX) await this.release();
|
|
18693
|
+
else if (end >= 0) await this.substitute(end);
|
|
18694
|
+
}
|
|
18695
|
+
async lines() {
|
|
18696
|
+
for (; ; ) {
|
|
18697
|
+
const at = crlf(this.buffer);
|
|
18698
|
+
if (at < 0) break;
|
|
18699
|
+
if (at > SMTP_LINE_MAX) return this.release();
|
|
18700
|
+
const all = this.buffer;
|
|
18701
|
+
this.buffer = all.slice(at + 2);
|
|
18702
|
+
const out = await this.smtp.line(latin12(all.subarray(0, at))).catch(() => ({ line: latin12(all.subarray(0, at)) }));
|
|
18703
|
+
all.fill(0);
|
|
18704
|
+
if ("values" in out && out.values) this.remember(out.values);
|
|
18705
|
+
const line = bytesOf(`${out.line}\r
|
|
18706
|
+
`);
|
|
18707
|
+
try {
|
|
18708
|
+
await writeAll4(this.conn, line);
|
|
18709
|
+
} finally {
|
|
18710
|
+
line.fill(0);
|
|
18711
|
+
}
|
|
18712
|
+
if (this.smtp.done) return this.release();
|
|
18713
|
+
}
|
|
18714
|
+
if (this.buffer.length > SMTP_LINE_MAX) await this.release();
|
|
18715
|
+
}
|
|
18563
18716
|
idle() {
|
|
18564
18717
|
this.chain = this.chain.then(() => this.release()).catch((err) => {
|
|
18565
18718
|
this.failed ??= err;
|
|
18566
18719
|
});
|
|
18567
18720
|
}
|
|
18568
18721
|
async release() {
|
|
18569
|
-
this.
|
|
18722
|
+
this.mode = "pass";
|
|
18570
18723
|
const out = this.buffer;
|
|
18571
18724
|
this.buffer = new Uint8Array(0);
|
|
18572
18725
|
try {
|
|
@@ -18576,37 +18729,15 @@ var Http1Writer = class {
|
|
|
18576
18729
|
}
|
|
18577
18730
|
}
|
|
18578
18731
|
async substitute(end) {
|
|
18579
|
-
this.
|
|
18732
|
+
this.mode = "pass";
|
|
18580
18733
|
const all = this.buffer;
|
|
18581
18734
|
this.buffer = new Uint8Array(0);
|
|
18582
|
-
const s = this.s;
|
|
18583
|
-
const session = sessionId();
|
|
18584
18735
|
const done5 = await substituteHead(
|
|
18585
18736
|
latin12(all.subarray(0, end)),
|
|
18586
|
-
|
|
18587
|
-
stamp: await s.stamp(),
|
|
18588
|
-
session,
|
|
18589
|
-
host: s.host,
|
|
18590
|
-
port: s.port,
|
|
18591
|
-
protocol: "http1",
|
|
18592
|
-
position,
|
|
18593
|
-
verified: s.verified,
|
|
18594
|
-
handshakeAt: s.handshakeAt,
|
|
18595
|
-
exit: s.exit,
|
|
18596
|
-
placeholders,
|
|
18597
|
-
head: head2
|
|
18598
|
-
})
|
|
18737
|
+
(position, placeholders, head2) => this.ask("http1", position, placeholders, head2)
|
|
18599
18738
|
).catch(() => void 0);
|
|
18600
18739
|
const head = done5 ? bytesOf(done5.head) : all.subarray(0, end);
|
|
18601
|
-
if (done5)
|
|
18602
|
-
this.scope.add(
|
|
18603
|
-
Object.entries(done5.values).map(([placeholder, value2]) => ({
|
|
18604
|
-
name: "sealed",
|
|
18605
|
-
value: value2,
|
|
18606
|
-
placeholder
|
|
18607
|
-
}))
|
|
18608
|
-
);
|
|
18609
|
-
}
|
|
18740
|
+
if (done5) this.remember(done5.values);
|
|
18610
18741
|
try {
|
|
18611
18742
|
await writeAll4(this.conn, head);
|
|
18612
18743
|
await writeAll4(this.conn, all.subarray(end));
|
|
@@ -18617,14 +18748,14 @@ var Http1Writer = class {
|
|
|
18617
18748
|
}
|
|
18618
18749
|
flushed() {
|
|
18619
18750
|
clearTimeout(this.timer);
|
|
18620
|
-
this.chain = this.chain.then(() => this.
|
|
18751
|
+
this.chain = this.chain.then(() => this.mode === "pass" ? void 0 : this.release());
|
|
18621
18752
|
return this.chain;
|
|
18622
18753
|
}
|
|
18623
18754
|
drop() {
|
|
18624
18755
|
clearTimeout(this.timer);
|
|
18625
18756
|
this.buffer.fill(0);
|
|
18626
18757
|
this.buffer = new Uint8Array(0);
|
|
18627
|
-
this.
|
|
18758
|
+
this.mode = "pass";
|
|
18628
18759
|
}
|
|
18629
18760
|
};
|
|
18630
18761
|
var IDLE2 = /* @__PURE__ */ Symbol("idle");
|
|
@@ -18635,11 +18766,9 @@ var ScrubReader = class {
|
|
|
18635
18766
|
held = false;
|
|
18636
18767
|
ended = false;
|
|
18637
18768
|
conn;
|
|
18638
|
-
s;
|
|
18639
18769
|
scope;
|
|
18640
|
-
constructor(conn,
|
|
18770
|
+
constructor(conn, scope) {
|
|
18641
18771
|
this.conn = conn;
|
|
18642
|
-
this.s = s;
|
|
18643
18772
|
this.scope = scope;
|
|
18644
18773
|
}
|
|
18645
18774
|
current() {
|
|
@@ -18698,8 +18827,8 @@ function idle() {
|
|
|
18698
18827
|
}
|
|
18699
18828
|
function sealedConn(conn, s) {
|
|
18700
18829
|
const scope = s.broker.sessions.scope(s.pid, s.host);
|
|
18701
|
-
const writer = substitutes(s) ? new
|
|
18702
|
-
const reader = new ScrubReader(conn,
|
|
18830
|
+
const writer = substitutes(s) ? new SessionWriter(conn, s, scope) : void 0;
|
|
18831
|
+
const reader = new ScrubReader(conn, scope);
|
|
18703
18832
|
return {
|
|
18704
18833
|
localAddr: conn.localAddr,
|
|
18705
18834
|
remoteAddr: conn.remoteAddr,
|
|
@@ -19825,6 +19954,12 @@ function tlsOverTransport(req, proc) {
|
|
|
19825
19954
|
return { fd: proc.fds.install(new OpenFile(mine), 3), alpnProtocol: "http/1.1", servername };
|
|
19826
19955
|
}
|
|
19827
19956
|
|
|
19957
|
+
// src/kernel/test-hooks.ts
|
|
19958
|
+
var SYSCALL_HOOK = /* @__PURE__ */ Symbol.for("slicc-kernel.test.syscall");
|
|
19959
|
+
function testSyscallHook() {
|
|
19960
|
+
return globalThis[SYSCALL_HOOK];
|
|
19961
|
+
}
|
|
19962
|
+
|
|
19828
19963
|
// src/kernel/vfs-file.ts
|
|
19829
19964
|
var O_ACCMODE = 3;
|
|
19830
19965
|
var O_WRONLY = 1;
|
|
@@ -21039,7 +21174,8 @@ var WasmProcess = class {
|
|
|
21039
21174
|
cwd,
|
|
21040
21175
|
...moved2,
|
|
21041
21176
|
...group,
|
|
21042
|
-
...exec ? { exec } : {}
|
|
21177
|
+
...exec ? { exec } : {},
|
|
21178
|
+
search: req.search
|
|
21043
21179
|
};
|
|
21044
21180
|
const pid = await this.children.spawn(spawned, stdio, inherit2);
|
|
21045
21181
|
if (deathSignal !== void 0) this.onDeath(pgid === 0 || session ? -pid : pid, deathSignal);
|
|
@@ -21125,6 +21261,8 @@ var WasmProcess = class {
|
|
|
21125
21261
|
return void 0;
|
|
21126
21262
|
}
|
|
21127
21263
|
async syscall(req, cancelled, block) {
|
|
21264
|
+
const hook = testSyscallHook();
|
|
21265
|
+
if (hook) await hook({ pid: this.pid, op: req.op });
|
|
21128
21266
|
this.options.onSyscall?.(req);
|
|
21129
21267
|
this.calls += 1;
|
|
21130
21268
|
const started = performance.now();
|
|
@@ -21422,18 +21560,18 @@ var WasmProcess = class {
|
|
|
21422
21560
|
}
|
|
21423
21561
|
if (this.writesAt(req) && this.options.readonlyAt?.(path)) throw new KernelError("EROFS");
|
|
21424
21562
|
const open2 = { ...req, path };
|
|
21425
|
-
const
|
|
21563
|
+
const plain3 = this.plainMode(req.mode);
|
|
21426
21564
|
if (req.exclusive) {
|
|
21427
21565
|
await nodes.createExclusive(path, this.newOwner(req.mode));
|
|
21428
|
-
if (
|
|
21566
|
+
if (plain3 !== void 0) await fs.chmod?.(path, plain3);
|
|
21429
21567
|
this.fds.installAt(fd, this.openVfsFile(open2, void 0, fs));
|
|
21430
21568
|
} else if (req.create) {
|
|
21431
21569
|
await nodes.onEntry(path, async () => {
|
|
21432
|
-
const marks = this.options.owners ||
|
|
21570
|
+
const marks = this.options.owners || plain3 !== void 0;
|
|
21433
21571
|
const fresh2 = marks && !await present3(fs, path, true);
|
|
21434
21572
|
this.fds.installAt(fd, this.openVfsFile(open2, await nodes.identify(path), fs));
|
|
21435
21573
|
await this.fds.get(fd).file.stat?.();
|
|
21436
|
-
if (fresh2 &&
|
|
21574
|
+
if (fresh2 && plain3 !== void 0) await fs.chmod?.(path, plain3);
|
|
21437
21575
|
else if (fresh2) {
|
|
21438
21576
|
await fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
|
|
21439
21577
|
}
|
|
@@ -23576,15 +23714,15 @@ function isRecord(value2) {
|
|
|
23576
23714
|
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
23577
23715
|
}
|
|
23578
23716
|
function serialized(reply) {
|
|
23579
|
-
let
|
|
23717
|
+
let plain3;
|
|
23580
23718
|
try {
|
|
23581
23719
|
const text = JSON.stringify(reply);
|
|
23582
|
-
|
|
23720
|
+
plain3 = text === void 0 ? void 0 : JSON.parse(text);
|
|
23583
23721
|
} catch (err) {
|
|
23584
23722
|
return JSON.stringify(errorReply("EIO", lineOf2(err)));
|
|
23585
23723
|
}
|
|
23586
|
-
if (!isRecord(
|
|
23587
|
-
const { v: _, ...rest2 } =
|
|
23724
|
+
if (!isRecord(plain3)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
|
|
23725
|
+
const { v: _, ...rest2 } = plain3;
|
|
23588
23726
|
return JSON.stringify({ v: 1, ...rest2 });
|
|
23589
23727
|
}
|
|
23590
23728
|
function lineOf2(err) {
|
|
@@ -25546,13 +25684,13 @@ function splitWords(text) {
|
|
|
25546
25684
|
while (piece.lastIndex < text.length) {
|
|
25547
25685
|
const m = piece.exec(text);
|
|
25548
25686
|
if (!m) return void 0;
|
|
25549
|
-
const [, single, double, escaped,
|
|
25687
|
+
const [, single, double, escaped, plain3, space] = m;
|
|
25550
25688
|
if (space !== void 0) {
|
|
25551
25689
|
if (word !== void 0) words2.push(word);
|
|
25552
25690
|
word = void 0;
|
|
25553
25691
|
continue;
|
|
25554
25692
|
}
|
|
25555
|
-
word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ??
|
|
25693
|
+
word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain3));
|
|
25556
25694
|
}
|
|
25557
25695
|
if (word !== void 0) words2.push(word);
|
|
25558
25696
|
return words2;
|
|
@@ -26745,16 +26883,52 @@ var Launcher = class {
|
|
|
26745
26883
|
for (const bin of planned?.bins ?? []) await need(g, "exec", bin, R_OK | X_OK);
|
|
26746
26884
|
if (planned?.target.copy) await need(g, "exec", planned.target.copy, R_OK | X_OK);
|
|
26747
26885
|
}
|
|
26748
|
-
async checkedPlan(req,
|
|
26749
|
-
|
|
26886
|
+
async checkedPlan(req, ctx, fds) {
|
|
26887
|
+
try {
|
|
26888
|
+
return await this.planOrSearch(req, ctx);
|
|
26889
|
+
} catch (err) {
|
|
26750
26890
|
await fds.closeAll();
|
|
26751
|
-
throw err
|
|
26891
|
+
throw err;
|
|
26892
|
+
}
|
|
26893
|
+
}
|
|
26894
|
+
async planOrSearch(req, ctx) {
|
|
26895
|
+
const bare = !req.file.includes("/") && !COMMAND2.test(req.file);
|
|
26896
|
+
try {
|
|
26897
|
+
return await this.planOne(req, ctx);
|
|
26898
|
+
} catch (err) {
|
|
26899
|
+
const code = codeOf7(err);
|
|
26900
|
+
const searched = bare && req.search !== false && (code === "ENOENT" || code === "ENOEXEC");
|
|
26901
|
+
if (!searched || await this.commandAt(req.file, ctx.scope)) throw err;
|
|
26902
|
+
const found = await this.onPath(req, ctx);
|
|
26903
|
+
if (found) return found;
|
|
26904
|
+
throw err;
|
|
26905
|
+
}
|
|
26906
|
+
}
|
|
26907
|
+
async onPath(req, ctx) {
|
|
26908
|
+
const path = req.env.PATH;
|
|
26909
|
+
if (typeof path !== "string") return void 0;
|
|
26910
|
+
let denied2 = false;
|
|
26911
|
+
for (const dir of path.split(":")) {
|
|
26912
|
+
const file = dir === "" ? `./${req.file}` : `${dir.replace(/\/+$/, "")}/${req.file}`;
|
|
26913
|
+
try {
|
|
26914
|
+
return await this.planOne({ ...req, file }, ctx);
|
|
26915
|
+
} catch (err) {
|
|
26916
|
+
const code = codeOf7(err);
|
|
26917
|
+
if (code === "EACCES") denied2 = true;
|
|
26918
|
+
else if (code !== "ENOENT" && code !== "ENOTDIR") throw err;
|
|
26919
|
+
}
|
|
26920
|
+
}
|
|
26921
|
+
if (denied2) throw new SpawnError("EACCES");
|
|
26922
|
+
return void 0;
|
|
26923
|
+
}
|
|
26924
|
+
async planOne(req, { ppid, cred, scope }) {
|
|
26925
|
+
const refused3 = (err) => {
|
|
26926
|
+
throw codeOf7(err) === "EACCES" ? new SpawnError("EACCES") : err;
|
|
26752
26927
|
};
|
|
26753
26928
|
const file = await this.execFile(req.file, req.cwd, this.execGuard(cred, ppid)).catch(refused3);
|
|
26754
26929
|
const planned = await this.plan(file, req.argv, req.cwd, scope, req.argv0);
|
|
26755
26930
|
await this.mayExec(cred, { ...req, file, pid: ppid }, planned).catch(refused3);
|
|
26756
26931
|
if (planned) return planned;
|
|
26757
|
-
await fds.closeAll();
|
|
26758
26932
|
throw new SpawnError(await this.unrunnable(file, req.cwd, scope));
|
|
26759
26933
|
}
|
|
26760
26934
|
async enterable(g, cwd) {
|
|
@@ -26921,12 +27095,12 @@ var Launcher = class {
|
|
|
26921
27095
|
return `${path}:${st.size}:${st.mtime.getTime()}`;
|
|
26922
27096
|
}
|
|
26923
27097
|
verified(path, caller, fs = this.base) {
|
|
26924
|
-
const
|
|
26925
|
-
return this.integrity ? this.integrity.read(path, caller,
|
|
27098
|
+
const plain3 = () => fs.readFileBuffer(path);
|
|
27099
|
+
return this.integrity ? this.integrity.read(path, caller, plain3) : plain3();
|
|
26926
27100
|
}
|
|
26927
27101
|
keyed(path, caller, asked) {
|
|
26928
|
-
const
|
|
26929
|
-
return this.integrity ? this.integrity.readKeyed(path, caller,
|
|
27102
|
+
const plain3 = async () => this.base.readFileBuffer(path);
|
|
27103
|
+
return this.integrity ? this.integrity.readKeyed(path, caller, plain3, asked) : plain3().then((bytes) => ({ bytes }));
|
|
26930
27104
|
}
|
|
26931
27105
|
cached(cache, key, load) {
|
|
26932
27106
|
const hit = cache.get(key);
|
|
@@ -27123,7 +27297,7 @@ var Launcher = class {
|
|
|
27123
27297
|
});
|
|
27124
27298
|
return {
|
|
27125
27299
|
source: {
|
|
27126
|
-
read: (path,
|
|
27300
|
+
read: (path, plain3) => integrity.readSource(path, caller(), plain3),
|
|
27127
27301
|
open: (path, real) => integrity.openSource(path, caller(), real)
|
|
27128
27302
|
}
|
|
27129
27303
|
};
|
|
@@ -28030,9 +28204,9 @@ var Launcher = class {
|
|
|
28030
28204
|
}
|
|
28031
28205
|
);
|
|
28032
28206
|
}
|
|
28033
|
-
environment(cwd, extra, account,
|
|
28207
|
+
environment(cwd, extra, account, login2 = false) {
|
|
28034
28208
|
const env = {
|
|
28035
|
-
...
|
|
28209
|
+
...login2 ? networkEnv(this.hostname) : this.env,
|
|
28036
28210
|
...userEnv(account, this.pnpmHome),
|
|
28037
28211
|
PWD: cwd,
|
|
28038
28212
|
...extra
|
|
@@ -28259,6 +28433,9 @@ var Launcher = class {
|
|
|
28259
28433
|
function cronLocks() {
|
|
28260
28434
|
return globalThis.navigator?.locks;
|
|
28261
28435
|
}
|
|
28436
|
+
function codeOf7(err) {
|
|
28437
|
+
return err?.code;
|
|
28438
|
+
}
|
|
28262
28439
|
|
|
28263
28440
|
// src/pin.ts
|
|
28264
28441
|
function pinScript(url) {
|