@k9o/kibo 1.0.0 → 1.1.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 +274 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +4 -4
- package/package.json +2 -2
- package/src/client-factory.ts +1 -1
- package/src/crypto-types.ts +1 -1
- package/src/index.ts +2 -2
- package/src/types.ts +4 -4
- package/src/vault.ts +6 -6
package/README.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# @k9o/kibo
|
|
2
|
+
|
|
3
|
+
Kibo is the Kilimanjaro vault SDK. Store and reveal secrets, keep wrapped data keys, and encrypt on the client with keys held in your own KMS.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Install](#install)
|
|
8
|
+
- [Quick start](#quick-start)
|
|
9
|
+
- [Reveal a secret](#reveal-a-secret)
|
|
10
|
+
- [Store a wrapped data key](#store-a-wrapped-data-key)
|
|
11
|
+
- [Encrypt and decrypt](#encrypt-and-decrypt)
|
|
12
|
+
- [Handle errors](#handle-errors)
|
|
13
|
+
- [Appendix](#appendix)
|
|
14
|
+
- [Client options](#client-options)
|
|
15
|
+
- [Methods](#methods)
|
|
16
|
+
- [Encrypt and decrypt fields](#encrypt-and-decrypt-fields)
|
|
17
|
+
- [Error codes](#error-codes)
|
|
18
|
+
- [Related packages](#related-packages)
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @k9o/kibo @k9o/core
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Bun works too: `bun add @k9o/kibo @k9o/core`.
|
|
27
|
+
|
|
28
|
+
- Needs a runtime with global `fetch`: Node 18+, Bun 1.1+, or a modern browser.
|
|
29
|
+
- Needs `@k9o/core`, which provides the client. The vault is available as `client.kibo`.
|
|
30
|
+
- `encrypt` and `decrypt` also need the Kibo core (WebAssembly). See [encrypt and decrypt](#encrypt-and-decrypt).
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
The examples use a `tasks` project that keeps a webhook signing key in the vault. Create the client once.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createClient } from "@k9o/core";
|
|
38
|
+
|
|
39
|
+
const client = createClient({
|
|
40
|
+
apiKey: process.env.K9O_API_KEY,
|
|
41
|
+
orgSlug: "acme",
|
|
42
|
+
projectSlug: "member-portal",
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For a custom host, pass the full organization host as `baseUrl` and `projectSlug`, and leave out `orgSlug`. See [client options](#client-options).
|
|
47
|
+
|
|
48
|
+
Create a secret. `region` is required for every secret method except `list`.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const created = await client.kibo.vault.secrets.create({
|
|
52
|
+
name: "tasks/webhook-signing-key",
|
|
53
|
+
value: "whsec_example",
|
|
54
|
+
region: "de01",
|
|
55
|
+
});
|
|
56
|
+
const secret = created.data;
|
|
57
|
+
|
|
58
|
+
console.log(secret.name, secret.version);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
tasks/webhook-signing-key 1
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
List secrets under a prefix. Listing returns metadata, never values.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const listed = await client.kibo.vault.secrets.list({ prefix: "tasks/" });
|
|
69
|
+
const secrets = listed.data.secrets;
|
|
70
|
+
|
|
71
|
+
for (const item of secrets) {
|
|
72
|
+
console.log(item.name, item.version);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
tasks/webhook-signing-key 1
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Every method resolves to `{ data, meta }`. See [methods](#methods) for the full list.
|
|
81
|
+
|
|
82
|
+
## Reveal a secret
|
|
83
|
+
|
|
84
|
+
Revealing takes two calls: ask for a single-use challenge, then present it.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const challenge = await client.kibo.vault.secrets.revealChallenge("tasks/webhook-signing-key", { region: "de01" });
|
|
88
|
+
const revealed = await client.kibo.vault.secrets.reveal("tasks/webhook-signing-key", {
|
|
89
|
+
region: "de01",
|
|
90
|
+
challenge: challenge.data.challenge,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
console.log(revealed.data.value);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
whsec_example
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Store a wrapped data key
|
|
101
|
+
|
|
102
|
+
`zkDeks.store` saves an envelope you produced yourself. The platform keeps it as opaque bytes and never reads it.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const stored = await client.kibo.vault.zkDeks.store({
|
|
106
|
+
envelope: "BASE64_ENVELOPE",
|
|
107
|
+
provider: "aws-kms",
|
|
108
|
+
keyVersionRef: "arn:aws:kms:eu-central-1:111122223333:key/example",
|
|
109
|
+
});
|
|
110
|
+
const handle = stored.data.id;
|
|
111
|
+
|
|
112
|
+
const fetched = await client.kibo.vault.zkDeks.fetch(handle);
|
|
113
|
+
console.log(fetched.data.provider);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
aws-kms
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Encrypt and decrypt
|
|
121
|
+
|
|
122
|
+
`encrypt` and `decrypt` run on your machine against your own KMS. They wrap the Kibo core, a WebAssembly module.
|
|
123
|
+
This package has no cryptography of its own.
|
|
124
|
+
|
|
125
|
+
- The core is loaded from `wasm/kibo_kms_rest_ffi.js` inside the package.
|
|
126
|
+
- Or load it yourself and pass it to `setZkWasm`.
|
|
127
|
+
- Without the core, both calls fail with `KIBO_WASM_ERROR`. See [handle errors](#handle-errors).
|
|
128
|
+
- The vault methods above work without the core.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { encrypt, decrypt } from "@k9o/kibo";
|
|
132
|
+
|
|
133
|
+
const kmsToken = process.env.KMS_FEDERATED_TOKEN ?? "";
|
|
134
|
+
const context = [{ key: "collection", value: "tasks" }];
|
|
135
|
+
const title = new TextEncoder().encode("Write the README");
|
|
136
|
+
|
|
137
|
+
const sealed = await encrypt({
|
|
138
|
+
provider: "aws-kms",
|
|
139
|
+
posture: "byok",
|
|
140
|
+
kms_cred: { secret: kmsToken, expires_at_unix_secs: 1_800_000_000 },
|
|
141
|
+
key_ref: { version: 1, external_ref: "arn:aws:kms:eu-central-1:111122223333:key/example" },
|
|
142
|
+
provider_config: { aws_region: "eu-central-1" },
|
|
143
|
+
attestation_tier: "software",
|
|
144
|
+
encryption_context: context,
|
|
145
|
+
plaintext: title,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const opened = await decrypt({
|
|
149
|
+
kms_cred: { secret: kmsToken, expires_at_unix_secs: 1_800_000_000 },
|
|
150
|
+
provider_config: { aws_region: "eu-central-1" },
|
|
151
|
+
envelope: sealed.envelope,
|
|
152
|
+
strategy: "local_envelope",
|
|
153
|
+
attestation_tier: "software",
|
|
154
|
+
encryption_context: context,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log(new TextDecoder().decode(opened.plaintext));
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
Write the README
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- Give `decrypt` the same `strategy`, `attestation_tier` and `encryption_context` you used to encrypt.
|
|
165
|
+
- Keep the returned `envelope` yourself, or save it with `zkDeks.store`.
|
|
166
|
+
- All fields are described in [encrypt and decrypt fields](#encrypt-and-decrypt-fields).
|
|
167
|
+
|
|
168
|
+
## Handle errors
|
|
169
|
+
|
|
170
|
+
Every failure is a `KError` from `@k9o/core`. Check `code`.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { KError } from "@k9o/core";
|
|
174
|
+
import { encrypt } from "@k9o/kibo";
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
await encrypt(input);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error instanceof KError && error.code === "KIBO_WASM_ERROR") {
|
|
180
|
+
console.log(error.message);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
the Kibo zero-knowledge core (wasm) is not bundled in this build of @k9o/kibo; encrypt and decrypt are unavailable
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
See [error codes](#error-codes).
|
|
190
|
+
|
|
191
|
+
## Appendix
|
|
192
|
+
|
|
193
|
+
### Client options
|
|
194
|
+
|
|
195
|
+
The vault is reached through `createClient` from `@k9o/core`. Pass only what you need.
|
|
196
|
+
|
|
197
|
+
| Form | Options | Use |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| Default | `apiKey`, `orgSlug`, `projectSlug` | Platform at `https://api.k9o.io` |
|
|
200
|
+
| Custom host | `apiKey`, `baseUrl`, `projectSlug` | Self-hosted or custom host |
|
|
201
|
+
|
|
202
|
+
Without a project, vault methods throw `K9O_MISSING_PROJECT_CONTEXT`.
|
|
203
|
+
|
|
204
|
+
### Methods
|
|
205
|
+
|
|
206
|
+
All are under `client.kibo.vault`.
|
|
207
|
+
|
|
208
|
+
| Method | What it does |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `secrets.list({ prefix?, cursor?, limit? })` | List secret metadata |
|
|
211
|
+
| `secrets.get(name, { region })` | Read one secret's metadata |
|
|
212
|
+
| `secrets.create({ name, value, region, visibility?, metadata? })` | Seal a new secret |
|
|
213
|
+
| `secrets.revealChallenge(name, { region })` | Mint a single-use reveal challenge |
|
|
214
|
+
| `secrets.reveal(name, { region, challenge })` | Return the plaintext value |
|
|
215
|
+
| `secrets.update(name, { region, value, metadata? })` | Set a new value and bump the version |
|
|
216
|
+
| `secrets.rotate(name, { region })` | Re-wrap the secret under a fresh key |
|
|
217
|
+
| `secrets.move(name, { region, newPath })` | Rename the secret's path |
|
|
218
|
+
| `secrets.delete(name, { region })` | Retire the secret |
|
|
219
|
+
| `zkDeks.store({ envelope, provider, keyVersionRef })` | Store a wrapped data key envelope |
|
|
220
|
+
| `zkDeks.fetch(id)` | Fetch a stored envelope by its handle |
|
|
221
|
+
|
|
222
|
+
Exported from `@k9o/kibo` directly:
|
|
223
|
+
|
|
224
|
+
| Function | What it does |
|
|
225
|
+
|---|---|
|
|
226
|
+
| `encrypt(input)` | Encrypt bytes with your KMS; returns `{ envelope }` |
|
|
227
|
+
| `decrypt(input)` | Decrypt an envelope; returns `{ plaintext }` |
|
|
228
|
+
| `setZkWasm(core)` | Supply the Kibo core yourself |
|
|
229
|
+
| `createKiboClient(config)` | Build a vault client from a core HTTP config |
|
|
230
|
+
|
|
231
|
+
`visibility` is `{ kind: "private" }` (default) or `{ kind: "shared", principals?, roles? }`.
|
|
232
|
+
|
|
233
|
+
### Encrypt and decrypt fields
|
|
234
|
+
|
|
235
|
+
| Field | Used by | Meaning |
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| `provider` | encrypt | `aws-kms`, `gcp-kms`, `azure-kv` or `hashicorp-vault` |
|
|
238
|
+
| `posture` | encrypt | `byok` or `hyok` |
|
|
239
|
+
| `kms_cred` | both | `{ secret, expires_at_unix_secs }`, a short-lived federated token you issue |
|
|
240
|
+
| `key_ref` | encrypt | `{ version, external_ref }` identifying your KMS key |
|
|
241
|
+
| `provider_config` | both | `{ aws_region?, vault_address? }` |
|
|
242
|
+
| `attestation_tier` | both | `software`, `hardware_backed` or `confidential` |
|
|
243
|
+
| `encryption_context` | both | List of `{ key, value }` pairs; `kibo:` keys are reserved |
|
|
244
|
+
| `strategy` | decrypt (optional on encrypt) | `local_envelope` or `customer_kms_oracle` |
|
|
245
|
+
| `oracle_mode` | encrypt, optional | `brokered` or `direct` |
|
|
246
|
+
| `posture_policy` | encrypt, optional | Minimum custody posture and allowed oracle modes |
|
|
247
|
+
| `plaintext` | encrypt | Bytes to encrypt |
|
|
248
|
+
| `envelope` | decrypt | Bytes returned by `encrypt` |
|
|
249
|
+
|
|
250
|
+
### Error codes
|
|
251
|
+
|
|
252
|
+
| Code | When |
|
|
253
|
+
|---|---|
|
|
254
|
+
| `KIBO_WASM_ERROR` | The Kibo core is not bundled in your build and was not supplied |
|
|
255
|
+
| `KIBO_WASM_DECODE_ERROR` | The core returned something other than bytes |
|
|
256
|
+
| `K9O_CRYPTO_POSTURE_FLOOR_VIOLATION` | `posture` is below the floor in `posture_policy`; `context` has `dimension`, `requested`, `declared` |
|
|
257
|
+
| `UPSTREAM_KMS_ERROR` | Your own KMS failed the call |
|
|
258
|
+
| `K9O_MISSING_PROJECT_CONTEXT` | A vault method was called without a bound project |
|
|
259
|
+
|
|
260
|
+
Other codes from the core are passed through unchanged.
|
|
261
|
+
|
|
262
|
+
### Related packages
|
|
263
|
+
|
|
264
|
+
| Package | Purpose |
|
|
265
|
+
|---|---|
|
|
266
|
+
| `@k9o/core` | The platform SDK; composes this package as `client.kibo` |
|
|
267
|
+
| `@k9o/kibo-react-native` | Client-side encryption for React Native |
|
|
268
|
+
| `@k9o/cli` | `k9o vault-secret` works with the same secrets |
|
|
269
|
+
|
|
270
|
+
Documentation: <https://kilimanjaro.dev>
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
Apache-2.0 © Formelo Inc. See `LICENSE` and `NOTICE`.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,16 +6,16 @@ export { AttestationTier, SecretVisibility } from './types.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* `client.kibo.vault.*` — the Kibo vault surface.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* `vault.zkDeks.{store,fetch}` — member-plane,
|
|
10
10
|
* project-scoped storage of the opaque customer-wrapped DEK envelope. Zero
|
|
11
11
|
* crypto SDK-side: this is a plain authenticated REST round-trip carrying an
|
|
12
12
|
* opaque blob (the wrapping happens in the ZK core, piece C). The encryption
|
|
13
|
-
* surface (`kibo.encrypt`/`decrypt`)
|
|
14
|
-
*
|
|
13
|
+
* surface (`kibo.encrypt`/`decrypt`) is provided by the ZK core binding when it is
|
|
14
|
+
* bundled.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
declare function makeVaultNamespace(config: HttpClientConfig): {
|
|
18
|
-
/** ZK-DEK opaque store/fetch (
|
|
18
|
+
/** ZK-DEK opaque store/fetch (F.5). */
|
|
19
19
|
zkDeks: {
|
|
20
20
|
/**
|
|
21
21
|
* Store an opaque customer-wrapped DEK envelope.
|
|
@@ -31,7 +31,7 @@ declare function makeVaultNamespace(config: HttpClientConfig): {
|
|
|
31
31
|
fetch(id: string): Promise<Envelope<ZkDekRecord>>;
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* typed secrets surface (`services/gateway/src/routes/vault-secrets.ts`) — the full family:
|
|
35
35
|
* list/get/create/revealChallenge/reveal/update/rotate/move/delete. Every operation on a NAMED
|
|
36
36
|
* secret (everything except `list`) requires `region` — live-verified against staging, not
|
|
37
37
|
* documented in the route's OpenAPI schema. `list` is the one exception (verified it does NOT
|
|
@@ -118,7 +118,7 @@ declare function makeVaultNamespace(config: HttpClientConfig): {
|
|
|
118
118
|
|
|
119
119
|
/** Shape of the per-instance client returned by `createKiboClient`. */
|
|
120
120
|
type KiboClient = {
|
|
121
|
-
/** Vault surface — `client.kibo.vault.zkDeks.{store,fetch}` (
|
|
121
|
+
/** Vault surface — `client.kibo.vault.zkDeks.{store,fetch}` (F.5). */
|
|
122
122
|
vault: ReturnType<typeof makeVaultNamespace>;
|
|
123
123
|
};
|
|
124
124
|
/** Construct a Kibo client bound to the supplied (core) HTTP config. */
|
|
@@ -156,7 +156,7 @@ interface KiboAllowedOracleModes {
|
|
|
156
156
|
brokered: boolean;
|
|
157
157
|
direct: boolean;
|
|
158
158
|
}
|
|
159
|
-
/** Project-declared crypto-posture floor
|
|
159
|
+
/** Project-declared crypto-posture floor. Omit fields for the
|
|
160
160
|
* most-permissive default (`platform_managed` floor + both oracle modes). */
|
|
161
161
|
interface KiboPosturePolicy {
|
|
162
162
|
min_custody_posture?: KiboMinPosture;
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ function requireProjectSlug(config) {
|
|
|
24
24
|
}
|
|
25
25
|
function makeVaultNamespace(config) {
|
|
26
26
|
return {
|
|
27
|
-
/** ZK-DEK opaque store/fetch (
|
|
27
|
+
/** ZK-DEK opaque store/fetch (F.5). */
|
|
28
28
|
zkDeks: {
|
|
29
29
|
/**
|
|
30
30
|
* Store an opaque customer-wrapped DEK envelope.
|
|
@@ -51,7 +51,7 @@ function makeVaultNamespace(config) {
|
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* typed secrets surface (`services/gateway/src/routes/vault-secrets.ts`) — the full family:
|
|
55
55
|
* list/get/create/revealChallenge/reveal/update/rotate/move/delete. Every operation on a NAMED
|
|
56
56
|
* secret (everything except `list`) requires `region` — live-verified against staging, not
|
|
57
57
|
* documented in the route's OpenAPI schema. `list` is the one exception (verified it does NOT
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client-factory.ts","../src/vault.ts","../src/crypto.ts"],"sourcesContent":["/**\n * `createKiboClient(config)` — per-instance Kibo (ZK vault) SDK factory.\n *\n * Composes into `@k9o/core` as `client.kibo` (re-export, available when a\n * project is bound — the same composition pattern as `@k9o/gate`). Reuses the\n * core HTTP layer (`@k9o/core/http`) so the bearer/header/retry contract is\n * identical to the rest of the SDK pool.\n */\n\nimport { type HttpClientConfig } from \"@k9o/core/http\";\nimport { makeVaultNamespace } from \"./vault.ts\";\n\n/** Shape of the per-instance client returned by `createKiboClient`. */\nexport type KiboClient = {\n /** Vault surface — `client.kibo.vault.zkDeks.{store,fetch}` (ch08 §11.3.2 F.5). */\n vault: ReturnType<typeof makeVaultNamespace>;\n};\n\n/** Construct a Kibo client bound to the supplied (core) HTTP config. */\nexport function createKiboClient(config: HttpClientConfig): KiboClient {\n return {\n vault: makeVaultNamespace(config),\n };\n}\n","/**\n * `client.kibo.vault.*` — the Kibo vault surface.\n *\n * v0.1.0: `vault.zkDeks.{store,fetch}` (ch08 §11.3.2 F.5) — member-plane,\n * project-scoped storage of the opaque customer-wrapped DEK envelope. Zero\n * crypto SDK-side: this is a plain authenticated REST round-trip carrying an\n * opaque blob (the wrapping happens in the ZK core, piece C). The encryption\n * surface (`kibo.encrypt`/`decrypt`) lands in a later version once the C\n * binding is consumable.\n */\n\nimport { request, type HttpClientConfig } from \"@k9o/core/http\";\nimport { projectPath } from \"@k9o/core/url-grammar\";\nimport { KError } from \"@k9o/core/errors\";\nimport type { Envelope } from \"@k9o/core/envelope\";\nimport type {\n StoreZkDekInput, ZkDekHandle, ZkDekRecord,\n CreateSecretInput, SecretSummary, ListSecretsInput, ListSecretsResult,\n RevealChallenge, RevealSecretResult, UpdateSecretInput, UpdateSecretResult,\n RotateSecretResult, MoveSecretResult,\n} from \"./types.ts\";\n\n/** UTF-8-safe base64 encode, usable across browser/Node/Bun (unlike `Buffer`) — the vault-secrets wire\n * `value` field is base64 (live-verified; the OpenAPI \"plaintext to seal\" description does not mean\n * raw-on-the-wire). */\nfunction encodeBase64Utf8(plaintext: string): string {\n return btoa(unescape(encodeURIComponent(plaintext)));\n}\n\n/** Inverse of {@link encodeBase64Utf8} — decodes a revealed wire `value` back to plaintext. */\nfunction decodeBase64Utf8(b64: string): string {\n return decodeURIComponent(escape(atob(b64)));\n}\n\n/** Both `vault.zkDeks` and `vault.secrets` require a project context (member-plane, ch08 §11.3/§14). */\nfunction requireProjectSlug(config: HttpClientConfig): string {\n if (config.projectSlug === undefined || config.projectSlug.length === 0) {\n throw new KError({\n code: \"K9O_MISSING_PROJECT_CONTEXT\",\n message: \"client.kibo.vault requires a bound projectSlug (member-plane resource)\",\n context: {},\n recommendation: \"Pass projectSlug when constructing the client.\",\n });\n }\n return config.projectSlug;\n}\n\nexport function makeVaultNamespace(config: HttpClientConfig) {\n return {\n /** ZK-DEK opaque store/fetch (ch08 §11.3.2 F.5). */\n zkDeks: {\n /**\n * Store an opaque customer-wrapped DEK envelope.\n * `POST /@<projectSlug>/vault/zk-deks` → the `zkdek_<base62>` handle.\n * The SDK ships the envelope verbatim; it is never parsed here.\n */\n async store(input: StoreZkDekInput): Promise<Envelope<ZkDekHandle>> {\n return request<ZkDekHandle>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), \"vault/zk-deks\"),\n body: input,\n });\n },\n\n /**\n * Fetch a stored `WrappedDek` by its handle.\n * `GET /@<projectSlug>/vault/zk-deks/<id>` → the opaque envelope + the\n * non-secret `provider`/`keyVersionRef` the caller needs to unwrap it.\n */\n async fetch(id: string): Promise<Envelope<ZkDekRecord>> {\n return request<ZkDekRecord>(config, {\n method: \"GET\",\n path: projectPath(requireProjectSlug(config), `vault/zk-deks/${encodeURIComponent(id)}`),\n });\n },\n },\n\n /**\n * §14 typed secrets surface (`services/gateway/src/routes/vault-secrets.ts`) — the full family:\n * list/get/create/revealChallenge/reveal/update/rotate/move/delete. Every operation on a NAMED\n * secret (everything except `list`) requires `region` — live-verified against staging, not\n * documented in the route's OpenAPI schema. `list` is the one exception (verified it does NOT\n * need `region`).\n *\n * Response shapes for `update`/`rotate`/`move` are typed to what the gateway ACTUALLY returns\n * (live-verified), not the OpenAPI's documented (and inaccurate for these three) `SecretSummary` —\n * see the 🔴 notes on `UpdateSecretResult`/`RotateSecretResult`/`MoveSecretResult` in `types.ts`.\n */\n secrets: {\n /** List secrets (metadata only — never lists ciphertext). `GET /@<projectSlug>/vault/secrets`.\n * The one secrets operation that does NOT need `region` (live-verified). */\n async list(input?: ListSecretsInput): Promise<Envelope<ListSecretsResult>> {\n return request<ListSecretsResult>(config, {\n method: \"GET\",\n path: projectPath(requireProjectSlug(config), \"vault/secrets\"),\n query: { prefix: input?.prefix, cursor: input?.cursor, limit: input?.limit },\n });\n },\n\n /**\n * Read a secret's metadata (never its value — use `revealChallenge`+`reveal` for that).\n * `GET /@<projectSlug>/vault/secrets/<name>?region=<region>`.\n *\n * 🔴 KNOWN LIVE BUG (staging, 2026-09-05): this call currently fails with\n * `K9O_REVEAL_CHALLENGE_REQUIRED` on every attempt — the gateway/kibo does not honor the\n * documented \"no `X-K9O-Reveal-Challenge` header ⇒ return metadata\" behavior; it demands a\n * challenge unconditionally. Implemented per the documented contract (this is a backend defect,\n * not a client workaround); `list({prefix: exactName})` is the current working alternative for a\n * single secret's metadata until the gateway/kibo side is fixed.\n */\n async get(name: string, opts: { region: string }): Promise<Envelope<SecretSummary>> {\n return request<SecretSummary>(config, {\n method: \"GET\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region: opts.region },\n });\n },\n\n /**\n * Create a secret under the caller's own auth.\n * `POST /@<projectSlug>/vault/secrets/<name>?region=<region>` → the `sec_<base62>` summary.\n *\n * 🔴 `region` is REQUIRED on the query string, live-verified against staging — the vault-forward\n * proxy fail-closes with `K9O_INVALID_FIELD_VALUE` (\"this request requires an authoritative\n * region\") without it. Distinct from any `region` carried inside `input.value`'s envelope (e.g.\n * the BYOS aws_sigv4 STS-signing region): this one scopes the vault WRITE itself, mirroring the\n * gateway's `gatewayRegion()` residency guard (region-guard.ts) that closed a 2026-08 wrong-region\n * incident for the audit path — the request-shape unit tests never caught this because they don't\n * exercise a real gateway.\n */\n async create(input: CreateSecretInput): Promise<Envelope<SecretSummary>> {\n const { name, region, value, ...rest } = input;\n return request<SecretSummary>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region },\n // Live-verified against staging: the wire `value` MUST be base64 — a raw plaintext string\n // (matching the route's own \"the plaintext to seal\" OpenAPI description literally) is\n // rejected with K9O_INVALID_FIELD_VALUE \"Request body could not be parsed.\" `input.value`\n // stays the caller-facing plaintext; base64 is purely a wire-transport detail handled here.\n body: { ...rest, value: encodeBase64Utf8(value) },\n });\n },\n\n /** Mint a single-use reveal challenge — present it as `X-K9O-Reveal-Challenge` on the\n * immediately-following `reveal()` call. `POST .../<name>?op=reveal-challenge®ion=<region>`. */\n async revealChallenge(name: string, opts: { region: string }): Promise<Envelope<RevealChallenge>> {\n return request<RevealChallenge>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { op: \"reveal-challenge\", region: opts.region },\n });\n },\n\n /** Reveal a secret's plaintext value using a single-use challenge from `revealChallenge()`.\n * `GET .../<name>?region=<region>` with `X-K9O-Reveal-Challenge`. Decodes the wire's base64\n * `value` back to plaintext (live-verified: the reveal response's `value` is base64, symmetric\n * with `create`'s encode). */\n async reveal(name: string, opts: { region: string; challenge: string }): Promise<Envelope<RevealSecretResult>> {\n const envelope = await request<RevealSecretResult>(config, {\n method: \"GET\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region: opts.region },\n headers: { \"X-K9O-Reveal-Challenge\": opts.challenge },\n });\n return { ...envelope, data: { ...envelope.data, value: decodeBase64Utf8(envelope.data.value) } };\n },\n\n /** Update a secret's value (bumps version) — owner-only. `PUT .../<name>?region=<region>`.\n * 🔴 Returns only `{id, name, version}` live (see `UpdateSecretResult`), not the full summary\n * the OpenAPI schema documents — call `get()` after if you need the rest. */\n async update(name: string, input: UpdateSecretInput): Promise<Envelope<UpdateSecretResult>> {\n return request<UpdateSecretResult>(config, {\n method: \"PUT\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region: input.region },\n body: { value: encodeBase64Utf8(input.value), ...(input.metadata !== undefined ? { metadata: input.metadata } : {}) },\n });\n },\n\n /** Rotate a secret's wrapping key (re-wraps under a fresh `kid`; content unchanged) — owner-only.\n * `POST .../<name>?op=rotate®ion=<region>`. */\n async rotate(name: string, opts: { region: string }): Promise<Envelope<RotateSecretResult>> {\n return request<RotateSecretResult>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { op: \"rotate\", region: opts.region },\n });\n },\n\n /** Move (rename) a secret's hierarchical path — owner-only.\n * `POST .../<name>?op=move®ion=<region>`. */\n async move(name: string, opts: { region: string; newPath: string }): Promise<Envelope<MoveSecretResult>> {\n return request<MoveSecretResult>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { op: \"move\", region: opts.region },\n body: { newPath: opts.newPath },\n });\n },\n\n /** Retire a secret — owner-only. `DELETE .../<name>?region=<region>` → `204 No Content`. */\n async delete(name: string, opts: { region: string }): Promise<Envelope<void>> {\n return request<void>(config, {\n method: \"DELETE\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region: opts.region },\n });\n },\n },\n };\n}\n","/**\n * ZK encrypt/decrypt — the thin TS wrapper over the bound crypto core\n * (`kibo-kms-rest-ffi`, piece C) compiled to wasm. ZERO crypto here: every\n * wrap/unwrap, envelope framing, posture-floor enforcement, and KMS round-trip\n * lives in the audited Rust core. This layer only marshals the typed input\n * across the wasm boundary and maps the core's error string → `KError`.\n *\n * The core exposes `zk_encrypt`/`zk_decrypt` (async) taking/returning plain JS\n * objects via serde-wasm-bindgen, and rejecting with the `ZkFfiError` display\n * string (`\"<K9O_CODE>: <message>\"`, optionally with a posture-violation tail).\n *\n * Composes into `@k9o/core` as `client.kibo.encrypt`/`decrypt`; the F.5\n * `vault.zkDeks.{store,fetch}` surface (envelope persistence) is separate +\n * decoupled (the core never sees the store).\n */\n\nimport { KError } from \"@k9o/core/errors\";\nimport type {\n KiboEncryptInput,\n KiboEncryptOutput,\n KiboDecryptInput,\n KiboDecryptOutput,\n} from \"./crypto-types.ts\";\n\n/** The minimal surface the bound ZK-core wasm exposes. */\nexport interface ZkWasm {\n zk_encrypt(input: unknown): Promise<unknown>;\n zk_decrypt(input: unknown): Promise<unknown>;\n}\n\nlet cachedWasm: ZkWasm | undefined;\n\n/**\n * Inject a pre-loaded core wasm module. Hosts that bundle the wasm themselves\n * (browser/RN, or a test) call this; otherwise `encrypt`/`decrypt` lazy-load\n * the vendored per-provider artifact.\n */\nexport function setZkWasm(mod: ZkWasm): void {\n cachedWasm = mod;\n}\n\n/**\n * Lazily load the bound core wasm. ONE all-provider artifact is generated from\n * `kibo-kms-rest-ffi` (the core compiles every v2 KMS provider in; the\n * `provider` input field selects at call time), vendored under `wasm/` by the\n * publish pipeline and gitignored in-repo. The specifier is assembled at\n * runtime so the bundler/typechecker must not hard-resolve it — the contract\n * is the `ZkWasm` interface above.\n */\nasync function loadWasm(): Promise<ZkWasm> {\n if (cachedWasm) return cachedWasm;\n const pkg = [\"..\", \"wasm\", \"kibo_kms_rest_ffi.js\"].join(\"/\");\n try {\n cachedWasm = (await import(pkg)) as unknown as ZkWasm;\n } catch {\n // A package published without its native core (no bundle at publish time). Say so; never surface a bare\n // module-resolution error, and do not cache the failure.\n throw new KError({\n code: \"KIBO_WASM_ERROR\",\n message: \"the Kibo zero-knowledge core (wasm) is not bundled in this build of @k9o/kibo; encrypt and decrypt are unavailable\",\n context: {},\n recommendation: \"Install a release of @k9o/kibo that ships the core, or load it yourself.\",\n });\n }\n return cachedWasm;\n}\n\n/**\n * Encrypt `input.plaintext` via the customer KMS, client-side. Returns the\n * opaque ZKFE envelope — store it via `vault.zkDeks.store` (F.5) or keep it\n * yourself; it is meaningless to the platform.\n */\nexport async function encrypt(input: KiboEncryptInput): Promise<KiboEncryptOutput> {\n const wasm = await loadWasm();\n let out: unknown;\n try {\n out = await wasm.zk_encrypt(input);\n } catch (e) {\n throw mapZkError(e);\n }\n return { envelope: toBytes((out as { envelope?: unknown }).envelope) };\n}\n\n/**\n * Decrypt a ZKFE `input.envelope`. `strategy`, `attestation_tier`, and\n * `encryption_context` MUST match the values used at encrypt — they are AAD-bound\n * and never recovered from the envelope (anti-substitution discipline).\n */\nexport async function decrypt(input: KiboDecryptInput): Promise<KiboDecryptOutput> {\n const wasm = await loadWasm();\n let out: unknown;\n try {\n out = await wasm.zk_decrypt(input);\n } catch (e) {\n throw mapZkError(e);\n }\n return { plaintext: toBytes((out as { plaintext?: unknown }).plaintext) };\n}\n\n/** Normalise wasm byte output (serde-wasm-bindgen emits `Vec<u8>` as a number\n * array; some hosts return a `Uint8Array`) to `Uint8Array`. */\nfunction toBytes(v: unknown): Uint8Array {\n if (v instanceof Uint8Array) return v;\n if (Array.isArray(v)) return Uint8Array.from(v as number[]);\n throw new KError({\n code: \"KIBO_WASM_DECODE_ERROR\",\n message: \"ZK core returned a non-byte value where bytes were expected\",\n context: {},\n });\n}\n\n/**\n * Map the core's rejected error → `KError`. The wasm path flattens `ZkFfiError`\n * to its display string: `\"<CODE>: <message>\"` where `<CODE>` is a `K9O_*` wire\n * code (or `UPSTREAM_KMS_ERROR` for the customer's own KMS failures), optionally\n * followed by `\"(dimension: …, requested: …, declared: …)\"` for a posture-floor\n * violation. Codes are passed through (never hardcoded), per the KError discipline.\n */\nfunction mapZkError(e: unknown): KError {\n if (e instanceof KError) return e;\n const raw = e instanceof Error ? e.message : String(e);\n const head = /^([A-Z][A-Z0-9_]*):\\s*([\\s\\S]*)$/.exec(raw);\n const code = head?.[1] ?? \"KIBO_WASM_ERROR\";\n let message = head?.[2] ?? raw;\n\n let context: Record<string, unknown> = {};\n const pv = /\\(dimension:\\s*([^,]+),\\s*requested:\\s*([^,]+),\\s*declared:\\s*([^)]+)\\)\\s*$/.exec(message);\n if (pv) {\n context = { dimension: pv[1]!.trim(), requested: pv[2]!.trim(), declared: pv[3]!.trim() };\n message = message.slice(0, pv.index).trim();\n }\n return new KError({ code, message, context });\n}\n"],"mappings":"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","names":["KError"]}
|
|
1
|
+
{"version":3,"sources":["../src/client-factory.ts","../src/vault.ts","../src/crypto.ts"],"sourcesContent":["/**\n * `createKiboClient(config)` — per-instance Kibo (ZK vault) SDK factory.\n *\n * Composes into `@k9o/core` as `client.kibo` (re-export, available when a\n * project is bound — the same composition pattern as `@k9o/gate`). Reuses the\n * core HTTP layer (`@k9o/core/http`) so the bearer/header/retry contract is\n * identical to the rest of the SDK pool.\n */\n\nimport { type HttpClientConfig } from \"@k9o/core/http\";\nimport { makeVaultNamespace } from \"./vault.ts\";\n\n/** Shape of the per-instance client returned by `createKiboClient`. */\nexport type KiboClient = {\n /** Vault surface — `client.kibo.vault.zkDeks.{store,fetch}` (F.5). */\n vault: ReturnType<typeof makeVaultNamespace>;\n};\n\n/** Construct a Kibo client bound to the supplied (core) HTTP config. */\nexport function createKiboClient(config: HttpClientConfig): KiboClient {\n return {\n vault: makeVaultNamespace(config),\n };\n}\n","/**\n * `client.kibo.vault.*` — the Kibo vault surface.\n *\n * `vault.zkDeks.{store,fetch}` — member-plane,\n * project-scoped storage of the opaque customer-wrapped DEK envelope. Zero\n * crypto SDK-side: this is a plain authenticated REST round-trip carrying an\n * opaque blob (the wrapping happens in the ZK core, piece C). The encryption\n * surface (`kibo.encrypt`/`decrypt`) is provided by the ZK core binding when it is\n * bundled.\n */\n\nimport { request, type HttpClientConfig } from \"@k9o/core/http\";\nimport { projectPath } from \"@k9o/core/url-grammar\";\nimport { KError } from \"@k9o/core/errors\";\nimport type { Envelope } from \"@k9o/core/envelope\";\nimport type {\n StoreZkDekInput, ZkDekHandle, ZkDekRecord,\n CreateSecretInput, SecretSummary, ListSecretsInput, ListSecretsResult,\n RevealChallenge, RevealSecretResult, UpdateSecretInput, UpdateSecretResult,\n RotateSecretResult, MoveSecretResult,\n} from \"./types.ts\";\n\n/** UTF-8-safe base64 encode, usable across browser/Node/Bun (unlike `Buffer`) — the vault-secrets wire\n * `value` field is base64 (live-verified; the OpenAPI \"plaintext to seal\" description does not mean\n * raw-on-the-wire). */\nfunction encodeBase64Utf8(plaintext: string): string {\n return btoa(unescape(encodeURIComponent(plaintext)));\n}\n\n/** Inverse of {@link encodeBase64Utf8} — decodes a revealed wire `value` back to plaintext. */\nfunction decodeBase64Utf8(b64: string): string {\n return decodeURIComponent(escape(atob(b64)));\n}\n\n/** Both `vault.zkDeks` and `vault.secrets` require a project context (member-plane, /). */\nfunction requireProjectSlug(config: HttpClientConfig): string {\n if (config.projectSlug === undefined || config.projectSlug.length === 0) {\n throw new KError({\n code: \"K9O_MISSING_PROJECT_CONTEXT\",\n message: \"client.kibo.vault requires a bound projectSlug (member-plane resource)\",\n context: {},\n recommendation: \"Pass projectSlug when constructing the client.\",\n });\n }\n return config.projectSlug;\n}\n\nexport function makeVaultNamespace(config: HttpClientConfig) {\n return {\n /** ZK-DEK opaque store/fetch (F.5). */\n zkDeks: {\n /**\n * Store an opaque customer-wrapped DEK envelope.\n * `POST /@<projectSlug>/vault/zk-deks` → the `zkdek_<base62>` handle.\n * The SDK ships the envelope verbatim; it is never parsed here.\n */\n async store(input: StoreZkDekInput): Promise<Envelope<ZkDekHandle>> {\n return request<ZkDekHandle>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), \"vault/zk-deks\"),\n body: input,\n });\n },\n\n /**\n * Fetch a stored `WrappedDek` by its handle.\n * `GET /@<projectSlug>/vault/zk-deks/<id>` → the opaque envelope + the\n * non-secret `provider`/`keyVersionRef` the caller needs to unwrap it.\n */\n async fetch(id: string): Promise<Envelope<ZkDekRecord>> {\n return request<ZkDekRecord>(config, {\n method: \"GET\",\n path: projectPath(requireProjectSlug(config), `vault/zk-deks/${encodeURIComponent(id)}`),\n });\n },\n },\n\n /**\n * typed secrets surface (`services/gateway/src/routes/vault-secrets.ts`) — the full family:\n * list/get/create/revealChallenge/reveal/update/rotate/move/delete. Every operation on a NAMED\n * secret (everything except `list`) requires `region` — live-verified against staging, not\n * documented in the route's OpenAPI schema. `list` is the one exception (verified it does NOT\n * need `region`).\n *\n * Response shapes for `update`/`rotate`/`move` are typed to what the gateway ACTUALLY returns\n * (live-verified), not the OpenAPI's documented (and inaccurate for these three) `SecretSummary` —\n * see the 🔴 notes on `UpdateSecretResult`/`RotateSecretResult`/`MoveSecretResult` in `types.ts`.\n */\n secrets: {\n /** List secrets (metadata only — never lists ciphertext). `GET /@<projectSlug>/vault/secrets`.\n * The one secrets operation that does NOT need `region` (live-verified). */\n async list(input?: ListSecretsInput): Promise<Envelope<ListSecretsResult>> {\n return request<ListSecretsResult>(config, {\n method: \"GET\",\n path: projectPath(requireProjectSlug(config), \"vault/secrets\"),\n query: { prefix: input?.prefix, cursor: input?.cursor, limit: input?.limit },\n });\n },\n\n /**\n * Read a secret's metadata (never its value — use `revealChallenge`+`reveal` for that).\n * `GET /@<projectSlug>/vault/secrets/<name>?region=<region>`.\n *\n * 🔴 KNOWN LIVE BUG (staging, 2026-09-05): this call currently fails with\n * `K9O_REVEAL_CHALLENGE_REQUIRED` on every attempt — the gateway/kibo does not honor the\n * documented \"no `X-K9O-Reveal-Challenge` header ⇒ return metadata\" behavior; it demands a\n * challenge unconditionally. Implemented per the documented contract (this is a backend defect,\n * not a client workaround); `list({prefix: exactName})` is the current working alternative for a\n * single secret's metadata until the gateway/kibo side is fixed.\n */\n async get(name: string, opts: { region: string }): Promise<Envelope<SecretSummary>> {\n return request<SecretSummary>(config, {\n method: \"GET\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region: opts.region },\n });\n },\n\n /**\n * Create a secret under the caller's own auth.\n * `POST /@<projectSlug>/vault/secrets/<name>?region=<region>` → the `sec_<base62>` summary.\n *\n * 🔴 `region` is REQUIRED on the query string, live-verified against staging — the vault-forward\n * proxy fail-closes with `K9O_INVALID_FIELD_VALUE` (\"this request requires an authoritative\n * region\") without it. Distinct from any `region` carried inside `input.value`'s envelope (e.g.\n * the BYOS aws_sigv4 STS-signing region): this one scopes the vault WRITE itself, mirroring the\n * gateway's `gatewayRegion()` residency guard (region-guard.ts) that closed a 2026-08 wrong-region\n * incident for the audit path — the request-shape unit tests never caught this because they don't\n * exercise a real gateway.\n */\n async create(input: CreateSecretInput): Promise<Envelope<SecretSummary>> {\n const { name, region, value, ...rest } = input;\n return request<SecretSummary>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region },\n // Live-verified against staging: the wire `value` MUST be base64 — a raw plaintext string\n // (matching the route's own \"the plaintext to seal\" OpenAPI description literally) is\n // rejected with K9O_INVALID_FIELD_VALUE \"Request body could not be parsed.\" `input.value`\n // stays the caller-facing plaintext; base64 is purely a wire-transport detail handled here.\n body: { ...rest, value: encodeBase64Utf8(value) },\n });\n },\n\n /** Mint a single-use reveal challenge — present it as `X-K9O-Reveal-Challenge` on the\n * immediately-following `reveal()` call. `POST .../<name>?op=reveal-challenge®ion=<region>`. */\n async revealChallenge(name: string, opts: { region: string }): Promise<Envelope<RevealChallenge>> {\n return request<RevealChallenge>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { op: \"reveal-challenge\", region: opts.region },\n });\n },\n\n /** Reveal a secret's plaintext value using a single-use challenge from `revealChallenge()`.\n * `GET .../<name>?region=<region>` with `X-K9O-Reveal-Challenge`. Decodes the wire's base64\n * `value` back to plaintext (live-verified: the reveal response's `value` is base64, symmetric\n * with `create`'s encode). */\n async reveal(name: string, opts: { region: string; challenge: string }): Promise<Envelope<RevealSecretResult>> {\n const envelope = await request<RevealSecretResult>(config, {\n method: \"GET\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region: opts.region },\n headers: { \"X-K9O-Reveal-Challenge\": opts.challenge },\n });\n return { ...envelope, data: { ...envelope.data, value: decodeBase64Utf8(envelope.data.value) } };\n },\n\n /** Update a secret's value (bumps version) — owner-only. `PUT .../<name>?region=<region>`.\n * 🔴 Returns only `{id, name, version}` live (see `UpdateSecretResult`), not the full summary\n * the OpenAPI schema documents — call `get()` after if you need the rest. */\n async update(name: string, input: UpdateSecretInput): Promise<Envelope<UpdateSecretResult>> {\n return request<UpdateSecretResult>(config, {\n method: \"PUT\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region: input.region },\n body: { value: encodeBase64Utf8(input.value), ...(input.metadata !== undefined ? { metadata: input.metadata } : {}) },\n });\n },\n\n /** Rotate a secret's wrapping key (re-wraps under a fresh `kid`; content unchanged) — owner-only.\n * `POST .../<name>?op=rotate®ion=<region>`. */\n async rotate(name: string, opts: { region: string }): Promise<Envelope<RotateSecretResult>> {\n return request<RotateSecretResult>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { op: \"rotate\", region: opts.region },\n });\n },\n\n /** Move (rename) a secret's hierarchical path — owner-only.\n * `POST .../<name>?op=move®ion=<region>`. */\n async move(name: string, opts: { region: string; newPath: string }): Promise<Envelope<MoveSecretResult>> {\n return request<MoveSecretResult>(config, {\n method: \"POST\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { op: \"move\", region: opts.region },\n body: { newPath: opts.newPath },\n });\n },\n\n /** Retire a secret — owner-only. `DELETE .../<name>?region=<region>` → `204 No Content`. */\n async delete(name: string, opts: { region: string }): Promise<Envelope<void>> {\n return request<void>(config, {\n method: \"DELETE\",\n path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),\n query: { region: opts.region },\n });\n },\n },\n };\n}\n","/**\n * ZK encrypt/decrypt — the thin TS wrapper over the bound crypto core\n * (`kibo-kms-rest-ffi`, piece C) compiled to wasm. ZERO crypto here: every\n * wrap/unwrap, envelope framing, posture-floor enforcement, and KMS round-trip\n * lives in the audited Rust core. This layer only marshals the typed input\n * across the wasm boundary and maps the core's error string → `KError`.\n *\n * The core exposes `zk_encrypt`/`zk_decrypt` (async) taking/returning plain JS\n * objects via serde-wasm-bindgen, and rejecting with the `ZkFfiError` display\n * string (`\"<K9O_CODE>: <message>\"`, optionally with a posture-violation tail).\n *\n * Composes into `@k9o/core` as `client.kibo.encrypt`/`decrypt`; the F.5\n * `vault.zkDeks.{store,fetch}` surface (envelope persistence) is separate +\n * decoupled (the core never sees the store).\n */\n\nimport { KError } from \"@k9o/core/errors\";\nimport type {\n KiboEncryptInput,\n KiboEncryptOutput,\n KiboDecryptInput,\n KiboDecryptOutput,\n} from \"./crypto-types.ts\";\n\n/** The minimal surface the bound ZK-core wasm exposes. */\nexport interface ZkWasm {\n zk_encrypt(input: unknown): Promise<unknown>;\n zk_decrypt(input: unknown): Promise<unknown>;\n}\n\nlet cachedWasm: ZkWasm | undefined;\n\n/**\n * Inject a pre-loaded core wasm module. Hosts that bundle the wasm themselves\n * (browser/RN, or a test) call this; otherwise `encrypt`/`decrypt` lazy-load\n * the vendored per-provider artifact.\n */\nexport function setZkWasm(mod: ZkWasm): void {\n cachedWasm = mod;\n}\n\n/**\n * Lazily load the bound core wasm. ONE all-provider artifact is generated from\n * `kibo-kms-rest-ffi` (the core compiles every v2 KMS provider in; the\n * `provider` input field selects at call time), vendored under `wasm/` by the\n * publish pipeline and gitignored in-repo. The specifier is assembled at\n * runtime so the bundler/typechecker must not hard-resolve it — the contract\n * is the `ZkWasm` interface above.\n */\nasync function loadWasm(): Promise<ZkWasm> {\n if (cachedWasm) return cachedWasm;\n const pkg = [\"..\", \"wasm\", \"kibo_kms_rest_ffi.js\"].join(\"/\");\n try {\n cachedWasm = (await import(pkg)) as unknown as ZkWasm;\n } catch {\n // A package published without its native core (no bundle at publish time). Say so; never surface a bare\n // module-resolution error, and do not cache the failure.\n throw new KError({\n code: \"KIBO_WASM_ERROR\",\n message: \"the Kibo zero-knowledge core (wasm) is not bundled in this build of @k9o/kibo; encrypt and decrypt are unavailable\",\n context: {},\n recommendation: \"Install a release of @k9o/kibo that ships the core, or load it yourself.\",\n });\n }\n return cachedWasm;\n}\n\n/**\n * Encrypt `input.plaintext` via the customer KMS, client-side. Returns the\n * opaque ZKFE envelope — store it via `vault.zkDeks.store` (F.5) or keep it\n * yourself; it is meaningless to the platform.\n */\nexport async function encrypt(input: KiboEncryptInput): Promise<KiboEncryptOutput> {\n const wasm = await loadWasm();\n let out: unknown;\n try {\n out = await wasm.zk_encrypt(input);\n } catch (e) {\n throw mapZkError(e);\n }\n return { envelope: toBytes((out as { envelope?: unknown }).envelope) };\n}\n\n/**\n * Decrypt a ZKFE `input.envelope`. `strategy`, `attestation_tier`, and\n * `encryption_context` MUST match the values used at encrypt — they are AAD-bound\n * and never recovered from the envelope (anti-substitution discipline).\n */\nexport async function decrypt(input: KiboDecryptInput): Promise<KiboDecryptOutput> {\n const wasm = await loadWasm();\n let out: unknown;\n try {\n out = await wasm.zk_decrypt(input);\n } catch (e) {\n throw mapZkError(e);\n }\n return { plaintext: toBytes((out as { plaintext?: unknown }).plaintext) };\n}\n\n/** Normalise wasm byte output (serde-wasm-bindgen emits `Vec<u8>` as a number\n * array; some hosts return a `Uint8Array`) to `Uint8Array`. */\nfunction toBytes(v: unknown): Uint8Array {\n if (v instanceof Uint8Array) return v;\n if (Array.isArray(v)) return Uint8Array.from(v as number[]);\n throw new KError({\n code: \"KIBO_WASM_DECODE_ERROR\",\n message: \"ZK core returned a non-byte value where bytes were expected\",\n context: {},\n });\n}\n\n/**\n * Map the core's rejected error → `KError`. The wasm path flattens `ZkFfiError`\n * to its display string: `\"<CODE>: <message>\"` where `<CODE>` is a `K9O_*` wire\n * code (or `UPSTREAM_KMS_ERROR` for the customer's own KMS failures), optionally\n * followed by `\"(dimension: …, requested: …, declared: …)\"` for a posture-floor\n * violation. Codes are passed through (never hardcoded), per the KError discipline.\n */\nfunction mapZkError(e: unknown): KError {\n if (e instanceof KError) return e;\n const raw = e instanceof Error ? e.message : String(e);\n const head = /^([A-Z][A-Z0-9_]*):\\s*([\\s\\S]*)$/.exec(raw);\n const code = head?.[1] ?? \"KIBO_WASM_ERROR\";\n let message = head?.[2] ?? raw;\n\n let context: Record<string, unknown> = {};\n const pv = /\\(dimension:\\s*([^,]+),\\s*requested:\\s*([^,]+),\\s*declared:\\s*([^)]+)\\)\\s*$/.exec(message);\n if (pv) {\n context = { dimension: pv[1]!.trim(), requested: pv[2]!.trim(), declared: pv[3]!.trim() };\n message = message.slice(0, pv.index).trim();\n }\n return new KError({ code, message, context });\n}\n"],"mappings":"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","names":["KError"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@k9o/kibo` — Zero-Knowledge BYOK/HYOK vault SDK types.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Surface: the ZK-DEK store/fetch. The wrapped
|
|
5
5
|
* envelope is produced client-side by the audited ZK core (the
|
|
6
6
|
* `kibo-kms-rest` binding); this SDK ships it **verbatim and opaque** and never
|
|
7
7
|
* reads its interior — the strict-ZK invariant + envelope framing live in the
|
|
@@ -37,7 +37,7 @@ type ZkDekRecord = {
|
|
|
37
37
|
keyVersionRef: string;
|
|
38
38
|
createdAt: string;
|
|
39
39
|
};
|
|
40
|
-
/**
|
|
40
|
+
/** secret visibility — write ops (create/update) stay owner-only regardless of this tag. */
|
|
41
41
|
type SecretVisibility = {
|
|
42
42
|
kind: "private";
|
|
43
43
|
} | {
|
|
@@ -45,7 +45,7 @@ type SecretVisibility = {
|
|
|
45
45
|
principals?: string[];
|
|
46
46
|
roles?: string[];
|
|
47
47
|
};
|
|
48
|
-
/** Input to create a vault secret
|
|
48
|
+
/** Input to create a vault secret — `POST /@<projectSlug>/vault/secrets/<name>?region=<region>`. */
|
|
49
49
|
type CreateSecretInput = {
|
|
50
50
|
/** Hierarchical path the secret is addressed under, e.g. `byos/s3-prod` (also the `:name` URL segment). */
|
|
51
51
|
name: string;
|
|
@@ -91,7 +91,7 @@ type ListSecretsResult = {
|
|
|
91
91
|
secrets: readonly SecretSummary[];
|
|
92
92
|
nextCursor: string | null;
|
|
93
93
|
};
|
|
94
|
-
/**
|
|
94
|
+
/** releasing-node observed attestation tier (K4) — present on every reveal. */
|
|
95
95
|
type AttestationTier = {
|
|
96
96
|
/** `attestation:destination:region` trust-tier URN. */
|
|
97
97
|
urn: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k9o/kibo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Kibo — Zero-Knowledge BYOK/HYOK vault SDK (client-side; composes into @k9o/core as client.kibo). ZK-DEK store/fetch (F.5) + encrypt/decrypt (thin wrappers over the audited bound core compiled to wasm).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"directory": "packages/kibo"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@k9o/core": "1.
|
|
39
|
+
"@k9o/core": "1.1.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"bun-types": "^1.1.0",
|
package/src/client-factory.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { makeVaultNamespace } from "./vault.ts";
|
|
|
12
12
|
|
|
13
13
|
/** Shape of the per-instance client returned by `createKiboClient`. */
|
|
14
14
|
export type KiboClient = {
|
|
15
|
-
/** Vault surface — `client.kibo.vault.zkDeks.{store,fetch}` (
|
|
15
|
+
/** Vault surface — `client.kibo.vault.zkDeks.{store,fetch}` (F.5). */
|
|
16
16
|
vault: ReturnType<typeof makeVaultNamespace>;
|
|
17
17
|
};
|
|
18
18
|
|
package/src/crypto-types.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface KiboAllowedOracleModes {
|
|
|
41
41
|
direct: boolean;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** Project-declared crypto-posture floor
|
|
44
|
+
/** Project-declared crypto-posture floor. Omit fields for the
|
|
45
45
|
* most-permissive default (`platform_managed` floor + both oracle modes). */
|
|
46
46
|
export interface KiboPosturePolicy {
|
|
47
47
|
min_custody_posture?: KiboMinPosture;
|
package/src/index.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* `@k9o/kibo` — Zero-Knowledge BYOK/HYOK vault SDK (client-side).
|
|
3
3
|
*
|
|
4
4
|
* Composes into `@k9o/core` as `client.kibo`. Two surfaces:
|
|
5
|
-
* - F.5 ZK-DEK store/fetch
|
|
5
|
+
* - F.5 ZK-DEK store/fetch — `vault.zkDeks.{store,fetch}`;
|
|
6
6
|
* decoupled, gateway-only opaque-blob persistence.
|
|
7
|
-
* - ZK `encrypt`/`decrypt` (
|
|
7
|
+
* - ZK `encrypt`/`decrypt` (/) — thin wrappers over the
|
|
8
8
|
* audited core (piece C, `kibo-kms-rest-ffi` compiled to wasm).
|
|
9
9
|
*
|
|
10
10
|
* Zero crypto in this package: every wrap/unwrap, envelope framing, and
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@k9o/kibo` — Zero-Knowledge BYOK/HYOK vault SDK types.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Surface: the ZK-DEK store/fetch. The wrapped
|
|
5
5
|
* envelope is produced client-side by the audited ZK core (the
|
|
6
6
|
* `kibo-kms-rest` binding); this SDK ships it **verbatim and opaque** and never
|
|
7
7
|
* reads its interior — the strict-ZK invariant + envelope framing live in the
|
|
@@ -42,12 +42,12 @@ export type ZkDekRecord = {
|
|
|
42
42
|
createdAt: string;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
/**
|
|
45
|
+
/** secret visibility — write ops (create/update) stay owner-only regardless of this tag. */
|
|
46
46
|
export type SecretVisibility =
|
|
47
47
|
| { kind: "private" }
|
|
48
48
|
| { kind: "shared"; principals?: string[]; roles?: string[] };
|
|
49
49
|
|
|
50
|
-
/** Input to create a vault secret
|
|
50
|
+
/** Input to create a vault secret — `POST /@<projectSlug>/vault/secrets/<name>?region=<region>`. */
|
|
51
51
|
export type CreateSecretInput = {
|
|
52
52
|
/** Hierarchical path the secret is addressed under, e.g. `byos/s3-prod` (also the `:name` URL segment). */
|
|
53
53
|
name: string;
|
|
@@ -97,7 +97,7 @@ export type ListSecretsResult = {
|
|
|
97
97
|
nextCursor: string | null;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
/**
|
|
100
|
+
/** releasing-node observed attestation tier (K4) — present on every reveal. */
|
|
101
101
|
export type AttestationTier = {
|
|
102
102
|
/** `attestation:destination:region` trust-tier URN. */
|
|
103
103
|
urn: string;
|
package/src/vault.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `client.kibo.vault.*` — the Kibo vault surface.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* `vault.zkDeks.{store,fetch}` — member-plane,
|
|
5
5
|
* project-scoped storage of the opaque customer-wrapped DEK envelope. Zero
|
|
6
6
|
* crypto SDK-side: this is a plain authenticated REST round-trip carrying an
|
|
7
7
|
* opaque blob (the wrapping happens in the ZK core, piece C). The encryption
|
|
8
|
-
* surface (`kibo.encrypt`/`decrypt`)
|
|
9
|
-
*
|
|
8
|
+
* surface (`kibo.encrypt`/`decrypt`) is provided by the ZK core binding when it is
|
|
9
|
+
* bundled.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { request, type HttpClientConfig } from "@k9o/core/http";
|
|
@@ -32,7 +32,7 @@ function decodeBase64Utf8(b64: string): string {
|
|
|
32
32
|
return decodeURIComponent(escape(atob(b64)));
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
/** Both `vault.zkDeks` and `vault.secrets` require a project context (member-plane,
|
|
35
|
+
/** Both `vault.zkDeks` and `vault.secrets` require a project context (member-plane, /). */
|
|
36
36
|
function requireProjectSlug(config: HttpClientConfig): string {
|
|
37
37
|
if (config.projectSlug === undefined || config.projectSlug.length === 0) {
|
|
38
38
|
throw new KError({
|
|
@@ -47,7 +47,7 @@ function requireProjectSlug(config: HttpClientConfig): string {
|
|
|
47
47
|
|
|
48
48
|
export function makeVaultNamespace(config: HttpClientConfig) {
|
|
49
49
|
return {
|
|
50
|
-
/** ZK-DEK opaque store/fetch (
|
|
50
|
+
/** ZK-DEK opaque store/fetch (F.5). */
|
|
51
51
|
zkDeks: {
|
|
52
52
|
/**
|
|
53
53
|
* Store an opaque customer-wrapped DEK envelope.
|
|
@@ -76,7 +76,7 @@ export function makeVaultNamespace(config: HttpClientConfig) {
|
|
|
76
76
|
},
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
79
|
+
* typed secrets surface (`services/gateway/src/routes/vault-secrets.ts`) — the full family:
|
|
80
80
|
* list/get/create/revealChallenge/reveal/update/rotate/move/delete. Every operation on a NAMED
|
|
81
81
|
* secret (everything except `list`) requires `region` — live-verified against staging, not
|
|
82
82
|
* documented in the route's OpenAPI schema. `list` is the one exception (verified it does NOT
|