@ai-ecoverse/slicc-vault 1.9.0 → 1.9.2

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.
Files changed (2) hide show
  1. package/README.md +9 -9
  2. package/package.json +13 -9
package/README.md CHANGED
@@ -24,7 +24,7 @@ The bundles hold this repo's code, `@ai-ecoverse/slicc-scrub`, and `@ai-ecoverse
24
24
 
25
25
  `GET /embedders.json` is answered by the Worker, not by this tree. It returns `{ "embedders": [...] }`, the same sources as `frame-ancestors`. The vault reads it at start and accepts only those origins. When it's missing or malformed, only `vault.sliccy.ai` falls back to `config.json`; on every other host (`vault-preview.sliccy.ai`, branch previews, local) that means no embedders, so it fails closed. An embedder source is `http(s)://host`, optionally with `:port` or `:*`, matched exactly.
26
26
 
27
- `main` is served at `vault.sliccy.ai`, and every other branch at `vault-<label>.sliccy.ai`. seven embeds only `vault.sliccy.ai`.
27
+ `main` is served at `vault.sliccy.ai`, and every other branch at `vault-<label>.sliccy.ai`. Releases run only on `main`: the release job (semantic-release, npm, and the deploy to `vault.sliccy.ai`) is guarded by `github.ref == 'refs/heads/main'`, also when dispatched by hand, and the branch preview publishes nothing to npm, makes no tag or release, and skips the merge queue's `gh-readonly-queue/…` branches. CI runs on pull requests, on the merge queue (`merge_group`) and on `main`; `ci / test` is the required check. A `runner` job (on a hosted runner for a fork's pull request) picks where it runs with `.github/runner.mjs`, slicc-kernel's decider: the self-hosted runner `vars.CERT_RUNNER` names (`ubuntu-latest` if it's unset; no workflow names a self-hosted label itself) for this repo's own pull requests, merge groups whose own entries (found by the group's head commit, with those ahead of it) are all such, and pushes to `main`; a hosted runner for a fork's pull request; if the decider fails, `ci / test` still runs, on a hosted runner. `vars.CI_RUNNER` overrides it. A fork that edits this workflow could still name the pool itself: that is held off by the pool's own runner-group settings, not by this file. The release and the branch previews stay on hosted runners. `workflows.test.mjs` keeps all of this so. seven embeds only `vault.sliccy.ai`.
28
28
 
29
29
  ## Storage and keys
30
30
 
