@ai-ecoverse/slicc-vault 1.11.0 → 1.12.1
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 +10 -5
- package/dist/types/contract.d.ts +24 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -112,7 +112,8 @@ Otherwise the vault keeps the policy it last accepted for that file, so the rule
|
|
|
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
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
|
+
- **Trust anchors** (§3.19.6), on any session rule: `anchors` (base64 DER certificates, each starting with a SEQUENCE and at most 8 KB), `pins` (base64 SHA-256 of a public key, 32 bytes) and `roots` (`true` or `false`), at most 8 anchors and 8 pins. A rule with anchors or pins accepts only sessions the kernel verified against them, never the public roots, unless it says `roots = true`; a rule without them accepts the public roots, and `roots = false` there is a warning. The vault checks only their format: the kernel checks that an anchor is a CA (CA:true and keyCertSign) and the chain. Like every rule, they come only from the attested root-owned file, so only an approved `sudo` write sets them: no program, agent or page op can.
|
|
116
|
+
- `port`, `position`, `anchors`, `pins` or `roots` without `protocol`, another `protocol`, or a missing or wrong `port`, `position`, `anchors`, `pins` or `roots` is a warning, and the rule is dropped.
|
|
116
117
|
- 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
118
|
- Nothing else in the file counts: no imports, profiles or other providers.
|
|
118
119
|
|
|
@@ -184,7 +185,8 @@ iframe.contentWindow.postMessage({ type: 'slicc-vault', v: 1, op: 'hello', role:
|
|
|
184
185
|
| `policy` | `files` | the attested policy files, below (Policy) |
|
|
185
186
|
| `forward` | `head`, `body?`, `owner`, `exit`, `rawTraits`, with a `MessagePort` transferred alongside | the injected head, below (Forward) |
|
|
186
187
|
| `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`, 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
|
+
| `sessionMaterialize` | `stamp`, `session`, `host`, `port`, `protocol`, `position`, `verified`, `handshakeAt`, `exit`, `verifiedBy?`, `anchor?`, 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) |
|
|
189
|
+
| `sessionAnchors` | `stamp`, `session`, `host`, `port`, `protocol`, `exit` | `{ anchors, pins }`, below (Sessions) |
|
|
188
190
|
| `import` | `kind`, `bytes`, `password?`, `pick?` | below (Import and export) |
|
|
189
191
|
| `export` | `passphrase` | `{ bundle, count }`, below |
|
|
190
192
|
| anything else | | `ENOTSUP` |
|
|
@@ -329,6 +331,7 @@ The fields are the kernel's session attestation:
|
|
|
329
331
|
- `host`: the name the kernel verified the leaf against, compared lower-cased without one trailing dot; `port`; `protocol` (`http1`, `smtp` or `pg`).
|
|
330
332
|
- `position`: `header:<name>` (lower-cased) for http1, else `auth-plain`, `auth-login`, `password-cleartext` or `scram-sha-256` (below).
|
|
331
333
|
- `verified`: the kernel's own verdict on the chain and the name; `handshakeAt`: when the handshake finished (epoch ms).
|
|
334
|
+
- `verifiedBy`: `roots` (the default; the kernel's bundled public roots) or `anchored` (a rule's anchors or pins, below), with `anchor`: base64 of the SHA-256 of the whole DER of the anchor that verified the chain, as `sessionAnchors` gave it, or the pin itself. `anchor` with `roots`, or a missing or malformed one with `anchored`, is `EINVAL`.
|
|
332
335
|
- `exit`: the exit the session leaves by (`page`, `proxy`, `tailscale` or `device:<id>`, with the device's `DeviceId`).
|
|
333
336
|
- `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
337
|
- 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.
|
|
@@ -342,7 +345,9 @@ Once the fields check out, the vault burns the session in IndexedDB, in one tran
|
|
|
342
345
|
|
|
343
346
|
Each placeholder then gets its value only when it is a `secret` record's, holding the placeholder `[secrets]` names, neither removed nor expired, with a session rule of that secret whose `protocol`, `domain`, `port` and position are the session's (and, for http1, whose `methods` and `paths` allow `head`), and whose `[exits]` line, if any, lists `exit`. A session can carry several secrets this way, each by its own rule. Just before answering, the records, the accepted policy and the registered endpoints are read again, at a fresh clock, in the transaction that audits the values. A value whose record was removed, expired or rotated meanwhile, or whose rule or `[exits]` line no longer allows it, is withheld. A session past its deadline by then, or an http1 URL now under a registered endpoint, refuses the whole request. That transaction is the point of release: if the vault locked before it, it writes nothing and refuses (`vault is locked`).
|
|
344
347
|
|
|
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.
|
|
348
|
+
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, verifiedBy, anchor?, exit, uid, tag }`, `rule` being the number of the rule that allowed it and `verifiedBy` and `anchor` how the kernel trusted the session (`anchor` only when `anchored`: the anchor's DER SHA-256 or the pin, never a value; entries written before 1.12.1 have neither, which means unknown, never `roots`), 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.
|
|
349
|
+
|
|
350
|
+
**`sessionAnchors`** (§3.19.6) is value-free: when the kernel's bundled roots fail a session only on trust (the leaf names the host and the chain is otherwise valid), it asks with `{ stamp, session, host, port, protocol, exit }` (checked as above; `users` must be `enforce`), and the vault answers `{ anchors, pins }`: every anchor and pin of the session rules for that `protocol`, `domain` and `port` whose secret's `[exits]` line, if any, lists `exit`, without duplicates, or empty lists. It burns nothing and needs no unlocked vault. The kernel then verifies the chain against those anchors only (never the program's `ca`), and asks `sessionMaterialize` with `verifiedBy: 'anchored'` and the `anchor` it used. A placeholder gets its value only by a rule that trusts the session: a rule with anchors or pins needs an `anchored` session whose `anchor` is one of that rule's own ("the anchor that verified the session isn't one of NAME's session rule" otherwise), or `roots = true` for a `roots` session ("NAME's session rule accepts only its anchors and pins, not public roots"); a rule without them takes a `roots` session only. So one rule's anchor never opens another rule's secret, and a pinned host can't fall back to a public CA.
|
|
346
351
|
|
|
347
352
|
**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
353
|
- `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.
|
|
@@ -452,13 +457,13 @@ The button writes its intent to `intents` under a random 128-bit nonce and opens
|
|
|
452
457
|
The npm package `@ai-ecoverse/slicc-vault` holds only TypeScript types, built from `src/contract.ts`: the port API (`Hello`, `Greeting`, `Request`, `Reply`, `VaultEvent`, `Role`), the button (`ButtonConfig`, `ButtonMessage`, `ButtonEvent`), the popup (`PopupIntent`, `PopupState`), the page's results (`SealResult`, `ImportResult`), and the data Settings shows (`SecretView`, `VaultStatus`, `ScrubState`, `PendingRequest`, `AuditEntry`, `ExitPolicyView`, `VaultList`). `Availability` (`connecting`, `waiting`, `locked`, `ready`) is for bios to tell spectrum "waiting for the vault" and "unlock the vault to continue". They mean:
|
|
453
458
|
|
|
454
459
|
- **`EpochMs`**: milliseconds since the epoch. **`DeviceId`**: the stable id of a linked device's link certificate, the same id bios uses for linked devices and trays, in `sourceDevice`, `device:<id>` exits (and `[exits]` lines) and `ExitPolicyView.devices[].device`; never a display name.
|
|
455
|
-
- **`Rule`**: `domain` is an exact lower-case host; `header` a lower-case header name; **empty `methods` or `paths` mean any**. A rule with `protocol` (`http1`, `smtp`, `pg`) is a session rule, with its `port` and, for smtp and pg, its `position`; show it as "PROTOCOL on host:port".
|
|
460
|
+
- **`Rule`**: `domain` is an exact lower-case host; `header` a lower-case header name; **empty `methods` or `paths` mean any**. A rule with `protocol` (`http1`, `smtp`, `pg`) is a session rule, with its `port` and, for smtp and pg, its `position`; show it as "PROTOCOL on host:port". `anchors`, `pins` and `roots` are its trust anchors (Policy).
|
|
456
461
|
- **`exec`**: `false` means the value is never revealed to a program; `any`, `argv` (with `execArgv`, the allowed argv prefixes) and `shell` are the `[exec]` modes under which `fnox exec` may hand it to a program.
|
|
457
462
|
- **`exits`** is present only when `/etc/slicc/secrets-policy.toml` restricts that secret; absent means every exit (`page`, `proxy`, `tailscale`, `device:<id>`).
|
|
458
463
|
- **Bindings in `list`:** read under one scrub version (retried, then `EAGAIN`), `provider`, `server` and `settings` are scrubbed over every value (a setting whose name holds one is dropped), and left out while the vault is locked before its first unlock.
|
|
459
464
|
- **`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`.
|
|
460
465
|
- **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 …".
|
|
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.
|
|
466
|
+
- **`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", with "trusted by public roots" or "trusted by its pinned anchor" from `verifiedBy`, and nothing when an older entry has no `verifiedBy`); **`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
467
|
- **`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`.
|
|
463
468
|
- **`VaultList.refused`** lists rules the vault dropped because they name a model or MCP endpoint.
|
|
464
469
|
- **`PendingRequest.prompt`** is untrusted, agent-written text of at most 200 characters.
|
package/dist/types/contract.d.ts
CHANGED
|
@@ -69,6 +69,9 @@ export interface Rule {
|
|
|
69
69
|
protocol?: SessionProtocol;
|
|
70
70
|
position?: SessionPosition;
|
|
71
71
|
port?: number;
|
|
72
|
+
anchors?: string[];
|
|
73
|
+
pins?: string[];
|
|
74
|
+
roots?: boolean;
|
|
72
75
|
}
|
|
73
76
|
export interface RefusedRule {
|
|
74
77
|
secret: string;
|
|
@@ -172,6 +175,8 @@ export interface SessionEntry extends AuditBase {
|
|
|
172
175
|
protocol: SessionProtocol;
|
|
173
176
|
position: string;
|
|
174
177
|
rule: number;
|
|
178
|
+
verifiedBy?: 'roots' | 'anchored';
|
|
179
|
+
anchor?: string;
|
|
175
180
|
exit: ExitName;
|
|
176
181
|
}
|
|
177
182
|
export interface McpEntry extends AuditBase {
|
|
@@ -492,7 +497,21 @@ export type Op = {
|
|
|
492
497
|
verified: boolean;
|
|
493
498
|
handshakeAt: EpochMs;
|
|
494
499
|
exit: ExitName;
|
|
495
|
-
} & SessionTarget
|
|
500
|
+
} & SessionTarget & ({
|
|
501
|
+
verifiedBy?: 'roots';
|
|
502
|
+
anchor?: never;
|
|
503
|
+
} | {
|
|
504
|
+
verifiedBy: 'anchored';
|
|
505
|
+
anchor: string;
|
|
506
|
+
})) | {
|
|
507
|
+
op: 'sessionAnchors';
|
|
508
|
+
stamp: Stamp;
|
|
509
|
+
session: string;
|
|
510
|
+
host: string;
|
|
511
|
+
port: number;
|
|
512
|
+
protocol: SessionProtocol;
|
|
513
|
+
exit: ExitName;
|
|
514
|
+
};
|
|
496
515
|
export type AgentOp = {
|
|
497
516
|
op: 'redact';
|
|
498
517
|
texts: (string | Uint8Array)[];
|
|
@@ -634,6 +653,10 @@ export type OpReply = {
|
|
|
634
653
|
} | {
|
|
635
654
|
op: 'sessionMaterialize';
|
|
636
655
|
values: Record<string, string>;
|
|
656
|
+
} | {
|
|
657
|
+
op: 'sessionAnchors';
|
|
658
|
+
anchors: string[];
|
|
659
|
+
pins: string[];
|
|
637
660
|
};
|
|
638
661
|
export type Request = Op & {
|
|
639
662
|
v: Protocol;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-vault",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.1",
|
|
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.
|
|
50
|
+
"@ai-ecoverse/slicc-kernel": "1.126.0",
|
|
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",
|