@ai-ecoverse/slicc-vault 0.0.0-stage → 1.0.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/LICENSE +191 -0
- package/README.md +218 -2
- package/dist/types/contract.d.ts +369 -0
- package/package.json +62 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to the Licensor for inclusion in the Work by the copyright
|
|
52
|
+
owner or by an individual or Legal Entity authorized to submit on
|
|
53
|
+
behalf of the copyright owner. For the purposes of this definition,
|
|
54
|
+
"submitted" means any form of electronic, verbal, or written
|
|
55
|
+
communication sent to the Licensor or its representatives, including
|
|
56
|
+
but not limited to communication on electronic mailing lists, source
|
|
57
|
+
code control systems, and issue tracking systems that are managed by,
|
|
58
|
+
or on behalf of, the Licensor for the purpose of discussing and
|
|
59
|
+
improving the Work, but excluding communication that is conspicuously
|
|
60
|
+
marked or otherwise designated in writing by the copyright owner as
|
|
61
|
+
"Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
Copyright 2025 ai-ecoverse
|
|
180
|
+
|
|
181
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
182
|
+
you may not use this file except in compliance with the License.
|
|
183
|
+
You may obtain a copy of the License at
|
|
184
|
+
|
|
185
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
186
|
+
|
|
187
|
+
Unless required by applicable law or agreed to in writing, software
|
|
188
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
189
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
190
|
+
See the License for the specific language governing permissions and
|
|
191
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,219 @@
|
|
|
1
|
-
#
|
|
1
|
+
# slicc-vault
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The separate-origin vault at `vault.sliccy.ai`. It holds the sealed secrets, OAuth tokens, and model and MCP credentials of SLICC seven, so that no value ever reaches the model provider. The design is `secrets-tokens-design.md` (revision 4) in slicc-agent-plan. This README is the spec of what is built.
|
|
4
|
+
|
|
5
|
+
- **Built:** the key store, records, origin namespacing, auto-unlock with an optional passkey or passphrase lock, the embedding API with its `page` and `agent` roles, the page's `put`, `seal`, `remove`, `restore`, `cancel` and `lock`, the scrub state, the lock button and its popup, and the vault side of the `/dev/sealed` ops `seal`, `placeholder`, `list`, `test`, `request`, `status`, `cancel` and `materialize`.
|
|
6
|
+
- **Not built yet:** policy (every record is `inert`, and `test` always refuses), `forward` and the scrubber, exec, `import` and `export`, and the agent's ops. They answer as described below.
|
|
7
|
+
|
|
8
|
+
## Documents and hosting
|
|
9
|
+
|
|
10
|
+
The build (`npm run build`) writes a flat static tree to `dist/site/`:
|
|
11
|
+
|
|
12
|
+
| file | what it is |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `index.html`, `frame.js` | the core frame. bios embeds it as a hidden iframe and talks to it over a `MessagePort` |
|
|
15
|
+
| `button.html`, `button.js` | the lock button. A popup can only open from a click inside a vault document, so bios and spectrum embed this wherever the lock is offered |
|
|
16
|
+
| `popup.html`, `popup.js` | the vault window, a top-level `vault.sliccy.ai` document, used only for the passkey or passphrase lock |
|
|
17
|
+
| `vault.css`, `fonts/` | Spectrum 2 from the CSS-only `@spectrum-css` packages, and Adobe Clean |
|
|
18
|
+
| `config.json` | `{ version, embedders }`. `embedders` is the fallback list, `["https://seven.sliccy.ai"]` |
|
|
19
|
+
|
|
20
|
+
There is no third-party JavaScript: the bundles hold only this repo's code. The sliccy-ai Worker serves the tree from the network on every load, never from OPFS, with:
|
|
21
|
+
- `Content-Security-Policy` with `script-src 'self'`, no inline scripts or styles, and `frame-ancestors` listing the seven origins;
|
|
22
|
+
- `Cross-Origin-Embedder-Policy: require-corp`, `Cross-Origin-Resource-Policy: same-site`, `Cross-Origin-Opener-Policy: same-origin`;
|
|
23
|
+
- `Origin-Agent-Cluster: ?1`, `nosniff`, `Referrer-Policy: no-referrer`, `no-cache` with an ETag.
|
|
24
|
+
|
|
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, falls back to `config.json` when it's missing or malformed, and accepts only those origins. An embedder source is `http(s)://host`, optionally with `:port` or `:*`, matched exactly.
|
|
26
|
+
|
|
27
|
+
`main` is served at `vault.sliccy.ai`, and every other branch at `vault-<label>.sliccy.ai`. seven embeds only `vault.sliccy.ai`.
|
|
28
|
+
|
|
29
|
+
## Storage and keys
|
|
30
|
+
|
|
31
|
+
Everything lives in the vault origin's IndexedDB, database `slicc-vault`, version 1:
|
|
32
|
+
|
|
33
|
+
| store | key | holds |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `meta` | `"keys"` | the key state below |
|
|
36
|
+
| `records` | `[ns, kind, name]`, unique index on `placeholder` | records |
|
|
37
|
+
| `requests` | `id` | requests that wait for a human |
|
|
38
|
+
| `audit` | `seq` (auto-increment) | the audit log, per namespace |
|
|
39
|
+
| `intents` | `nonce` | hand-offs from a button to the popup, valid for 10 minutes |
|
|
40
|
+
|
|
41
|
+
**The data key** is one AES-GCM 256 key. Each record's value is encrypted under it with a fresh 12-byte IV and the additional data `slicc-vault/1 NUL ns NUL kind NUL name`, so a ciphertext can't be moved to another record.
|
|
42
|
+
|
|
43
|
+
**The key state** is one of:
|
|
44
|
+
- `auto` (the default): the data key is stored as a **non-extractable** `CryptoKey`. Every vault document loads it at start, so there's never a prompt.
|
|
45
|
+
- `passkey`: the data key is stored only wrapped (AES-GCM) under a key-encryption key from **WebAuthn PRF**. The passkey's RP id is the vault's host, the PRF input is a random 32-byte salt, and the KEK is HKDF-SHA-256 over the PRF output with that salt and the info `slicc-vault/kek/1`.
|
|
46
|
+
- `passphrase`: the same, with the KEK from PBKDF2-SHA-256, 600,000 iterations, over a random 16-byte salt.
|
|
47
|
+
|
|
48
|
+
**Locking:**
|
|
49
|
+
- While protected, a document starts locked and asks the other open vault documents (over the origin's `BroadcastChannel` `slicc-vault`) for the unwrapped key. Unlocking in the popup unwraps the key as non-extractable and hands it to every open vault document the same way.
|
|
50
|
+
- `Lock now` drops the key from every document. Closing every vault document has the same effect.
|
|
51
|
+
- **Protecting** makes a new data key, re-encrypts every record under it, and stores it wrapped, in one transaction. If a record changed meanwhile, protecting fails with `EAGAIN` and changes nothing. **Removing protection** stores the current key as non-extractable again.
|
|
52
|
+
- Every data key has an id, stored with it. A seal checks that id in the same transaction that writes the record, so a value encrypted under a key that was replaced meanwhile is refused with `EAGAIN` rather than stored unreadable.
|
|
53
|
+
- While locked, `seal` answers `EACCES` with a message starting `vault is locked`. Ops that need no value (`placeholder`, `list`, `status`, ...) still work.
|
|
54
|
+
|
|
55
|
+
## Records
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
{ ns, kind: "secret" | "grant" | "model" | "mcp", name, placeholder, sealed: { iv, data },
|
|
59
|
+
source, createdAt, updatedAt, usedAt?, expiresAt?, refresh?, removedAt? }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **`ns`** is the embedder's origin, below.
|
|
63
|
+
- **`name`** matches `^[A-Za-z_][A-Za-z0-9_]*$`, at most 128 characters.
|
|
64
|
+
- **A value** is at least 8 bytes of UTF-8, without CR, LF or NUL, and contains no placeholder. Anything shorter is refused with `EINVAL` "values shorter than 8 bytes can't be protected from the model", because the scrubber couldn't tell it apart from ordinary output.
|
|
65
|
+
- **`placeholder`** is `fnox_proxy_` plus 24 lower-case hex characters from `crypto.getRandomValues`, unique across the whole vault and stable for the record's life. All placeholders have the same length, so none contains another.
|
|
66
|
+
- **`source`** is `settings` (entered in spectrum), `process`, `jerk:<provider>@<device>`, `oauth:<provider>` or `import:<kind>`.
|
|
67
|
+
- **`state`** is computed, never stored: `removed` when `removedAt` is set, `active` when the accepted policy has a rule naming the record, otherwise `inert`. With no policy yet, every record is `inert`.
|
|
68
|
+
- **Removing** sets `removedAt`. A removed record keeps its placeholder, can be restored with `restore`, and is deleted for good 30 days later. Sealing a removed name brings it back with a new value and the same placeholder.
|
|
69
|
+
- **Times** are epoch milliseconds everywhere.
|
|
70
|
+
|
|
71
|
+
## Scrub state
|
|
72
|
+
|
|
73
|
+
`scrub { needed, version, ready }` tells bios and the agent whether output must pass the vault's scrubber before it reaches the model. It's per namespace:
|
|
74
|
+
- `needed` is false only when the namespace holds no plaintext at all: no record of any kind or state, removed ones included until they're purged.
|
|
75
|
+
- `version` counts every change to the namespace's set of values: each seal, rotation, removal, restore and purge.
|
|
76
|
+
- `ready` is false only when values exist but this vault frame never held the data key since it loaded, which is a reload with the lock on, before unlock. Locking again later doesn't clear it, because the compiled scrubber stays in memory for the frame's life.
|
|
77
|
+
|
|
78
|
+
It's in `status`, in `placeholders` replies and in every `placeholders` push. After `put` and `seal`, the push with the new pair goes out **before** the reply.
|
|
79
|
+
|
|
80
|
+
## Namespaces
|
|
81
|
+
|
|
82
|
+
Every record, request, audit entry and scrub version belongs to the origin that embeds the vault. The core frame takes it from the `event.origin` of the hello, and the button from `location.ancestorOrigins[0]` (or the first message's origin when the browser hides ancestors). A branch preview of bios therefore gets its own empty vault and never sees seven's records. The popup shows the namespace of the button that opened it. The lock itself is one per vault.
|
|
83
|
+
|
|
84
|
+
## The embedding API (core frame)
|
|
85
|
+
|
|
86
|
+
The embedder (bios) loads `https://vault.sliccy.ai/` in an iframe and, after `load`, posts a hello carrying a port:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
iframe.contentWindow.postMessage({ type: 'slicc-vault', v: 1, op: 'hello', role: 'page' }, 'https://vault.sliccy.ai', [port2]);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- The frame answers only when the message comes from its own parent window, from an allowed origin, matching `location.ancestorOrigins[0]` when the browser exposes it. Anything else gets no answer at all.
|
|
93
|
+
- On `port2` it answers `{ v: 1, op: 'welcome', role, status }`, or `{ v: 1, op: 'refused', error }` for another protocol version or an unknown role.
|
|
94
|
+
- Everything after that goes over the port. The namespace and the role are fixed by the hello. Requests are `{ v: 1, id, op, ...fields }`; replies are `{ v: 1, id, op, ...fields }` or `{ v: 1, id, error: { code, message } }`. Each port is answered in order (FIFO).
|
|
95
|
+
- **Roles:** `page` (the default) is bios and spectrum. bios may hand spectrum a page port of its own, or relay; the messages are the same. `agent` is the port bios hands the agent worker: it may use only `redact`, `redactStream`, `push`, `end`, `drop`, `model`, `mcp`, `endpoints`, `records`, `grant` and `refresh` (all `ENOTSUP` in this version), and anything else is `EACCES`. The agent port gets no events.
|
|
96
|
+
|
|
97
|
+
| op (page role) | fields | reply |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `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 |
|
|
100
|
+
| `status` | — | `{ status: { version, protocol, origin, locked, lock, scrub, waiting } }` |
|
|
101
|
+
| `placeholders` | — | `{ placeholders, scrub }`: every placeholder of a record that isn't removed, active and inert, sorted |
|
|
102
|
+
| `list` | — | `{ list: { secrets, refused, audit, auditMore, exits } }` for Settings › Secrets |
|
|
103
|
+
| `audit` | `before?` (a `seq`), `limit?` (1–1000, default 100) | `{ audit, more, next? }`, newest first; pass `next` as `before` for the next page |
|
|
104
|
+
| `put` | `name`, `value`, `request?` | `{ name, placeholder, state }`. A value typed in Settings, with source `settings`; on an existing name it's a rotation that keeps the placeholder. With `request`, it answers that value request, which must still be pending and ask for `name` (else `EINVAL`, and nothing is stored), so its `/dev/sealed` `status` becomes `done` |
|
|
105
|
+
| `seal` | `name`, `value`, `source`, `kind?` (default `secret`), `expiresAt?`, `refresh?` | `{ name, placeholder, state }`. bios's, for J1 leases and sign-in tokens |
|
|
106
|
+
| `remove`, `restore` | `name`, `kind?` (default `secret`) | `{ name, placeholder, state }`. A record removed more than 30 days ago can't be restored (`ENOENT`) |
|
|
107
|
+
| `cancel` | `request` | `{ state }`; the request's `/dev/sealed` `status` becomes `cancelled` |
|
|
108
|
+
| `lock` | — | `{}`; drops the key everywhere when the lock is on |
|
|
109
|
+
| `import`, `export`, anything else | | `ENOTSUP` |
|
|
110
|
+
|
|
111
|
+
`put` and `seal` need the vault unlocked. They answer `EACCES` with a message starting `vault is locked` otherwise.
|
|
112
|
+
|
|
113
|
+
**Events** on page ports:
|
|
114
|
+
- `{ v: 1, event: 'status', status }` whenever requests, records, the scrub state or the lock change;
|
|
115
|
+
- `{ v: 1, event: 'placeholders', placeholders, scrub }` whenever either changes;
|
|
116
|
+
- `{ v: 1, event: 'changed', parts }`, with `parts` among `secrets`, `audit` and `exits`, so a view knows what to reload.
|
|
117
|
+
|
|
118
|
+
**Errors** use errno names: `EINVAL`, `ENOENT`, `EACCES`, `EEXIST`, `EAGAIN`, `ETIMEDOUT`, `ENOTCONN`, and `ENOTSUP` for ops this version doesn't have.
|
|
119
|
+
|
|
120
|
+
## `/dev/sealed`, the vault side
|
|
121
|
+
|
|
122
|
+
bios relays each kernel request unchanged as the `sealed` op, with the kernel's stamp. The vault validates and answers `{ v: 1, ...fields }` or `{ v: 1, error: { code, message } }`. A request over 64 KiB, one that isn't a single JSON object, one with `v` other than 1, and an unknown `op` are `EINVAL`, as is a missing or malformed stamp.
|
|
123
|
+
|
|
124
|
+
| op | fields | reply |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `seal` | `name`, `value` | `{ placeholder, state }`. A new name creates a `process` record. An existing one is rotated and keeps its placeholder, but only when its value came from a process; a value from Settings, jerk, OAuth or an import is replaced only by uid 0, and anyone else gets `EEXIST` |
|
|
127
|
+
| `placeholder` | `name` | `{ placeholder, state }`, including `removed`; `ENOENT` when there's no record |
|
|
128
|
+
| `list` | — | `{ secrets: [{ name, placeholder, state, exec, rules, expiresAt? }] }`, records of kind `secret` that aren't removed |
|
|
129
|
+
| `test` | `name`, `url`, `method?`, `header?` | `{ verdict, reason }`. Without policy it's always `refuse`; `ENOENT` for an unknown name |
|
|
130
|
+
| `request` | `kind: "value"`, `name`, `prompt?` (≤ 200 characters) | `{ state: "pending", id }` |
|
|
131
|
+
| `request` | `kind: "oauth"`, `provider`, `oauth` (an object) | the same |
|
|
132
|
+
| `request` | `kind: "jerk-seal"`, `provider` | the same |
|
|
133
|
+
| `status` | `id` | `{ state }`, with `placeholder` and `record` once a value request is `done` |
|
|
134
|
+
| `cancel` | `id` | `{ state }`: `cancelled`, or the state it had already reached |
|
|
135
|
+
| `materialize` | `names`, `path`, `argv`, `cwd` | checked, then always `EACCES` in this version |
|
|
136
|
+
|
|
137
|
+
- **Requests** expire 300 s after they're made. Each uid has at most one pending request per namespace; a second one is `EAGAIN`. `status` and `cancel` see only the caller's own requests (uid 0 sees all), and others get `ENOENT`.
|
|
138
|
+
- A value request shows up in `status.waiting` and is answered by the page's `put` with its `request` id, or ended by `cancel`. `oauth` and `jerk-seal` requests can only be cancelled in this version.
|
|
139
|
+
|
|
140
|
+
## The button (`button.html`)
|
|
141
|
+
|
|
142
|
+
A popup needs a click inside a vault document, so the way into the lock popup is this button. The embedder loads `https://vault.sliccy.ai/button.html` in an iframe and, after `load`, posts:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
frame.contentWindow.postMessage({ type: 'slicc-vault', v: 1, op: 'button', button: config }, 'https://vault.sliccy.ai');
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**`config`** (`ButtonConfig` in the types):
|
|
149
|
+
|
|
150
|
+
| field | values | default |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `intent` | `{ kind: 'unlock' }`, `{ kind: 'protect' }`, or `"auto"` | `"auto"` |
|
|
153
|
+
| `label` | text instead of the default label | per intent |
|
|
154
|
+
| `size` | `s`, `m`, `l` (24, 32 and 40 px high) | `m` |
|
|
155
|
+
| `variant` | `accent`, `primary`, `secondary` | `secondary` |
|
|
156
|
+
| `treatment` | `fill`, `outline` | `fill` |
|
|
157
|
+
| `quiet`, `disabled` | booleans | `false` |
|
|
158
|
+
| `scale` | `medium`, `large` | `medium` |
|
|
159
|
+
| `theme` | `light`, `dark` | the system's colour scheme |
|
|
160
|
+
| `dir`, `lang` | `ltr`/`rtl`, a language tag | `ltr`, `en` |
|
|
161
|
+
|
|
162
|
+
- **Sending `op: 'button'` again updates the same button in place.** The frame doesn't reload, and only the fields given apply on top of the defaults.
|
|
163
|
+
- `{ op: 'focus' }` moves keyboard focus into the button.
|
|
164
|
+
- `unlock` is labelled `Unlock vault`, and `protect` (set up or remove the lock) `Vault lock`. `"auto"` shows `Unlock vault` while the vault is locked, and otherwise no button at all (size 0×0).
|
|
165
|
+
- The background is transparent, and the button sits inside a 2 px inset so its focus ring isn't clipped. Pass `theme` to match the page: Chrome paints an iframe opaque when its colour scheme differs from the embedder's, so the frame takes the scheme of `theme`, and the browser default without it.
|
|
166
|
+
|
|
167
|
+
**Events** to the embedder, all `{ type: 'slicc-vault', v: 1, event, ... }`:
|
|
168
|
+
- `ready`, once;
|
|
169
|
+
- `size { width, height, inset }`, on every change, so the embedder sizes the iframe to it;
|
|
170
|
+
- `label { label }`, on every change, for the iframe's `title`;
|
|
171
|
+
- `focus { direction: 'next' | 'previous' }`, when Tab or Shift+Tab leaves the button;
|
|
172
|
+
- `popup { intent, state }`, with `state` `opened`, `done`, `cancelled`, `closed` (the window closed without an answer) or `blocked` (the browser blocked the window).
|
|
173
|
+
|
|
174
|
+
## The popup (`popup.html`)
|
|
175
|
+
|
|
176
|
+
The button writes its intent to `intents` under a random 128-bit nonce and opens `/popup.html#<nonce>` in a 460×680 window. The popup claims that intent and shows the namespace it was opened for. Without a valid nonce it shows only "Nothing to do here". It asks the browser to make the vault's storage persistent.
|
|
177
|
+
|
|
178
|
+
| intent | what the popup does |
|
|
179
|
+
|---|---|
|
|
180
|
+
| `unlock` | unlocks with the passkey or passphrase, then closes |
|
|
181
|
+
| `protect` | while unlocked: **Protect with a passkey** (WebAuthn PRF), **Protect with a passphrase**, or, once protected, **Lock now** and **Remove protection** |
|
|
182
|
+
|
|
183
|
+
- A locked vault asks to be unlocked first, whatever the intent.
|
|
184
|
+
- A wrong passphrase or passkey says so ("That passphrase doesn't unlock this vault."), and a wrong passphrase is cleared from the field.
|
|
185
|
+
- A new passphrase needs at least 12 characters, typed twice. The popup says what's missing as you type, and **Protect** stays disabled until both fields pass.
|
|
186
|
+
- **Remove protection** asks first: "Remove protection? The vault will unlock by itself on this device."
|
|
187
|
+
|
|
188
|
+
## Types for bios and spectrum
|
|
189
|
+
|
|
190
|
+
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:
|
|
191
|
+
|
|
192
|
+
- **`EpochMs`**: milliseconds since the epoch. **`DeviceId`**: the same id bios uses for linked devices and trays, in `sourceDevice`, `device:<id>` exits and `ExitPolicyView.devices[].device`.
|
|
193
|
+
- **`Rule`**: `domain` is an exact lower-case host; `header` a lower-case header name; **empty `methods` or `paths` mean any**.
|
|
194
|
+
- **`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.
|
|
195
|
+
- **`exits`** is present only when `/etc/slicc/secrets-policy.toml` restricts that secret; absent means every exit (`page`, `proxy`, `tailscale`, `device:<id>`).
|
|
196
|
+
- **`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.
|
|
197
|
+
- **`AuditEntry`**: `forward` is a use of a value in a request, with the exit that carried it; **`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.
|
|
198
|
+
- **`VaultList.refused`** lists rules the vault dropped because they name a model or MCP endpoint.
|
|
199
|
+
- **`PendingRequest.prompt`** is untrusted, agent-written text of at most 200 characters.
|
|
200
|
+
- **`ExitPolicyView`** is read-only: the secrets `/etc/slicc/secrets-policy.toml` restricts to certain exits.
|
|
201
|
+
|
|
202
|
+
## Develop
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
npm install
|
|
206
|
+
npm run build # dist/site and dist/types
|
|
207
|
+
npm run lint
|
|
208
|
+
npm test # Chromium integration tests
|
|
209
|
+
npm start # vault on http://vault.sliccy.localhost:8790, a test embedder on seven.sliccy.localhost
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- **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.
|
|
213
|
+
- **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/`.
|
|
214
|
+
- 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.
|
|
215
|
+
|
|
216
|
+
## Release and deploy
|
|
217
|
+
|
|
218
|
+
- **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`.
|
|
219
|
+
- **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.
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
export type Protocol = 1;
|
|
2
|
+
export type MessageType = 'slicc-vault';
|
|
3
|
+
export type EpochMs = number;
|
|
4
|
+
export type DeviceId = string;
|
|
5
|
+
export type RecordKind = 'secret' | 'grant' | 'model' | 'mcp';
|
|
6
|
+
export type RecordState = 'active' | 'inert' | 'removed';
|
|
7
|
+
export type RecordSource = 'settings' | 'process' | `jerk:${string}@${DeviceId}` | `oauth:${string}` | `import:${string}`;
|
|
8
|
+
export type LockMode = 'auto' | 'passkey' | 'passphrase';
|
|
9
|
+
export type RequestKind = 'value' | 'oauth' | 'jerk-seal';
|
|
10
|
+
export type RequestState = 'pending' | 'done' | 'cancelled' | 'expired';
|
|
11
|
+
export type ErrorCode = 'EINVAL' | 'ENOENT' | 'EACCES' | 'EEXIST' | 'EAGAIN' | 'ETIMEDOUT' | 'ENOTCONN' | 'ENOTSUP';
|
|
12
|
+
export type ExitName = 'page' | 'proxy' | 'tailscale' | `device:${DeviceId}`;
|
|
13
|
+
export type ExecMode = false | 'any' | 'argv' | 'shell';
|
|
14
|
+
export type Role = 'page' | 'agent';
|
|
15
|
+
export interface Stamp {
|
|
16
|
+
pid: number;
|
|
17
|
+
ppid: number;
|
|
18
|
+
uid: number;
|
|
19
|
+
gid: number;
|
|
20
|
+
tag: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface VaultError {
|
|
23
|
+
code: ErrorCode;
|
|
24
|
+
message: string;
|
|
25
|
+
}
|
|
26
|
+
export interface Rule {
|
|
27
|
+
domain: string;
|
|
28
|
+
header: string;
|
|
29
|
+
methods: string[];
|
|
30
|
+
paths: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface RefusedRule {
|
|
33
|
+
secret: string;
|
|
34
|
+
rule: Rule;
|
|
35
|
+
reason: string;
|
|
36
|
+
}
|
|
37
|
+
export interface SecretView {
|
|
38
|
+
name: string;
|
|
39
|
+
kind: RecordKind;
|
|
40
|
+
placeholder: string;
|
|
41
|
+
state: RecordState;
|
|
42
|
+
source: RecordSource;
|
|
43
|
+
sourceDevice: DeviceId | null;
|
|
44
|
+
provider?: string;
|
|
45
|
+
account?: string;
|
|
46
|
+
domains: string[];
|
|
47
|
+
rules: Rule[];
|
|
48
|
+
exits?: ExitName[];
|
|
49
|
+
exec: ExecMode;
|
|
50
|
+
execArgv?: string[][];
|
|
51
|
+
createdAt: EpochMs;
|
|
52
|
+
usedAt?: EpochMs;
|
|
53
|
+
expiresAt?: EpochMs;
|
|
54
|
+
removedAt?: EpochMs;
|
|
55
|
+
}
|
|
56
|
+
export interface PendingRequest {
|
|
57
|
+
id: string;
|
|
58
|
+
kind: RequestKind;
|
|
59
|
+
name?: string;
|
|
60
|
+
provider?: string;
|
|
61
|
+
prompt?: string;
|
|
62
|
+
uid: number;
|
|
63
|
+
tag: string | null;
|
|
64
|
+
createdAt: EpochMs;
|
|
65
|
+
expiresAt: EpochMs;
|
|
66
|
+
}
|
|
67
|
+
export interface ScrubState {
|
|
68
|
+
needed: boolean;
|
|
69
|
+
version: number;
|
|
70
|
+
ready: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface VaultStatus {
|
|
73
|
+
version: string;
|
|
74
|
+
protocol: Protocol;
|
|
75
|
+
origin: string;
|
|
76
|
+
locked: boolean;
|
|
77
|
+
lock: LockMode;
|
|
78
|
+
scrub: ScrubState;
|
|
79
|
+
waiting: PendingRequest[];
|
|
80
|
+
}
|
|
81
|
+
export type Availability = 'connecting' | 'waiting' | 'locked' | 'ready';
|
|
82
|
+
export type PolicyFile = '/etc/fnox/config.toml' | '/etc/slicc/secrets-policy.toml';
|
|
83
|
+
export type PolicyChange = {
|
|
84
|
+
kind: 'rule';
|
|
85
|
+
secret: string;
|
|
86
|
+
rule: Rule;
|
|
87
|
+
change: 'added' | 'removed';
|
|
88
|
+
} | {
|
|
89
|
+
kind: 'exec';
|
|
90
|
+
secret: string;
|
|
91
|
+
from: ExecMode;
|
|
92
|
+
to: ExecMode;
|
|
93
|
+
} | {
|
|
94
|
+
kind: 'exits';
|
|
95
|
+
secret: string;
|
|
96
|
+
from: ExitName[] | null;
|
|
97
|
+
to: ExitName[] | null;
|
|
98
|
+
} | {
|
|
99
|
+
kind: 'limits';
|
|
100
|
+
field: string;
|
|
101
|
+
from: number;
|
|
102
|
+
to: number;
|
|
103
|
+
};
|
|
104
|
+
interface AuditBase {
|
|
105
|
+
seq: number;
|
|
106
|
+
id: string;
|
|
107
|
+
at: EpochMs;
|
|
108
|
+
name: string;
|
|
109
|
+
uid: number | null;
|
|
110
|
+
tag: string | null;
|
|
111
|
+
}
|
|
112
|
+
export interface ForwardEntry extends AuditBase {
|
|
113
|
+
type: 'forward';
|
|
114
|
+
domain: string;
|
|
115
|
+
method: string;
|
|
116
|
+
path: string;
|
|
117
|
+
rule: Rule;
|
|
118
|
+
exit: ExitName;
|
|
119
|
+
}
|
|
120
|
+
export interface RevealEntry extends AuditBase {
|
|
121
|
+
type: 'materialize';
|
|
122
|
+
exec: ExecMode;
|
|
123
|
+
path: string;
|
|
124
|
+
argv: string;
|
|
125
|
+
argvSha256: string;
|
|
126
|
+
cwd: string;
|
|
127
|
+
pid: number;
|
|
128
|
+
ppid: number;
|
|
129
|
+
ticket: string;
|
|
130
|
+
consumed: boolean;
|
|
131
|
+
}
|
|
132
|
+
export interface SealEntry extends AuditBase {
|
|
133
|
+
type: 'seal';
|
|
134
|
+
source: RecordSource;
|
|
135
|
+
rotated: boolean;
|
|
136
|
+
}
|
|
137
|
+
export interface RemoveEntry extends AuditBase {
|
|
138
|
+
type: 'remove' | 'restore';
|
|
139
|
+
}
|
|
140
|
+
export interface PolicyEntry {
|
|
141
|
+
type: 'policy';
|
|
142
|
+
seq: number;
|
|
143
|
+
id: string;
|
|
144
|
+
at: EpochMs;
|
|
145
|
+
file: PolicyFile;
|
|
146
|
+
sha256: string;
|
|
147
|
+
accepted: boolean;
|
|
148
|
+
changes: PolicyChange[];
|
|
149
|
+
warning?: string;
|
|
150
|
+
}
|
|
151
|
+
export type AuditEntry = ForwardEntry | RevealEntry | SealEntry | RemoveEntry | PolicyEntry;
|
|
152
|
+
export interface AuditPage {
|
|
153
|
+
audit: AuditEntry[];
|
|
154
|
+
more: boolean;
|
|
155
|
+
next?: number;
|
|
156
|
+
}
|
|
157
|
+
export interface ExitPolicyView {
|
|
158
|
+
file: '/etc/slicc/secrets-policy.toml';
|
|
159
|
+
sha256: string | null;
|
|
160
|
+
restricted: {
|
|
161
|
+
name: string;
|
|
162
|
+
exits: ExitName[];
|
|
163
|
+
}[];
|
|
164
|
+
}
|
|
165
|
+
export interface VaultList {
|
|
166
|
+
secrets: SecretView[];
|
|
167
|
+
refused: RefusedRule[];
|
|
168
|
+
audit: AuditEntry[];
|
|
169
|
+
auditMore: boolean;
|
|
170
|
+
exits: ExitPolicyView;
|
|
171
|
+
}
|
|
172
|
+
export interface SealResult {
|
|
173
|
+
name: string;
|
|
174
|
+
placeholder: string;
|
|
175
|
+
state: RecordState;
|
|
176
|
+
}
|
|
177
|
+
export type ImportKind = 'bundle' | 'keepass' | 'six';
|
|
178
|
+
export interface ImportCandidate {
|
|
179
|
+
name: string;
|
|
180
|
+
exists: boolean;
|
|
181
|
+
suggestedRules?: string;
|
|
182
|
+
}
|
|
183
|
+
export type ImportResult = {
|
|
184
|
+
stage: 'pick';
|
|
185
|
+
candidates: ImportCandidate[];
|
|
186
|
+
} | {
|
|
187
|
+
stage: 'done';
|
|
188
|
+
imported: SealResult[];
|
|
189
|
+
skipped: string[];
|
|
190
|
+
};
|
|
191
|
+
export type PopupIntent = {
|
|
192
|
+
kind: 'unlock';
|
|
193
|
+
} | {
|
|
194
|
+
kind: 'protect';
|
|
195
|
+
};
|
|
196
|
+
export type PopupState = 'opened' | 'done' | 'cancelled' | 'closed' | 'blocked';
|
|
197
|
+
export interface ButtonConfig {
|
|
198
|
+
intent: PopupIntent | 'auto';
|
|
199
|
+
label?: string;
|
|
200
|
+
size?: 's' | 'm' | 'l';
|
|
201
|
+
variant?: 'accent' | 'primary' | 'secondary';
|
|
202
|
+
treatment?: 'fill' | 'outline';
|
|
203
|
+
quiet?: boolean;
|
|
204
|
+
scale?: 'medium' | 'large';
|
|
205
|
+
theme?: 'light' | 'dark';
|
|
206
|
+
dir?: 'ltr' | 'rtl';
|
|
207
|
+
lang?: string;
|
|
208
|
+
disabled?: boolean;
|
|
209
|
+
}
|
|
210
|
+
export type SealedRequest = {
|
|
211
|
+
v: Protocol;
|
|
212
|
+
op: string;
|
|
213
|
+
} & Record<string, unknown>;
|
|
214
|
+
export type SealedReply = {
|
|
215
|
+
v: Protocol;
|
|
216
|
+
} & Record<string, unknown>;
|
|
217
|
+
export type Op = {
|
|
218
|
+
op: 'sealed';
|
|
219
|
+
req: SealedRequest;
|
|
220
|
+
stamp: Stamp;
|
|
221
|
+
} | {
|
|
222
|
+
op: 'status';
|
|
223
|
+
} | {
|
|
224
|
+
op: 'placeholders';
|
|
225
|
+
} | {
|
|
226
|
+
op: 'list';
|
|
227
|
+
} | {
|
|
228
|
+
op: 'audit';
|
|
229
|
+
before?: number;
|
|
230
|
+
limit?: number;
|
|
231
|
+
} | {
|
|
232
|
+
op: 'put';
|
|
233
|
+
name: string;
|
|
234
|
+
value: string;
|
|
235
|
+
request?: string;
|
|
236
|
+
} | {
|
|
237
|
+
op: 'cancel';
|
|
238
|
+
request: string;
|
|
239
|
+
} | {
|
|
240
|
+
op: 'remove';
|
|
241
|
+
name: string;
|
|
242
|
+
kind?: RecordKind;
|
|
243
|
+
} | {
|
|
244
|
+
op: 'restore';
|
|
245
|
+
name: string;
|
|
246
|
+
kind?: RecordKind;
|
|
247
|
+
} | {
|
|
248
|
+
op: 'seal';
|
|
249
|
+
name: string;
|
|
250
|
+
value: string;
|
|
251
|
+
source: RecordSource;
|
|
252
|
+
kind?: RecordKind;
|
|
253
|
+
expiresAt?: EpochMs;
|
|
254
|
+
refresh?: string;
|
|
255
|
+
} | {
|
|
256
|
+
op: 'import';
|
|
257
|
+
kind: ImportKind;
|
|
258
|
+
bytes: Uint8Array;
|
|
259
|
+
password?: string;
|
|
260
|
+
pick?: string[];
|
|
261
|
+
} | {
|
|
262
|
+
op: 'export';
|
|
263
|
+
passphrase: string;
|
|
264
|
+
} | {
|
|
265
|
+
op: 'lock';
|
|
266
|
+
};
|
|
267
|
+
export type OpReply = {
|
|
268
|
+
op: 'sealed';
|
|
269
|
+
reply: SealedReply;
|
|
270
|
+
} | {
|
|
271
|
+
op: 'status';
|
|
272
|
+
status: VaultStatus;
|
|
273
|
+
} | {
|
|
274
|
+
op: 'placeholders';
|
|
275
|
+
placeholders: string[];
|
|
276
|
+
scrub: ScrubState;
|
|
277
|
+
} | {
|
|
278
|
+
op: 'list';
|
|
279
|
+
list: VaultList;
|
|
280
|
+
} | ({
|
|
281
|
+
op: 'audit';
|
|
282
|
+
} & AuditPage) | ({
|
|
283
|
+
op: 'put' | 'seal' | 'remove' | 'restore';
|
|
284
|
+
} & SealResult) | {
|
|
285
|
+
op: 'cancel';
|
|
286
|
+
state: RequestState;
|
|
287
|
+
} | ({
|
|
288
|
+
op: 'import';
|
|
289
|
+
} & ImportResult) | {
|
|
290
|
+
op: 'export';
|
|
291
|
+
bundle: Uint8Array;
|
|
292
|
+
} | {
|
|
293
|
+
op: 'lock';
|
|
294
|
+
};
|
|
295
|
+
export type Request = Op & {
|
|
296
|
+
v: Protocol;
|
|
297
|
+
id: number;
|
|
298
|
+
};
|
|
299
|
+
export type Reply = (OpReply & {
|
|
300
|
+
v: Protocol;
|
|
301
|
+
id: number;
|
|
302
|
+
}) | {
|
|
303
|
+
v: Protocol;
|
|
304
|
+
id: number;
|
|
305
|
+
error: VaultError;
|
|
306
|
+
};
|
|
307
|
+
export type ChangedPart = 'secrets' | 'audit' | 'exits';
|
|
308
|
+
export type VaultEvent = {
|
|
309
|
+
v: Protocol;
|
|
310
|
+
event: 'status';
|
|
311
|
+
status: VaultStatus;
|
|
312
|
+
} | {
|
|
313
|
+
v: Protocol;
|
|
314
|
+
event: 'placeholders';
|
|
315
|
+
placeholders: string[];
|
|
316
|
+
scrub: ScrubState;
|
|
317
|
+
} | {
|
|
318
|
+
v: Protocol;
|
|
319
|
+
event: 'changed';
|
|
320
|
+
parts: ChangedPart[];
|
|
321
|
+
};
|
|
322
|
+
export interface Hello {
|
|
323
|
+
type: MessageType;
|
|
324
|
+
v: Protocol;
|
|
325
|
+
op: 'hello';
|
|
326
|
+
role?: Role;
|
|
327
|
+
}
|
|
328
|
+
export type Greeting = {
|
|
329
|
+
v: Protocol;
|
|
330
|
+
op: 'welcome';
|
|
331
|
+
role: Role;
|
|
332
|
+
status: VaultStatus;
|
|
333
|
+
} | {
|
|
334
|
+
v: Protocol;
|
|
335
|
+
op: 'refused';
|
|
336
|
+
error: VaultError;
|
|
337
|
+
};
|
|
338
|
+
export type ButtonMessage = {
|
|
339
|
+
type: MessageType;
|
|
340
|
+
v: Protocol;
|
|
341
|
+
op: 'button';
|
|
342
|
+
button: ButtonConfig;
|
|
343
|
+
} | {
|
|
344
|
+
type: MessageType;
|
|
345
|
+
v: Protocol;
|
|
346
|
+
op: 'focus';
|
|
347
|
+
};
|
|
348
|
+
export type ButtonEvent = {
|
|
349
|
+
type: MessageType;
|
|
350
|
+
v: Protocol;
|
|
351
|
+
} & ({
|
|
352
|
+
event: 'ready';
|
|
353
|
+
} | {
|
|
354
|
+
event: 'size';
|
|
355
|
+
width: number;
|
|
356
|
+
height: number;
|
|
357
|
+
inset: number;
|
|
358
|
+
} | {
|
|
359
|
+
event: 'label';
|
|
360
|
+
label: string;
|
|
361
|
+
} | {
|
|
362
|
+
event: 'focus';
|
|
363
|
+
direction: 'next' | 'previous';
|
|
364
|
+
} | {
|
|
365
|
+
event: 'popup';
|
|
366
|
+
intent: PopupIntent;
|
|
367
|
+
state: PopupState;
|
|
368
|
+
});
|
|
369
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-vault",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ai-ecoverse/slicc-vault.git"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=24"
|
|
13
|
+
},
|
|
14
|
+
"fledgling": {
|
|
15
|
+
"provider": "github",
|
|
16
|
+
"workflow": "release.yml",
|
|
17
|
+
"repo": "ai-ecoverse/slicc-vault",
|
|
18
|
+
"publish": true
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"provenance": true
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/types/contract.d.ts"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"types": "./dist/types/contract.d.ts",
|
|
30
|
+
"files": [
|
|
31
|
+
"dist/types"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"prepare": "[ ! -e .git ] || lefthook install",
|
|
35
|
+
"build": "node build.mjs && tsc -p tsconfig.build.json",
|
|
36
|
+
"prepack": "npm run build",
|
|
37
|
+
"start": "npm run build && node test/integration/serve.mjs",
|
|
38
|
+
"pretest": "npm run build && playwright-core install --no-shell chromium",
|
|
39
|
+
"test": "node --test --test-timeout=300000 --test-global-setup=node_modules/@ai-ecoverse/slicc-shared-web/harness/global.mjs 'test/integration/*.test.mjs'",
|
|
40
|
+
"test:unit": "NODE_NO_WARNINGS=1 mcr -c node_modules/@ai-ecoverse/slicc-shared-web/mcr.config.mjs node --import fake-indexeddb/auto --test --test-force-exit 'test/unit/**/*.test.mjs'",
|
|
41
|
+
"typecheck": "tsc",
|
|
42
|
+
"lint": "slicc-lint",
|
|
43
|
+
"format": "biome check --write .",
|
|
44
|
+
"trust": "npx fledgling sync -y --skip-publish",
|
|
45
|
+
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
|
|
46
|
+
"release": "semantic-release"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@ai-ecoverse/slicc-shared-web": "1.13.0",
|
|
50
|
+
"@biomejs/biome": "2.5.15",
|
|
51
|
+
"@happy-dom/global-registrator": "20.14.5",
|
|
52
|
+
"@spectrum-css/button": "14.2.0",
|
|
53
|
+
"@spectrum-css/textfield": "8.2.0",
|
|
54
|
+
"@spectrum-css/tokens": "16.0.2",
|
|
55
|
+
"esbuild": "0.28.2",
|
|
56
|
+
"fake-indexeddb": "6.2.5",
|
|
57
|
+
"happy-dom": "20.14.5",
|
|
58
|
+
"lefthook": "2.1.17",
|
|
59
|
+
"monocart-coverage-reports": "2.13.0",
|
|
60
|
+
"playwright-core": "1.63.0",
|
|
61
|
+
"semantic-release": "25.0.9",
|
|
62
|
+
"typescript": "7.0.2"
|
|
63
|
+
}
|
|
64
|
+
}
|