@@ -164,7 +164,7 @@ iframe.contentWindow.postMessage({ type: 'slicc-vault', v: 1, op: 'hello', role:
164
164
 
165
165
  | op (page role) | fields | reply |
166
166
  |---|---|---|
167
- | `sealed` | `req` (the `/dev/sealed` request object, or its JSON text), `stamp` (`{ pid, ppid, uid, gid, tag }` from the kernel) | `{ reply }`, the §3.7 reply, below |
167
+ | `sealed` | `req` (the `/dev/sealed` request object, or its JSON text), `stamp` (the kernel's `{ pid, ppid, uid, gid, tag, users, groups?, user? }`) | `{ reply }`, the §3.7 reply, below |
168
168
  | `status` | — | `{ status: { version, protocol, origin, locked, lock, scrub, waiting, exportedAt, backupReminder, policy } }` |
169
169
  | `placeholders` | — | `{ placeholders, scrub }`: every placeholder of a record that isn't removed, active and inert, sorted |
170
170
  | `list` | — | `{ list: { secrets, refused, audit, auditMore, exits } }` for Settings › Secrets |
@@ -262,7 +262,7 @@ Values are checked as at `put` (at least 8 bytes, no CR, LF or NUL, no placehold
262
262
 
263
263
  `fnox exec` (§3.5) asks for a one-shot ticket, and the kernel redeems it when it execs the child.
264
264
 
265
- **`materialize`** (a `/dev/sealed` op) takes `names`, `path` (the absolute program fnox will exec), `argv` (exact, `argv[0]` included), `cwd`, and optionally `interpreter`: fnox's claim of the absolute shebang or binfmt interpreter of `path`, omitted for a native program. It's only a claim; the kernel's chain at consume decides. `shellGrants: true` (`fnox exec --shell-grants`) adds every `shell = true` secret to `names`, which may then be omitted.
265
+ **`materialize`** (a `/dev/sealed` op) takes `names`, `path` (the absolute program fnox will exec), `argv` (exact, `argv[0]` included), `cwd`, and optionally `interpreter`: fnox's claim of the absolute shebang or binfmt interpreter of `path`, omitted for a native program. It's only a claim; the kernel's chain at consume decides. `shellGrants: true` (`fnox exec --shell-grants`) adds every `shell = true` secret to `names`, which may then be omitted. While the caller's stamp doesn't say `users: 'enforce'`, it's `EACCES` "kernel users must be enforce" in every `[exec]` mode, `shell` included (§3.16.2): with users off, every process is effectively root and the stamp proves nothing.
266
266
  - Each name must be a `secret` record (`ENOENT` otherwise) that hasn't expired (`EACCES: NAME expired; refreshing`). `real` says which will get their value: removed records and secrets without an `[exec]` grant stay `false` and keep their placeholder.
267
267
  - **`true`** (exec `any`): refused with `EACCES` when `path`, or `interpreter`, is a shell (`sh`, `bash`, `dash`, `zsh`, `fish`, `busybox`, `jsh`), an interpreter (`python*`, `node`, `deno`, `bun`, `perl`, `ruby`, `php`, `lua`, `wasmer`, `awk` and `gawk`/`mawk`/`nawk`, `sed`, `tclsh`, `expect`, `R`, `Rscript`, `irb`) or an env dumper or wrapper (`env`, `printenv`, `xargs`, `sudo`, `su`, `nohup`, `timeout`, `time`, `nice`, `strace`), by basename.
268
268
  - **`{ argv = [[…]] }`**: `real` only when `[path, ...argv[1..]]` starts with one of the listed prefixes. A listed shell or interpreter is allowed.
@@ -276,10 +276,10 @@ Values are checked as at `put` (at least 8 bytes, no CR, LF or NUL, no placehold
276
276
  - `stamp` is the exec'ing process.
277
277
  - `resolved` is the kernel-normalised file that actually runs: `path` with every symlink resolved.
278
278
  - `interpreters` is the absolute, symlink-resolved shebang/binfmt chain the kernel resolved for `path`, empty for a native binary. A plain `#!/usr/bin/env X` (or `env -S`) is reported as the resolved `X`; `env` with any other option or a `NAME=value` assignment is reported as `env` itself.
279
- - `trusted` is true when the program and every chain entry are root-owned regular files, not group- or other-writable, under root-owned, non-writable parents (the attestation rule). Under users `off` it's always false.
279
+ - `trusted` is true when the program and every chain entry are root-owned regular files, not group- or other-writable, under root-owned, non-writable parents (the attestation rule). Under users `off` it's always false, and the consume is refused anyway.
280
280
 
281
281
  The vault burns the ticket, then requires that:
282
- - it's unexpired and from this namespace, `stamp.ppid` is the pid that asked, and `path`, `argv` (element by element) and `cwd` are exactly the ones it was issued for;
282
+ - `stamp.users` is `enforce` (else "kernel users must be enforce"), it's unexpired and from this namespace, `stamp.ppid` is the pid that asked, and `path`, `argv` (element by element) and `cwd` are exactly the ones it was issued for;
283
283
  - `resolved` is an absolute path (missing or malformed refuses), `interpreters` a list of absolute paths and `trusted` a boolean; a claimed `interpreter` from fnox equals `interpreters[0]` (without a claim, the kernel's chain is taken as it is);
284
284
  - each real name is still granted by the policy as stored now (reloaded at materialize and at consume): a removed grant, or an `{ argv }` grant that no longer lists the command, refuses;
285
285
  - under that name's mode, the chain passes:
@@ -299,13 +299,13 @@ const channel = new MessageChannel();
299
299
  port.postMessage({ v: 1, id, op: 'forward', head, body, owner, exit, rawTraits }, [channel.port2]);
300
300
  ```
301
301
 
302
- - `head` is `{ method, url, headers: [name, value][] }`; `owner` the kernel's `{ pid, ppid, uid, gid, tag }`; `exit` the exit bios chose (`page`, `proxy`, `tailscale` or `device:<name>`); `rawTraits` that exit's `{ manualRedirects?, encodedBodies? }`. `body` (bytes or a string) is needed only for MCP requests (below); for every other request it stays in bios, because bodies are never injected.
302
+ - `head` is `{ method, url, headers: [name, value][] }`; `owner` the kernel's `{ pid, ppid, uid, gid, tag, users, groups?, user? }` (`users` must be `enforce`, else nothing is injected); `exit` the exit bios chose (`page`, `proxy`, `tailscale` or `device:<name>`); `rawTraits` that exit's `{ manualRedirects?, encodedBodies? }`. `body` (bytes or a string) is needed only for MCP requests (below); for every other request it stays in bios, because bodies are never injected.
303
303
  - **The reply** is `{ head }`, the same head with the values filled in, or `{ refused: { status, reason } }`: `403` with a one-line reason, or `502` for an exit with encoded bodies. A refused request must not be sent anywhere. Malformed fields are `EINVAL`.
304
304
  - **The response** goes back over `channel.port1`, scrubbed: bios posts `{ op: 'response', status, statusText, url, redirected?, headers }`, then any number of `{ op: 'body', chunk: Uint8Array }`, then `{ op: 'end' }` or `{ op: 'error', message }`. The vault answers with the same messages, with `statusText`, `url`, every header name and value, the body and the error scrubbed over every value of the namespace. A value in its raw form comes back as its placeholder, anything else as `[redacted:NAME]`. The body is scrubbed as a stream; after 250 ms without input, a held tail shorter than 16 bytes is let through.
305
305
 
306
306
  **The injector**, fnox's proxy semantics:
307
307
  - **Hosts** are compared lower-cased, without a trailing dot, in rules, the refusal list and MCP servers alike.
308
- - **Refused before anything is sent:** a placeholder in the URL or in a header name; credentials in the URL; a scheme other than `https:` or a port other than 443 (except §3.16.3's kernel servers, below); an ambiguous path (`.`/`..` segments, `\`, `%2e`, `%2f`, `%5c`, `%25`, checked on the raw URL); an exit with encoded bodies; no placeholder of this vault at all; a record that is removed, expired ("NAME expired; refreshing") or not a `secret`; a locked vault; no rule (in `/etc/fnox/config.toml`) whose exact domain, header, method and path allow it; an exit the secret's `[exits]` line doesn't list; the same placeholder twice in one header value; a header other than `authorization` on an exit that follows redirects (`manualRedirects` false); CR, LF or NUL after substitution.
308
+ - **Refused before anything is sent:** an `owner` whose `users` isn't `enforce` ("kernel users must be enforce", §3.16.2; this covers kernel programs calling a registered MCP server, and the `/dev/sealed` `test` verdict, too); a placeholder in the URL or in a header name; credentials in the URL; a scheme other than `https:` or a port other than 443 (except §3.16.3's kernel servers, below); an ambiguous path (`.`/`..` segments, `\`, `%2e`, `%2f`, `%5c`, `%25`, checked on the raw URL); an exit with encoded bodies; no placeholder of this vault at all; a record that is removed, expired ("NAME expired; refreshing") or not a `secret`; a locked vault; no rule (in `/etc/fnox/config.toml`) whose exact domain, header, method and path allow it; an exit the secret's `[exits]` line doesn't list; the same placeholder twice in one header value; a header other than `authorization` on an exit that follows redirects (`manualRedirects` false); CR, LF or NUL after substitution.
309
309
  - **Paths** in rules are globs: `*` stays within a segment, `**` crosses segments, `?` is one character.
310
310
  - **Injected:** each placeholder is replaced inside the rule's header value, so `Bearer …` and `token …` templates work. In `Authorization: Basic`, the credential is decoded, the user and password parts substituted (a substituted user may not contain `:`), and re-encoded. A Basic value that holds a placeholder but isn't valid base64 of UTF-8 is refused.
311
311
  - **Audited:** each injection adds a `forward` audit entry `{ name, domain, method, path, rule, exit, uid, tag }` and sets the record's `usedAt`. Never headers, bodies or values.
@@ -430,11 +430,11 @@ npm test # Chromium integration tests
430
430
  npm start # vault on http://vault.sliccy.localhost:8790, a test embedder on seven.sliccy.localhost
431
431
  ```
432
432
 
433
- - **Integration tests** (`test/integration/`) run the built vault on a local server that sends the production headers, at `http://vault.sliccy.localhost:<port>`, with test embedders on `seven.`, `preview.` and `evil.sliccy.localhost`. Chromium treats `*.sliccy.localhost` as one site and as a secure context, so the embedded frame gets the same first-party storage it gets under `sliccy.ai`. They use slicc-shared-web's CDP harness, plus a second CDP session for popups and the virtual WebAuthn authenticator.
433
+ - **Integration tests** (`test/integration/`) run the built vault on a local server that sends the production headers, at `http://vault.sliccy.localhost:<port>`, with test embedders on `seven.`, `preview.` and `evil.sliccy.localhost`. Chromium treats `*.sliccy.localhost` as one site and as a secure context, so the embedded frame gets the same first-party storage it gets under `sliccy.ai`. They use slicc-shared-web's CDP harness, plus a second CDP session for popups and the virtual WebAuthn authenticator. `kernel.test.mjs` runs exec tickets end to end against the real kernel (`@ai-ecoverse/slicc-kernel/node` with wasm-bash and wasm-coreutils, test dependencies only): its `/dev/sealed` hook relays to the built vault, so a `materialize`, the spawn with `SLICC_SEALED_TICKET`, the kernel's `materializeConsume`, the value swap and S2's scrubbing of the child's output all run as in seven, and with users `off` nothing is revealed.
434
434
  - **Unit tests** stay in the gitignored `test/unit/` (`npm run test:unit`), and the pre-commit hook requires them to cover every changed line of `src/`.
435
435
  - The Biome, TypeScript, lefthook and Renovate configuration comes from [slicc-shared-web](https://github.com/ai-ecoverse/slicc-shared-web). There are no comments in any file.
436
436
 
437
437
  ## Release and deploy
438
438
 
439
439
  - **Releases:** semantic-release on `main` tags a version from the conventional commits. It publishes the types package when the repository variable `NPM_PUBLISH` is `true`.
440
- - **Deploy:** after semantic-release, the release job builds with the new version and publishes `dist/site/` to `vault.sliccy.ai` from the `production` environment. Every push to another branch publishes it to `vault-<label>.sliccy.ai`. Both use GitHub's OIDC token against sliccy-ai's `/api/publish` route (`edge/publish.mjs`), with no stored secret.
440
+ - **Deploy:** after semantic-release, the release job builds with the new version and publishes `dist/site/` to `vault.sliccy.ai` from the `production` environment. Every push to another branch publishes it to `vault-<label>.sliccy.ai`. Both use GitHub's OIDC token against sliccy-ai's `/api/publish` route (`edge/publish.mjs`), with no stored secret. `edge/publish.mjs` ends with the verdict line `publish: PASS <url> <sha>`, and both publish steps fail without it, so a script that exits without publishing can't read as a pass. It runs as the main module also through a symlinked checkout or workspace: both sides of that check are real paths. A `manifest` answer without an `https://` URL fails the publish.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-vault",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "description": "The separate-origin vault at vault.sliccy.ai: sealed secrets, OAuth tokens, and model and MCP credentials for SLICC seven, used only by its injector",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -46,23 +46,27 @@
46
46
  "release": "semantic-release"
47
47
  },
48
48
  "devDependencies": {
49
- "@ai-ecoverse/jerk-oauth": "0.15.1",
49
+ "@ai-ecoverse/jerk-oauth": "0.24.0",
50
+ "@ai-ecoverse/slicc-kernel": "1.122.1",
50
51
  "@ai-ecoverse/slicc-scrub": "1.0.0",
51
- "@ai-ecoverse/slicc-shared-web": "1.13.1",
52
+ "@ai-ecoverse/slicc-shared-web": "1.23.1",
53
+ "@ai-ecoverse/wasm-bash": "5.3.0-19",
54
+ "@ai-ecoverse/wasm-coreutils": "9.12.0-9",
52
55
  "@biomejs/biome": "2.5.15",
53
- "@earendil-works/pi-ai": "1.0.4",
54
- "@happy-dom/global-registrator": "20.14.5",
56
+ "@earendil-works/pi-ai": "1.1.0",
57
+ "@happy-dom/global-registrator": "20.14.6",
55
58
  "@spectrum-css/button": "14.2.0",
56
59
  "@spectrum-css/textfield": "8.2.0",
57
60
  "@spectrum-css/tokens": "16.0.2",
58
61
  "esbuild": "0.28.2",
59
62
  "fake-indexeddb": "6.2.5",
60
- "happy-dom": "20.14.5",
63
+ "happy-dom": "20.14.6",
61
64
  "hash-wasm": "4.12.0",
65
+ "js-yaml": "4.3.2",
62
66
  "kdbxweb": "2.1.1",
63
- "lefthook": "2.1.17",
64
- "monocart-coverage-reports": "2.13.1",
65
- "playwright-core": "1.63.0",
67
+ "lefthook": "2.2.1",
68
+ "monocart-coverage-reports": "2.13.2",
69
+ "playwright-core": "1.64.0",
66
70
  "semantic-release": "25.0.9",
67
71
  "typescript": "7.0.2"
68
72
  }