@ai-ecoverse/slicc-vault 1.10.1 → 1.11.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 CHANGED
@@ -111,7 +111,7 @@ Otherwise the vault keeps the policy it last accepted for that file, so the rule
111
111
  - `[[proxy.rules]]`: `secret` (a seven entry of `[secrets]`), `domain` (one exact host, lower-cased), `header` (lower-cased, default `authorization`), `methods` (upper-cased; empty means any) and `paths` (each starting with `/`; empty means any). A rule that breaks any of this is dropped with a warning; the rest of the file still counts. Rules are numbered from 1 in file order, dropped ones included; a `session` audit entry names its rule by that number.
112
112
  - **Session rules** (§3.19) add `protocol` and `port` (an integer from 1 to 65535), and are used only by `sessionMaterialize`, never by `forward`; a rule without `protocol` is used only by `forward`:
113
113
  - `protocol = "http1"`: `header`, `methods` and `paths` as above; the position is the header. A `position` is a warning.
114
- - `protocol = "smtp"`, with `position = "auth-plain"` or `"auth-login"`, or `protocol = "pg"`, with `position = "password-cleartext"`: no `header`, `methods` or `paths` (a warning otherwise). Its `header` reads `''`.
114
+ - `protocol = "smtp"`, with `position = "auth-plain"` or `"auth-login"`, or `protocol = "pg"`, with `position = "password-cleartext"` or `"scram-sha-256"` (each allowed only where a rule names it): no `header`, `methods` or `paths` (a warning otherwise). Its `header` reads `''`.
115
115
  - `port` or `position` without `protocol`, another `protocol`, or a missing or wrong `port` or `position` is a warning, and the rule is dropped.
116
116
  - A record of kind `secret` is `active` when the file's `[secrets]` entry of its name holds the record's own placeholder and at least one rule names it. A placeholder from another vault or an old record doesn't activate anything.
117
117
  - Nothing else in the file counts: no imports, profiles or other providers.
