@ai-ecoverse/slicc-kernel 1.136.3 → 1.136.5
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 +23 -3
- package/dist/kernel-worker.js +175 -31
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +1 -1
- package/dist/node.js +175 -31
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +1 -1
- package/dist/types/commands.d.ts +5 -0
- package/dist/types/kernel/net/sealed-binding.d.ts +2 -0
- package/dist/types/kernel/net/sealed-conn.d.ts +1 -0
- package/dist/types/kernel/net/sealed-pg.d.ts +5 -0
- package/dist/types/kernel/net/sealed-scram.d.ts +8 -0
- package/dist/types/launcher.d.ts +2 -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 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. A searched bare name is never looked up in the working directory itself: a file of that name there runs only through an empty `PATH` entry, as with `execvp` ([#1234](https://github.com/ai-ecoverse/slicc-kernel/issues/1234)). 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.
|
|
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. The refusal follows the program, not the name: its glue or `.wasm` run by path (directly or through a symlink), and a byte-identical copy of its glue beside it named for one of its commands (#390, #899), exit 126 with the same message ([#1235](https://github.com/ai-ecoverse/slicc-kernel/issues/1235)). 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. A searched bare name is never looked up in the working directory itself: a file of that name there runs only through an empty `PATH` entry, as with `execvp` ([#1234](https://github.com/ai-ecoverse/slicc-kernel/issues/1234)). 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
|
|
|
@@ -505,10 +505,20 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
505
505
|
- `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.
|
|
506
506
|
- 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.
|
|
507
507
|
- **pg:** a session that starts with a protocol 3 `StartupMessage` (after `sslmode`'s `SSLRequest` upgrade, or with direct TLS) is followed message by message. The only position is the **`PasswordMessage` answering `AuthenticationCleartextPassword` (`R` 3)**, as the program read it from the server. The password is substituted (`password-cleartext`) and the message is framed again. **SCRAM-SHA-256** (`R` 10, the pg 14+ default) works with an unmodified client:
|
|
508
|
-
- **What the
|
|
508
|
+
- **What the program is offered:** the server's mechanisms. `SCRAM-SHA-256-PLUS` is offered only when the kernel can bind to the server: a verified session whose leaf certificate's signature hash is SHA-256, SHA-384 or SHA-512, with MD5 and SHA-1 counting as SHA-256 (RFC 5929 §4.1). With any other hash (RSA-PSS or Ed25519, say), `-PLUS` is dropped. So libpq's default `channel_binding=prefer`, `require`, and node-postgres' `enableChannelBinding` all work.
|
|
509
|
+
- **Channel binding (`tls-server-end-point`):** the kernel changes only what the server would refuse.
|
|
510
|
+
- `n,,` goes out as written.
|
|
511
|
+
- `y,,` goes out as written to a server that didn't offer `-PLUS`. Otherwise it becomes `SCRAM-SHA-256-PLUS` with `p=tls-server-end-point,,` (or `n,,` when the kernel can't bind), because pg refuses `y,,` from a client after offering `-PLUS`.
|
|
512
|
+
- `p=tls-server-end-point,,` keeps its header. Its `c=` binding data becomes the hash of the leaf the kernel verified, when the program's differs.
|
|
513
|
+
- Any other `p=` type is changed to the kernel's choice.
|
|
514
|
+
|
|
515
|
+
So a program that asks for binding is bound to the real server. A program using its own password in such a session still authenticates whenever nothing had to change: `n,,`, `y,,` without `-PLUS`, or a right `p=`.
|
|
516
|
+
- **The vault:** substitution with `-PLUS` (a program's `p=tls-server-end-point`) needs slicc-vault ≥ 1.15.1. An older vault refuses `p=` with `EINVAL` and fails closed. A program's `p=tls-unique` or `p=tls-exporter` fails in a substituting session either way: the vault refuses it with a placeholder (`EINVAL`), and with the program's own password, the kernel's rewrite to `tls-server-end-point` invalidates its proof. PostgreSQL supports only `tls-server-end-point` anyway.
|
|
517
|
+
- **Two auth messages:** the vault and the check against the placeholder see the program's own messages (P). The server sees the kernel's (S), with its header and `c=`. The real proof is computed over S, and the server's signature is verified over S before the program gets the placeholder's signature over P.
|
|
509
518
|
- **The match:** the kernel sends the three SCRAM messages and that proof to the vault (`scram-sha-256`, with no placeholders), and the vault names the one bound placeholder whose proof matches.
|
|
510
519
|
- **The exchange:** the kernel recomputes the proof from the value and sends that instead. It checks the server's final signature against the value **before** it hands the program the signature its own placeholder-based check expects. A server whose signature doesn't verify, or that answers with an error, ends the session (`EPROTO`) without passing it on. The salted password and the derived keys are wiped after the exchange.
|
|
511
|
-
- **Requirements:**
|
|
520
|
+
- **Requirements:** a GS2 header without an authorization identity (`n,,`, `y,,` or `p=<type>,,`), whose `c=` starts with it, and the bare RFC 5802 messages; at most 100000 iterations.
|
|
521
|
+
- **A stale vault value:** on a verified session, a value the server no longer accepts makes its final signature fail to verify. The program sees `EPROTO`, not pg's `28P01`. This is by design: the kernel never passes on a signature it couldn't check.
|
|
512
522
|
|
|
513
523
|
Anything else goes out as written: the startup, an MD5 (`R` 5) answer, every later message, and a frame over 16 KiB. A server whose `pg_hba` asks for MD5 gets the placeholder, and refuses.
|
|
514
524
|
- **Vault anchors** (§3.19.6) are for private-CA services such as managed pg and corporate APIs.
|
|
@@ -1266,6 +1276,16 @@ Not part of the run yet: the rest of bios's setup (the `rest` shard, the upgrade
|
|
|
1266
1276
|
|
|
1267
1277
|
The WASIX fixtures of `test/integration/epoll.test.mjs` (#674), under `test/integration/fixtures/`, are committed with their sources, build scripts and built wasm.
|
|
1268
1278
|
|
|
1279
|
+
### `pg-fixture` with `plus`
|
|
1280
|
+
|
|
1281
|
+
`test/integration/pg-fixture.mjs`, `pgFixture(t, cert, password, { plus: true })`: pg-gateway's SCRAM-SHA-256 server, which offers `SCRAM-SHA-256-PLUS` too and refuses as PostgreSQL's `auth-scram.c` does (jsh#531):
|
|
1282
|
+
- `y,,` after it offered `-PLUS`: "SCRAM channel binding negotiation error";
|
|
1283
|
+
- `p=` with `SCRAM-SHA-256`, and `SCRAM-SHA-256-PLUS` without `p=`;
|
|
1284
|
+
- a channel-binding type other than `tls-server-end-point`;
|
|
1285
|
+
- a `c=` other than base64 of the GS2 header plus, for `-PLUS`, the SHA-256 of its leaf.
|
|
1286
|
+
|
|
1287
|
+
The refusals and the client-final messages are recorded in `seen.plus`. The real-server check is slicc-jsh's, against PostgreSQL over SSL.
|
|
1288
|
+
|
|
1269
1289
|
### `heldcwd`
|
|
1270
1290
|
|
|
1271
1291
|
`test/integration/fixtures/heldcwd`: a Rust wasm32-wasip1 program, built by `build.sh` with rustup's wasm32-wasip1 target, for the [working directory](#the-working-directory) rows (#1164).
|
package/dist/kernel-worker.js
CHANGED
|
@@ -7098,7 +7098,7 @@ function satisfies(version, range2) {
|
|
|
7098
7098
|
}
|
|
7099
7099
|
|
|
7100
7100
|
// src/version.ts
|
|
7101
|
-
var KERNEL_VERSION = true ? "1.136.
|
|
7101
|
+
var KERNEL_VERSION = true ? "1.136.5" : "0.0.0-dev";
|
|
7102
7102
|
var OS_RELEASE = "6.0.0-slicc";
|
|
7103
7103
|
function procVersion() {
|
|
7104
7104
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -18560,6 +18560,66 @@ async function anchorFor(engine, chain2, host, found) {
|
|
|
18560
18560
|
return void 0;
|
|
18561
18561
|
}
|
|
18562
18562
|
|
|
18563
|
+
// src/kernel/net/sealed-binding.ts
|
|
18564
|
+
var HASH_OF_SIGNATURE = {
|
|
18565
|
+
"1.2.840.113549.1.1.4": "SHA-256",
|
|
18566
|
+
"1.2.840.113549.1.1.5": "SHA-256",
|
|
18567
|
+
"1.2.840.10045.4.1": "SHA-256",
|
|
18568
|
+
"1.2.840.10040.4.3": "SHA-256",
|
|
18569
|
+
"1.2.840.113549.1.1.11": "SHA-256",
|
|
18570
|
+
"1.2.840.10045.4.3.2": "SHA-256",
|
|
18571
|
+
"1.2.840.113549.1.1.12": "SHA-384",
|
|
18572
|
+
"1.2.840.10045.4.3.3": "SHA-384",
|
|
18573
|
+
"1.2.840.113549.1.1.13": "SHA-512",
|
|
18574
|
+
"1.2.840.10045.4.3.4": "SHA-512"
|
|
18575
|
+
};
|
|
18576
|
+
function tlv3(der2, at) {
|
|
18577
|
+
const tag = der2[at];
|
|
18578
|
+
let len = der2[at + 1];
|
|
18579
|
+
if (tag === void 0 || len === void 0) return void 0;
|
|
18580
|
+
let start2 = at + 2;
|
|
18581
|
+
if (len & 128) {
|
|
18582
|
+
const n = len & 127;
|
|
18583
|
+
if (n === 0 || n > 4) return void 0;
|
|
18584
|
+
len = 0;
|
|
18585
|
+
for (let i = 0; i < n; i++) len = len * 256 + (der2[start2 + i] ?? 0);
|
|
18586
|
+
start2 += n;
|
|
18587
|
+
}
|
|
18588
|
+
const end = start2 + len;
|
|
18589
|
+
return end <= der2.length ? { tag, start: start2, end } : void 0;
|
|
18590
|
+
}
|
|
18591
|
+
function oidText(bytes) {
|
|
18592
|
+
const first = bytes[0] ?? 0;
|
|
18593
|
+
const parts2 = [Math.floor(first / 40), first % 40];
|
|
18594
|
+
let value2 = 0;
|
|
18595
|
+
for (const b of bytes.subarray(1)) {
|
|
18596
|
+
value2 = value2 * 128 + (b & 127);
|
|
18597
|
+
if ((b & 128) === 0) {
|
|
18598
|
+
parts2.push(value2);
|
|
18599
|
+
value2 = 0;
|
|
18600
|
+
}
|
|
18601
|
+
}
|
|
18602
|
+
return parts2.join(".");
|
|
18603
|
+
}
|
|
18604
|
+
function signatureOid(cert) {
|
|
18605
|
+
const outer = tlv3(cert, 0);
|
|
18606
|
+
if (outer?.tag !== 48) return void 0;
|
|
18607
|
+
const tbs = tlv3(cert, outer.start);
|
|
18608
|
+
if (tbs?.tag !== 48) return void 0;
|
|
18609
|
+
const algorithm = tlv3(cert, tbs.end);
|
|
18610
|
+
if (algorithm?.tag !== 48) return void 0;
|
|
18611
|
+
const oid2 = tlv3(cert, algorithm.start);
|
|
18612
|
+
if (oid2?.tag !== 6) return void 0;
|
|
18613
|
+
return oidText(cert.subarray(oid2.start, oid2.end));
|
|
18614
|
+
}
|
|
18615
|
+
async function endPointBinding(cert) {
|
|
18616
|
+
if (!cert) return void 0;
|
|
18617
|
+
const oid2 = signatureOid(cert);
|
|
18618
|
+
const hash = oid2 === void 0 ? void 0 : HASH_OF_SIGNATURE[oid2];
|
|
18619
|
+
if (!hash) return void 0;
|
|
18620
|
+
return new Uint8Array(await crypto.subtle.digest(hash, cert));
|
|
18621
|
+
}
|
|
18622
|
+
|
|
18563
18623
|
// node_modules/@ai-ecoverse/slicc-scrub/dist/index.js
|
|
18564
18624
|
var FRAGMENT = 16;
|
|
18565
18625
|
var MIN_VALUE = 8;
|
|
@@ -19295,25 +19355,35 @@ function base64Of(bytes) {
|
|
|
19295
19355
|
for (const b of bytes) bin += String.fromCharCode(b);
|
|
19296
19356
|
return btoa(bin);
|
|
19297
19357
|
}
|
|
19298
|
-
function
|
|
19299
|
-
const m = /^n,,(n=[^,]*,r=[^,]+)$/.exec(message3);
|
|
19300
|
-
return m ? m[1] : void 0;
|
|
19358
|
+
function clientFirstOf(message3) {
|
|
19359
|
+
const m = /^((?:n|y|p=[A-Za-z0-9.-]+),,)(n=[^,]*,r=[^,]+)$/.exec(message3);
|
|
19360
|
+
return m ? { header: m[1], bare: m[2] } : void 0;
|
|
19361
|
+
}
|
|
19362
|
+
var latinBytes = (text) => Uint8Array.from(text, (c) => c.charCodeAt(0));
|
|
19363
|
+
function channelOf(header3, binding) {
|
|
19364
|
+
const head = latinBytes(header3);
|
|
19365
|
+
const all = new Uint8Array(head.length + (binding?.length ?? 0));
|
|
19366
|
+
all.set(head);
|
|
19367
|
+
if (binding) all.set(binding, head.length);
|
|
19368
|
+
return base64Of(all);
|
|
19301
19369
|
}
|
|
19302
19370
|
var SERVER_FIRST = /^r=([^,]+),s=([^,]+),i=(\d{1,6})$/;
|
|
19303
|
-
var CLIENT_FINAL = /^(c=
|
|
19371
|
+
var CLIENT_FINAL = /^(c=([A-Za-z0-9+/]+={0,2}),r=([^,]+)),p=([^,]+)$/;
|
|
19304
19372
|
var SCRAM_MAX = 1024;
|
|
19305
19373
|
function exchangeOf(clientFirst, serverFirst, clientFinal) {
|
|
19306
|
-
const
|
|
19374
|
+
const first = clientFirstOf(clientFirst);
|
|
19375
|
+
const bare = first?.bare;
|
|
19307
19376
|
const sf = SERVER_FIRST.exec(serverFirst);
|
|
19308
19377
|
const fin = CLIENT_FINAL.exec(clientFinal);
|
|
19309
19378
|
if (!bare || !sf || !fin || [bare, serverFirst, clientFinal].some((t) => t.length > SCRAM_MAX))
|
|
19310
19379
|
return void 0;
|
|
19380
|
+
const channel = base64Bytes(fin[2]);
|
|
19311
19381
|
const cnonce = bare.slice(bare.indexOf(",r=") + 3);
|
|
19312
19382
|
const nonce = sf[1];
|
|
19313
19383
|
const salt = base64Bytes(sf[2]);
|
|
19314
19384
|
const iterations = Number(sf[3]);
|
|
19315
|
-
const proof = base64Bytes(fin[
|
|
19316
|
-
const ok4 = fin[
|
|
19385
|
+
const proof = base64Bytes(fin[4]);
|
|
19386
|
+
const ok4 = channel !== void 0 && latin(channel).startsWith(first.header) && fin[3] === nonce && nonce.startsWith(cnonce) && iterations >= 1 && iterations <= 1e5 && salt !== void 0 && proof?.length === 32;
|
|
19317
19387
|
if (!ok4) return void 0;
|
|
19318
19388
|
return {
|
|
19319
19389
|
exchange: {
|
|
@@ -19323,9 +19393,16 @@ function exchangeOf(clientFirst, serverFirst, clientFinal) {
|
|
|
19323
19393
|
salt,
|
|
19324
19394
|
iterations
|
|
19325
19395
|
},
|
|
19326
|
-
proof
|
|
19396
|
+
proof,
|
|
19397
|
+
channel,
|
|
19398
|
+
nonce
|
|
19327
19399
|
};
|
|
19328
19400
|
}
|
|
19401
|
+
function latin(bytes) {
|
|
19402
|
+
let out = "";
|
|
19403
|
+
for (const b of bytes) out += String.fromCharCode(b);
|
|
19404
|
+
return out;
|
|
19405
|
+
}
|
|
19329
19406
|
async function hmac(key, data) {
|
|
19330
19407
|
const k = await crypto.subtle.importKey(
|
|
19331
19408
|
"raw",
|
|
@@ -19392,16 +19469,23 @@ function sniffPg(start2) {
|
|
|
19392
19469
|
const length2 = int32(bytes, 0);
|
|
19393
19470
|
return length2 >= 8 && length2 <= PG_MESSAGE_MAX && int32(bytes, 4) === PROTOCOL_3 ? "yes" : "no";
|
|
19394
19471
|
}
|
|
19472
|
+
var PLAIN = "SCRAM-SHA-256";
|
|
19395
19473
|
var PLUS = "SCRAM-SHA-256-PLUS";
|
|
19474
|
+
var END_POINT = "p=tls-server-end-point,,";
|
|
19396
19475
|
var SASL = 10;
|
|
19397
19476
|
var SASL_CONTINUE = 11;
|
|
19398
19477
|
var SASL_FINAL = 12;
|
|
19399
|
-
function
|
|
19478
|
+
function latin2(bytes) {
|
|
19400
19479
|
let out = "";
|
|
19401
19480
|
for (const b of bytes) out += String.fromCharCode(b);
|
|
19402
19481
|
return out;
|
|
19403
19482
|
}
|
|
19404
19483
|
var PgServer = class {
|
|
19484
|
+
binding;
|
|
19485
|
+
plus = false;
|
|
19486
|
+
constructor(binding) {
|
|
19487
|
+
this.binding = binding;
|
|
19488
|
+
}
|
|
19405
19489
|
buffer = new Uint8Array(0);
|
|
19406
19490
|
skip = 0;
|
|
19407
19491
|
cleartext = false;
|
|
@@ -19415,20 +19499,22 @@ var PgServer = class {
|
|
|
19415
19499
|
if (this.expect && code !== SASL_FINAL) this.failed = true;
|
|
19416
19500
|
this.cleartext = message3.length === 9 && code === CLEARTEXT;
|
|
19417
19501
|
if (code === SASL) return this.mechanisms(message3);
|
|
19418
|
-
else if (code === SASL_CONTINUE) this.serverFirst =
|
|
19502
|
+
else if (code === SASL_CONTINUE) this.serverFirst = latin2(message3.subarray(9));
|
|
19419
19503
|
else if (code === SASL_FINAL) this.final(message3);
|
|
19420
19504
|
return message3;
|
|
19421
19505
|
}
|
|
19422
19506
|
mechanisms(message3) {
|
|
19423
19507
|
this.sasl = true;
|
|
19424
|
-
const
|
|
19425
|
-
|
|
19508
|
+
const offered = latin2(message3.subarray(9)).split("\0").filter((n) => n !== "");
|
|
19509
|
+
this.plus = offered.includes(PLUS);
|
|
19510
|
+
const names2 = offered.filter((n) => n !== PLUS || this.binding !== void 0);
|
|
19511
|
+
return frame3(82, `${latin2(message3.subarray(5, 9))}${names2.map((n) => `${n}\0`).join("")}\0`);
|
|
19426
19512
|
}
|
|
19427
19513
|
final(message3) {
|
|
19428
19514
|
const expect = this.expect;
|
|
19429
19515
|
if (!expect) return;
|
|
19430
19516
|
this.expect = void 0;
|
|
19431
|
-
const data =
|
|
19517
|
+
const data = latin2(message3.subarray(9));
|
|
19432
19518
|
if (data !== expect.real || expect.fake.length !== expect.real.length) {
|
|
19433
19519
|
this.failed = true;
|
|
19434
19520
|
return;
|
|
@@ -19503,9 +19589,9 @@ function pgLength(bytes, started) {
|
|
|
19503
19589
|
const length2 = int32(bytes, started ? 1 : 0);
|
|
19504
19590
|
return started ? 1 + length2 : length2;
|
|
19505
19591
|
}
|
|
19506
|
-
var MECHANISMS = /* @__PURE__ */ new Set([
|
|
19592
|
+
var MECHANISMS = /* @__PURE__ */ new Set([PLAIN, PLUS]);
|
|
19507
19593
|
async function cleartext(message3, asks) {
|
|
19508
|
-
const body =
|
|
19594
|
+
const body = latin2(message3.subarray(5));
|
|
19509
19595
|
if (!body.endsWith("\0") || body.indexOf("\0") !== body.length - 1)
|
|
19510
19596
|
return { bytes: message3, done: true };
|
|
19511
19597
|
const password = body.slice(0, -1);
|
|
@@ -19517,14 +19603,30 @@ async function cleartext(message3, asks) {
|
|
|
19517
19603
|
return { bytes: frame3(112, `${replaced(password, values)}\0`), values, done: true };
|
|
19518
19604
|
}
|
|
19519
19605
|
function initial(message3) {
|
|
19520
|
-
const body =
|
|
19606
|
+
const body = latin2(message3.subarray(5));
|
|
19521
19607
|
const nul = body.indexOf("\0");
|
|
19522
|
-
|
|
19608
|
+
const mechanism = body.slice(0, nul);
|
|
19609
|
+
if (nul < 0 || !MECHANISMS.has(mechanism) || body.length < nul + 5) return void 0;
|
|
19523
19610
|
const data = message3.subarray(5 + nul + 5);
|
|
19524
|
-
return int32(message3, 5 + nul + 1) === data.length ?
|
|
19611
|
+
return int32(message3, 5 + nul + 1) === data.length ? { mechanism, data: latin2(data) } : void 0;
|
|
19612
|
+
}
|
|
19613
|
+
function int32Text(n) {
|
|
19614
|
+
return String.fromCharCode(n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255);
|
|
19615
|
+
}
|
|
19616
|
+
function serverHeader(header3, server) {
|
|
19617
|
+
if (header3 === "n,,") return header3;
|
|
19618
|
+
if (header3 === "y,," && !server.plus) return header3;
|
|
19619
|
+
if (header3 === END_POINT && server.binding) return header3;
|
|
19620
|
+
return server.plus && server.binding ? END_POINT : "n,,";
|
|
19621
|
+
}
|
|
19622
|
+
function rewritten(first, header3) {
|
|
19623
|
+
const parsed2 = clientFirstOf(first.data);
|
|
19624
|
+
if (!parsed2 || parsed2.header === header3) return first;
|
|
19625
|
+
return { mechanism: header3 === END_POINT ? PLUS : PLAIN, data: `${header3}${parsed2.bare}` };
|
|
19525
19626
|
}
|
|
19526
19627
|
var PgClient = class {
|
|
19527
19628
|
clientFirst;
|
|
19629
|
+
header = "n,,";
|
|
19528
19630
|
server;
|
|
19529
19631
|
asks;
|
|
19530
19632
|
constructor(server, asks) {
|
|
@@ -19539,17 +19641,28 @@ var PgClient = class {
|
|
|
19539
19641
|
return cleartext(message3, this.asks);
|
|
19540
19642
|
}
|
|
19541
19643
|
if (server.sasl && this.clientFirst === void 0) {
|
|
19542
|
-
|
|
19543
|
-
|
|
19644
|
+
const first = initial(message3);
|
|
19645
|
+
const parsed2 = first && clientFirstOf(first.data);
|
|
19646
|
+
if (!first || !parsed2) return { bytes: message3, done: true };
|
|
19647
|
+
this.clientFirst = first.data;
|
|
19648
|
+
this.header = serverHeader(parsed2.header, server);
|
|
19649
|
+
const sent = rewritten(first, this.header);
|
|
19650
|
+
if (sent === first) return { bytes: message3, done: false };
|
|
19651
|
+
return {
|
|
19652
|
+
bytes: frame3(112, `${sent.mechanism}\0${int32Text(sent.data.length)}${sent.data}`),
|
|
19653
|
+
done: false
|
|
19654
|
+
};
|
|
19544
19655
|
}
|
|
19545
19656
|
if (this.clientFirst !== void 0 && server.serverFirst !== void 0)
|
|
19546
19657
|
return this.final(message3, this.clientFirst, server.serverFirst);
|
|
19547
19658
|
return { bytes: message3, done: true };
|
|
19548
19659
|
}
|
|
19549
19660
|
async final(message3, first, serverFirst) {
|
|
19550
|
-
const got = exchangeOf(first, serverFirst,
|
|
19661
|
+
const got = exchangeOf(first, serverFirst, latin2(message3.subarray(5)));
|
|
19551
19662
|
if (!got) return { bytes: message3, done: true };
|
|
19552
19663
|
const x = got.exchange;
|
|
19664
|
+
const binding = this.header === END_POINT ? this.server.binding : void 0;
|
|
19665
|
+
const serverFinal = `c=${channelOf(this.header, binding)},r=${got.nonce}`;
|
|
19553
19666
|
const values = usable(
|
|
19554
19667
|
await this.asks.scram({
|
|
19555
19668
|
clientFirstBare: x.clientFirstBare,
|
|
@@ -19559,16 +19672,19 @@ var PgClient = class {
|
|
|
19559
19672
|
})
|
|
19560
19673
|
);
|
|
19561
19674
|
const [entry, ...more] = Object.entries(values);
|
|
19562
|
-
|
|
19675
|
+
const own = { bytes: frame3(112, `${serverFinal},p=${base64Of(got.proof)}`), done: true };
|
|
19676
|
+
if (!entry || more.length > 0)
|
|
19677
|
+
return serverFinal === x.clientFinalBare ? { bytes: message3, done: true } : own;
|
|
19563
19678
|
const [placeholder, value2] = entry;
|
|
19564
19679
|
const fake = await scramKeys(placeholder, x);
|
|
19565
|
-
if (!sameBytes(fake.proof, got.proof))
|
|
19566
|
-
|
|
19680
|
+
if (!sameBytes(fake.proof, got.proof))
|
|
19681
|
+
return serverFinal === x.clientFinalBare ? { bytes: message3, done: true } : own;
|
|
19682
|
+
const real = await scramKeys(saslprep(value2), { ...x, clientFinalBare: serverFinal });
|
|
19567
19683
|
this.server.expect = {
|
|
19568
19684
|
real: `v=${base64Of(real.serverSignature)}`,
|
|
19569
19685
|
fake: `v=${base64Of(fake.serverSignature)}`
|
|
19570
19686
|
};
|
|
19571
|
-
const bytes = frame3(112, `${
|
|
19687
|
+
const bytes = frame3(112, `${serverFinal},p=${base64Of(real.proof)}`);
|
|
19572
19688
|
for (const k of [real.proof, real.serverSignature, fake.proof, fake.serverSignature]) k.fill(0);
|
|
19573
19689
|
return { bytes, values, done: true };
|
|
19574
19690
|
}
|
|
@@ -19967,7 +20083,7 @@ function idle() {
|
|
|
19967
20083
|
return new Promise((resolve2) => setTimeout(() => resolve2(IDLE2), SCRUB_IDLE_MS));
|
|
19968
20084
|
}
|
|
19969
20085
|
function sealedConn(conn, s) {
|
|
19970
|
-
const pg = new PgServer();
|
|
20086
|
+
const pg = new PgServer(s.binding);
|
|
19971
20087
|
const scope = s.broker.sessions.scope(s.pid, s.host);
|
|
19972
20088
|
const writer = substitutes(s) ? new SessionWriter(conn, s, pg, scope) : void 0;
|
|
19973
20089
|
const observed = {
|
|
@@ -20258,6 +20374,8 @@ function secure(prepared, seal) {
|
|
|
20258
20374
|
const session = sealing({ ...seal, engine }, raw2, made2.info, prepared.options.servername);
|
|
20259
20375
|
if (!session) return made2;
|
|
20260
20376
|
made2.info.substitutes = substitutes(session);
|
|
20377
|
+
if (made2.info.substitutes && session.verified)
|
|
20378
|
+
session.binding = await endPointBinding(made2.info.peerCertificates[0]);
|
|
20261
20379
|
return { conn: sealedConn(made2.conn, session), info: made2.info };
|
|
20262
20380
|
} finally {
|
|
20263
20381
|
prepared.own?.release();
|
|
@@ -26718,7 +26836,8 @@ function refusing(command) {
|
|
|
26718
26836
|
argv0: command.name,
|
|
26719
26837
|
args: [`${command.name}: ${command.refused}`],
|
|
26720
26838
|
refused: command.refused,
|
|
26721
|
-
shows: command.script ?? command.wasm
|
|
26839
|
+
shows: command.script ?? command.wasm,
|
|
26840
|
+
origin: { abi: command.abi, glue: command.glue, wasm: command.wasm }
|
|
26722
26841
|
};
|
|
26723
26842
|
}
|
|
26724
26843
|
|
|
@@ -26734,7 +26853,7 @@ var RULE = new RegExp(
|
|
|
26734
26853
|
String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()\s]+)\)\s+)?(?:Cwd\s+(${WORD})\s+)?Cmnd(?:\s+(.*))?$`
|
|
26735
26854
|
);
|
|
26736
26855
|
var TERM = /^[A-Za-z0-9._+-]+$/;
|
|
26737
|
-
var
|
|
26856
|
+
var PLAIN2 = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
26738
26857
|
var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
|
|
26739
26858
|
function failed2(errno3, error) {
|
|
26740
26859
|
return { error, errno: errno3 };
|
|
@@ -26761,7 +26880,7 @@ function splitWords(text) {
|
|
|
26761
26880
|
return words2;
|
|
26762
26881
|
}
|
|
26763
26882
|
function quoteWord(word) {
|
|
26764
|
-
return
|
|
26883
|
+
return PLAIN2.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
|
|
26765
26884
|
}
|
|
26766
26885
|
function validWho(who) {
|
|
26767
26886
|
if (who === "ALL") return true;
|
|
@@ -27810,7 +27929,7 @@ var Launcher = class {
|
|
|
27810
27929
|
const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked2)?.argv0Path === true;
|
|
27811
27930
|
return target && byPath ? { ...target, argv0: path } : target;
|
|
27812
27931
|
}
|
|
27813
|
-
const own = await this.
|
|
27932
|
+
const own = await this.ownOrRefused(glue, baseName2(file), scope);
|
|
27814
27933
|
if (own) return own.script ? void 0 : { ...targetOf(own), ...asked };
|
|
27815
27934
|
const head = await this.head(glue);
|
|
27816
27935
|
if (!head) return void 0;
|
|
@@ -27834,6 +27953,31 @@ var Launcher = class {
|
|
|
27834
27953
|
if (!owner) return void 0;
|
|
27835
27954
|
return { ...targetOf(owner), ...asked, ...owner.glue === glue ? {} : { copy: glue } };
|
|
27836
27955
|
}
|
|
27956
|
+
async ownOrRefused(path, name2, scope) {
|
|
27957
|
+
return await this.commandAt(path, scope) ?? await this.refusedAt(path, name2, scope);
|
|
27958
|
+
}
|
|
27959
|
+
async refusedAt(path, name2, scope) {
|
|
27960
|
+
const refused3 = [...(await this.commands(scope)).values()].filter((c) => c.origin);
|
|
27961
|
+
if (refused3.length === 0 || !await this.base.exists(path)) return void 0;
|
|
27962
|
+
const real = await physicalPathAsync(this.base, path).catch(() => path);
|
|
27963
|
+
const at = async (c, which) => {
|
|
27964
|
+
const own = c.origin[which];
|
|
27965
|
+
return own === path || await physicalPathAsync(this.base, own).catch(() => own) === real;
|
|
27966
|
+
};
|
|
27967
|
+
const owners = [];
|
|
27968
|
+
for (const c of refused3) {
|
|
27969
|
+
if (await at(c, "glue") || await at(c, "wasm")) owners.push(c);
|
|
27970
|
+
}
|
|
27971
|
+
if (owners.length > 0) return owners.find((c) => c.name === name2) ?? owners[0];
|
|
27972
|
+
const dir = real.slice(0, real.lastIndexOf("/"));
|
|
27973
|
+
for (const c of refused3) {
|
|
27974
|
+
const origin = c.origin;
|
|
27975
|
+
if (origin.abi !== "emscripten" || c.name !== name2) continue;
|
|
27976
|
+
const own = await physicalPathAsync(this.base, origin.glue).catch(() => origin.glue);
|
|
27977
|
+
if (own.slice(0, own.lastIndexOf("/")) === dir && await this.sameFile(real, own)) return c;
|
|
27978
|
+
}
|
|
27979
|
+
return void 0;
|
|
27980
|
+
}
|
|
27837
27981
|
async glueOwner(glue, name2, scope) {
|
|
27838
27982
|
const commands = [...(await this.commands(scope)).values()];
|
|
27839
27983
|
const owners = commands.filter((c) => c.abi === "emscripten" && !c.script && c.glue === glue);
|