@@ -134,7 +134,7 @@ shell_per_day = 5000
134
134
  ```
135
135
 
136
136
  - `[exec]`: `false` is an explicit off, `true` is `exec: 'any'`, exactly `{ argv = [[…]] }` is `'argv'` (each prefix an absolute program path), and exactly `{ shell = true }` is `'shell'`. Anything else, a table with other or extra keys included, is a warning, and `exec` stays `false`. VAULT-1d acts on them.
137
- - `[exits]` is **optional**: a secret without a line may use every exit; a line restricts it to the exits listed (`page`, `proxy`, `tailscale`, `device:<id>`). `<id>` is the device's `DeviceId`, the stable id of its link certificate, never its display name, which can be renamed or shared by two devices; an entry is compared exactly with the `exit` bios sends. Unknown names are dropped from the line with a warning, which leaves the line narrower, never wider.
137
+ - `[exits]` is **optional**: a secret without a line may use every exit; a line restricts it to the exits listed (`page`, `proxy`, `tailscale`, `device:<id>`). `<id>` is the device's `DeviceId`, the stable id of its link certificate (a tray's 64 hex characters, or `local:…` for a CLI linked on the same computer), never its display name, which can be renamed or shared by two devices: one or more of `A-Z`, `a-z`, `0-9`, `.`, `_`, `:` and `-`. An entry is compared exactly with the `exit` bios sends. Unknown names are dropped from the line with a warning, which leaves the line narrower, never wider.
138
138
  - `[limits]` overrides the defaults above with positive integers.
139
139
 
140
140
  Accepted policy is kept per namespace in IndexedDB, and loaded when an embedder says hello. Each file is audited once per SHA-256 and verdict (the vault remembers the last 64 per file) as a `policy` entry, with `accepted`, any `warning`, and its `changes` against the previously accepted file: rules `added` or `removed`, `exec` modes, `exits` lines and `limits`. Any change to the accepted policy, or a new audit entry, pushes `changed` with `secrets`, `exits` and `audit`, and other vault documents of the namespace reload the policy. Each `policy` call reads, merges and writes the stored policy in one transaction, so two pages can't undo each other's update.
@@ -184,7 +184,7 @@ iframe.contentWindow.postMessage({ type: 'slicc-vault', v: 1, op: 'hello', role:
184
184
  | `policy` | `files` | the attested policy files, below (Policy) |
185
185
  | `forward` | `head`, `body?`, `owner`, `exit`, `rawTraits`, with a `MessagePort` transferred alongside | the injected head, below (Forward) |
186
186
  | `materializeConsume` | `ticket`, `stamp`, `path`, `resolved`, `argv`, `cwd`, `interpreters`, `trusted` | the values for the child's environment, below (Exec) |
187
- | `sessionMaterialize` | `stamp`, `session`, `host`, `port`, `protocol`, `position`, `verified`, `handshakeAt`, `exit`, `placeholders`, `head?` | `{ values }` for a kernel-verified TLS session, below (Sessions) |
187
+ | `sessionMaterialize` | `stamp`, `session`, `host`, `port`, `protocol`, `position`, `verified`, `handshakeAt`, `exit`, then `placeholders` (and `head` for http1) at every position but `scram-sha-256`, which takes `scram` and no `placeholders` | `{ values }` for a kernel-verified TLS session, below (Sessions) |
188
188
  | `import` | `kind`, `bytes`, `password?`, `pick?` | below (Import and export) |
189
189
  | `export` | `passphrase` | `{ bundle, count }`, below |
190
190
  | anything else | | `ENOTSUP` |
@@ -317,7 +317,7 @@ port.postMessage({ v: 1, id, op: 'forward', head, body, owner, exit, rawTraits }
317
317
 
318
318
  ## Sessions
319
319
 
320
- **`sessionMaterialize`** (§3.19, a page op that bios relays unchanged from the kernel) hands the kernel's TLS writer the secrets for one kernel-terminated TLS session (A2), for protocols `forward` can't carry: an http1 header on a kernel-made connection, SMTP AUTH, or pg's cleartext password. The kernel asks only after its own handshake verified the host (SNI, chain and hostname, whatever the caller's flags), never on a plaintext leg; it substitutes only at the position, uses each value once within 60 s, keeps it out of every process, fd, file, log and telemetry, and scrubs the session's read side (S2) for the values and their encodings. The vault picks the rules, so the kernel never parses `/etc/fnox/config.toml`.
320
+ **`sessionMaterialize`** (§3.19, a page op that bios relays unchanged from the kernel) hands the kernel's TLS writer the secrets for one kernel-terminated TLS session (A2), for protocols `forward` can't carry: an http1 header on a kernel-made connection, SMTP AUTH, or pg's cleartext password or SCRAM-SHA-256. The kernel asks only after its own handshake verified the host (SNI, chain and hostname, whatever the caller's flags), never on a plaintext leg; it substitutes only at the position, uses each value once within 60 s, keeps it out of every process, fd, file, log and telemetry, and scrubs the session's read side (S2) for the values and their encodings. The vault picks the rules, so the kernel never parses `/etc/fnox/config.toml`.
321
321
 
322
322
  ```js
323
323
  port.postMessage({ v: 1, id, op: 'sessionMaterialize', stamp, session, host, port: 587, protocol: 'smtp', position: 'auth-plain', verified: true, handshakeAt, exit, placeholders });
@@ -327,10 +327,10 @@ The fields are the kernel's session attestation:
327
327
  - `stamp`: the kernel's stamp of the process that owns the socket; `users` must be `enforce` (else `EACCES` "kernel users must be enforce").
328
328
  - `session`: the kernel's session id, 16 to 128 characters of `A-Z`, `a-z`, `0-9`, `_` and `-`.
329
329
  - `host`: the name the kernel verified the leaf against, compared lower-cased without one trailing dot; `port`; `protocol` (`http1`, `smtp` or `pg`).
330
- - `position`: `header:<name>` (lower-cased) for http1, else `auth-plain`, `auth-login` or `password-cleartext`.
330
+ - `position`: `header:<name>` (lower-cased) for http1, else `auth-plain`, `auth-login`, `password-cleartext` or `scram-sha-256` (below).
331
331
  - `verified`: the kernel's own verdict on the chain and the name; `handshakeAt`: when the handshake finished (epoch ms).
332
332
  - `exit`: the exit the session leaves by (`page`, `proxy`, `tailscale` or `device:<id>`, with the device's `DeviceId`).
333
- - `placeholders`: 1 to 16 placeholders, every one found at the position, in one request. `head` (http1 only, required there): `{ method, path }`, the request line's method and target.
333
+ - `placeholders` (every position but `scram-sha-256`, which takes `scram` instead, below): 1 to 16 placeholders, every one found at the position, in one request. `head` (http1 only, required there): `{ method, path }`, the request line's method and target.
334
334
  - A malformed field is `EINVAL`, and so is a `protocol` other than these or a `position` that isn't one of its protocol's; neither burns the session.
335
335
 
336
336
  Once the fields check out, the vault burns the session in IndexedDB, in one transaction, so no vault frame of the namespace, now or after a reload, answers it again until 60 s after the later of the request and `handshakeAt`. Then it refuses the whole request with `EACCES` "session refused: …" when:
@@ -344,6 +344,13 @@ Each placeholder then gets its value only when it is a `secret` record's, holdin
344
344
 
345
345
  It answers `{ values: { [placeholder]: value } }` for those, and leaves the others out, so the kernel sends them as written. When none gets a value, it's `EACCES` with the first placeholder's reason: "NAME isn't a secret", "a placeholder of no record of this vault", "NAME's placeholder isn't the one /etc/fnox/config.toml names", "NAME was removed", "NAME expired", "no session rule in /etc/fnox/config.toml lets NAME be used", "no session rule lets NAME at PROTOCOL POSITION on HOST:PORT" (with " for METHOD PATH" for http1), "NAME isn't allowed on EXIT", or "the secret or its rule changed meanwhile". Each value handed out adds a `session` audit entry `{ name, session, domain, port, protocol, position, rule, exit, uid, tag }`, `rule` being the number of the rule that allowed it, and sets the record's `usedAt`. It never includes the value. Stored values never hold CR, LF or NUL (Records), so a value can't break out of its position.
346
346
 
347
+ **pg SCRAM-SHA-256** (`position: 'scram-sha-256'`, §3.19.5). The program's pg client computes its `ClientProof` from the password it holds, a placeholder, so the kernel can't see which one it used; the vault finds it from the proof. The request carries `scram: { clientFirstBare, serverFirst, clientFinalBare, proof }` and no `placeholders` (`EINVAL` otherwise, and `scram` at any other position is `EINVAL` too):
348
+ - `clientFirstBare` is `n=<user>,r=<client nonce>` and `serverFirst` is `r=<nonce>,s=<salt>,i=<iterations>`, with no extensions; the server's nonce must extend the client's, and `i` must be 1 to 100000.
349
+ - `clientFinalBare` is `c=biws,r=<the server's nonce>`, without `p=`: `biws` is the GS2 header `n,,`, so channel binding (`-PLUS`, `p=…`, `y,,`) is refused. The kernel offers the program only `SCRAM-SHA-256`, because the program's binding data couldn't match the kernel's TLS.
350
+ - `proof` is base64 of the program's 32-byte `ClientProof`. Each message is at most 1024 characters. Anything else is `EINVAL`, before the burn.
351
+
352
+ The candidates are the placeholders that would pass the per-placeholder checks above for this session (a live `secret` with a `scram-sha-256` session rule for this host and port, whose `[exits]` allow `exit`), never other records; more than 16 is `EACCES` "more than 16 session rules' placeholders to try for SCRAM", before any hashing. For each, the vault computes RFC 5802's `ClientProof` with the placeholder as the password (PBKDF2-SHA-256 over the salt and iterations, then the Client Key, Stored Key and signature over `clientFirstBare,serverFirst,clientFinalBare`) and compares it in constant time. Only placeholders are hashed, never values, so it reveals nothing the program doesn't hold. Exactly one match is handed out as above (re-checked at release, audited with `position: 'scram-sha-256'`); none is `EACCES` "no session rule's placeholder matches the SCRAM proof", and more than one is `EACCES` "more than one placeholder matches the SCRAM proof". The kernel computes the real proof from the value, checks the server's real `v=` before giving the program one made from the placeholder, never passes on a server `e=` derived from the value, and wipes the value and every derived key.
353
+
347
354
  ## Model and MCP endpoints
348
355
 
349
356
  The vault never injects a secret into a model or MCP endpoint, whatever a rule says.
@@ -452,6 +459,7 @@ The npm package `@ai-ecoverse/slicc-vault` holds only TypeScript types, built fr
452
459
  - **`kind`**: `secret` (a credential programs use through rules), **`grant`** (a sign-in's refresh token, never injected; show it as "Sign-in"), `model` (a model provider key), `mcp` (an MCP server credential). `provider` and `account` name the service and the signed-in account where one is known; a `model` record always has its `provider`, an `mcp` record may have its `server`, and either may carry non-secret `settings`.
453
460
  - **Who did it:** `uid` and `tag` are the kernel stamp for entries a program or the agent caused (`/dev/sealed` seals, `forward`, `mcp`, `materialize`, `session`). Every entry from a page-role op (`put`, `seal`, `remove`, `restore`, `import`, `policy`) and from the vault itself (`refresh`) has `uid: null, tag: null`, older entries included. Spectrum shows page ops as "by you", `refresh` as "automatically", and `policy` entries with no "by …".
454
461
  - **`AuditEntry`**: `forward` is a use of a value in a request, with the exit that carried it; `mcp` a kernel program's call to a registered MCP server; `session` a value handed to the kernel for a verified TLS session (show it as "Sent over PROTOCOL to host"); **`materialize` is shown as "Revealed to a program"**; `seal`, `remove`, `restore` and `policy` (with its `changes`) record changes. Every entry has a `seq` for paging.
462
+ - **`SessionTarget`**: a `sessionMaterialize` request's protocol, position and what goes with it: `placeholders` (and `head` for http1) for every position but `scram-sha-256`, which takes `scram` (a `ScramExchange`) and no `placeholders`.
455
463
  - **`VaultList.refused`** lists rules the vault dropped because they name a model or MCP endpoint.
456
464
  - **`PendingRequest.prompt`** is untrusted, agent-written text of at most 200 characters.
457
465
  - **`ExitPolicyView`** is read-only: the secrets `/etc/slicc/secrets-policy.toml` restricts to certain exits.
@@ -26,7 +26,41 @@ export interface VaultError {
26
26
  message: string;
27
27
  }
28
28
  export type SessionProtocol = 'http1' | 'smtp' | 'pg';
29
- export type SessionPosition = 'auth-plain' | 'auth-login' | 'password-cleartext';
29
+ export type SessionPosition = 'auth-plain' | 'auth-login' | 'password-cleartext' | 'scram-sha-256';
30
+ export interface ScramExchange {
31
+ clientFirstBare: string;
32
+ serverFirst: string;
33
+ clientFinalBare: string;
34
+ proof: string;
35
+ }
36
+ export type SessionTarget = {
37
+ protocol: 'http1';
38
+ position: `header:${string}`;
39
+ placeholders: string[];
40
+ head: {
41
+ method: string;
42
+ path: string;
43
+ };
44
+ scram?: never;
45
+ } | {
46
+ protocol: 'smtp';
47
+ position: 'auth-plain' | 'auth-login';
48
+ placeholders: string[];
49
+ head?: never;
50
+ scram?: never;
51
+ } | {
52
+ protocol: 'pg';
53
+ position: 'password-cleartext';
54
+ placeholders: string[];
55
+ head?: never;
56
+ scram?: never;
57
+ } | {
58
+ protocol: 'pg';
59
+ position: 'scram-sha-256';
60
+ scram: ScramExchange;
61
+ placeholders?: never;
62
+ head?: never;
63
+ };
30
64
  export interface Rule {
31
65
  domain: string;
32
66
  header: string;
@@ -449,23 +483,16 @@ export type Op = {
449
483
  cwd: string;
450
484
  interpreters: string[];
451
485
  trusted: boolean;
452
- } | {
486
+ } | ({
453
487
  op: 'sessionMaterialize';
454
488
  stamp: Stamp;
455
489
  session: string;
456
490
  host: string;
457
491
  port: number;
458
- protocol: SessionProtocol;
459
- position: string;
460
492
  verified: boolean;
461
493
  handshakeAt: EpochMs;
462
494
  exit: ExitName;
463
- placeholders: string[];
464
- head?: {
465
- method: string;
466
- path: string;
467
- };
468
- };
495
+ } & SessionTarget);
469
496
  export type AgentOp = {
470
497
  op: 'redact';
471
498
  texts: (string | Uint8Array)[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-vault",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
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",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@ai-ecoverse/jerk-oauth": "1.0.0",
50
- "@ai-ecoverse/slicc-kernel": "1.125.9",
50
+ "@ai-ecoverse/slicc-kernel": "1.125.12",
51
51
  "@ai-ecoverse/slicc-scrub": "1.0.0",
52
52
  "@ai-ecoverse/slicc-shared-web": "1.24.2",
53
53
  "@ai-ecoverse/wasm-bash": "5.3.0-20",