@k9o/kibo 1.2.0 → 1.3.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/CHANGELOG.md +23 -0
- package/README.md +53 -33
- package/dist/index.d.ts +54 -68
- package/dist/index.js +133 -75
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +18 -31
- package/package.json +3 -3
- package/src/client-factory.ts +1 -1
- package/src/crypto-types.ts +3 -3
- package/src/crypto.ts +11 -24
- package/src/index.ts +8 -13
- package/src/types.ts +18 -31
- package/src/vault.ts +71 -76
- package/src/wasm-loader.ts +106 -0
- package/wasm/kibo_kms_rest_ffi.js +9 -9
- package/wasm/kibo_kms_rest_ffi_bg.wasm +0 -0
- package/wasm/kibo_kms_rest_ffi_bg.wasm.d.ts +3 -3
package/dist/index.js
CHANGED
|
@@ -22,9 +22,15 @@ function requireProjectSlug(config) {
|
|
|
22
22
|
}
|
|
23
23
|
return config.projectSlug;
|
|
24
24
|
}
|
|
25
|
+
var MAX_GET_PAGES = 20;
|
|
25
26
|
function makeVaultNamespace(config) {
|
|
27
|
+
const listSecrets = async (input) => request(config, {
|
|
28
|
+
method: "GET",
|
|
29
|
+
path: projectPath(requireProjectSlug(config), "vault/secrets"),
|
|
30
|
+
query: { prefix: input?.prefix, cursor: input?.cursor, limit: input?.limit }
|
|
31
|
+
});
|
|
26
32
|
return {
|
|
27
|
-
/**
|
|
33
|
+
/** Opaque store/fetch of a customer-wrapped DEK envelope. */
|
|
28
34
|
zkDeks: {
|
|
29
35
|
/**
|
|
30
36
|
* Store an opaque customer-wrapped DEK envelope.
|
|
@@ -51,55 +57,47 @@ function makeVaultNamespace(config) {
|
|
|
51
57
|
}
|
|
52
58
|
},
|
|
53
59
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
60
|
+
* Typed secrets surface (`/@<projectSlug>/vault/secrets`): list / get / create / revealChallenge / reveal /
|
|
61
|
+
* update / rotate / move / delete.
|
|
62
|
+
*
|
|
63
|
+
* `region`: the gateway sets the residency region of every vault call itself and discards a caller-supplied
|
|
64
|
+
* `region` query parameter, so it is optional on every method. When given, it is still sent (older gateways
|
|
65
|
+
* required it).
|
|
59
66
|
*
|
|
60
|
-
* Response shapes for `update`/`rotate`/`move` are typed to what the gateway
|
|
61
|
-
*
|
|
62
|
-
* see the 🔴 notes on `UpdateSecretResult`/`RotateSecretResult`/`MoveSecretResult` in `types.ts`.
|
|
67
|
+
* Response shapes for `update`/`rotate`/`move` are typed to what the gateway returns, not the OpenAPI's
|
|
68
|
+
* `SecretSummary` (see `UpdateSecretResult`/`RotateSecretResult`/`MoveSecretResult` in `types.ts`).
|
|
63
69
|
*/
|
|
64
70
|
secrets: {
|
|
65
|
-
/** List secrets (metadata only
|
|
66
|
-
|
|
67
|
-
async list(input) {
|
|
68
|
-
return request(config, {
|
|
69
|
-
method: "GET",
|
|
70
|
-
path: projectPath(requireProjectSlug(config), "vault/secrets"),
|
|
71
|
-
query: { prefix: input?.prefix, cursor: input?.cursor, limit: input?.limit }
|
|
72
|
-
});
|
|
73
|
-
},
|
|
71
|
+
/** List secrets (metadata only, never ciphertext). `GET /@<projectSlug>/vault/secrets`. */
|
|
72
|
+
list: listSecrets,
|
|
74
73
|
/**
|
|
75
|
-
* Read
|
|
76
|
-
* `GET /@<projectSlug>/vault/secrets/<name>?region=<region>`.
|
|
74
|
+
* Read one secret's metadata (never its value; use `revealChallenge` + `reveal` for that).
|
|
77
75
|
*
|
|
78
|
-
*
|
|
79
|
-
* `
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* single secret's metadata until the gateway/kibo side is fixed.
|
|
76
|
+
* Vault answers a `GET /@<projectSlug>/vault/secrets/<name>` only as a reveal: with no
|
|
77
|
+
* `X-K9O-Reveal-Challenge` header it refuses with 401 `K9O_REVEAL_CHALLENGE_REQUIRED`, whatever the caller's
|
|
78
|
+
* rights. There is no plain metadata read on that route, so `get` reads the metadata the route that exists
|
|
79
|
+
* for it returns: it lists by the exact name as prefix and picks the exact match. It throws `K9O_NOT_FOUND`
|
|
80
|
+
* (status 404) when no secret has that name.
|
|
84
81
|
*/
|
|
85
|
-
async get(name,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
async get(name, _opts) {
|
|
83
|
+
let cursor;
|
|
84
|
+
for (let page = 0; page < MAX_GET_PAGES; page++) {
|
|
85
|
+
const env = await listSecrets({ prefix: name, ...cursor !== void 0 ? { cursor } : {}, limit: 100 });
|
|
86
|
+
const hit = env.data.secrets.find((s) => s.name === name);
|
|
87
|
+
if (hit !== void 0) return { ...env, data: hit };
|
|
88
|
+
if (env.data.nextCursor === null || env.data.nextCursor === void 0) break;
|
|
89
|
+
cursor = env.data.nextCursor;
|
|
90
|
+
}
|
|
91
|
+
throw new KError({
|
|
92
|
+
code: "K9O_NOT_FOUND",
|
|
93
|
+
message: `No secret named '${name}' in this project.`,
|
|
94
|
+
context: { name },
|
|
95
|
+
status: 404
|
|
90
96
|
});
|
|
91
97
|
},
|
|
92
98
|
/**
|
|
93
99
|
* Create a secret under the caller's own auth.
|
|
94
|
-
* `POST /@<projectSlug>/vault/secrets/<name
|
|
95
|
-
*
|
|
96
|
-
* 🔴 `region` is REQUIRED on the query string, live-verified against staging — the vault-forward
|
|
97
|
-
* proxy fail-closes with `K9O_INVALID_FIELD_VALUE` ("this request requires an authoritative
|
|
98
|
-
* region") without it. Distinct from any `region` carried inside `input.value`'s envelope (e.g.
|
|
99
|
-
* the BYOS aws_sigv4 STS-signing region): this one scopes the vault WRITE itself, mirroring the
|
|
100
|
-
* gateway's `gatewayRegion()` residency guard (region-guard.ts) that closed a 2026-08 wrong-region
|
|
101
|
-
* incident for the audit path — the request-shape unit tests never caught this because they don't
|
|
102
|
-
* exercise a real gateway.
|
|
100
|
+
* `POST /@<projectSlug>/vault/secrets/<name>` -> the `sec_<base62>` summary.
|
|
103
101
|
*/
|
|
104
102
|
async create(input) {
|
|
105
103
|
const { name, region, value, ...rest } = input;
|
|
@@ -107,26 +105,24 @@ function makeVaultNamespace(config) {
|
|
|
107
105
|
method: "POST",
|
|
108
106
|
path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),
|
|
109
107
|
query: { region },
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// rejected with K9O_INVALID_FIELD_VALUE "Request body could not be parsed." `input.value`
|
|
113
|
-
// stays the caller-facing plaintext; base64 is purely a wire-transport detail handled here.
|
|
108
|
+
// The wire `value` is base64 (a raw string is rejected as unparseable). `input.value` stays the
|
|
109
|
+
// caller-facing plaintext; base64 is purely a wire detail handled here.
|
|
114
110
|
body: { ...rest, value: encodeBase64Utf8(value) }
|
|
115
111
|
});
|
|
116
112
|
},
|
|
117
|
-
/** Mint a single-use reveal challenge
|
|
118
|
-
*
|
|
113
|
+
/** Mint a single-use reveal challenge; pass it to the immediately-following `reveal()`.
|
|
114
|
+
* `POST .../<name>?op=reveal-challenge`. The optional `reason` is accepted by the gateway and not forwarded. */
|
|
119
115
|
async revealChallenge(name, opts) {
|
|
120
116
|
return request(config, {
|
|
121
117
|
method: "POST",
|
|
122
118
|
path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),
|
|
123
|
-
query: { op: "reveal-challenge", region: opts
|
|
119
|
+
query: { op: "reveal-challenge", region: opts?.region },
|
|
120
|
+
...opts?.reason !== void 0 ? { body: { reason: opts.reason } } : {}
|
|
124
121
|
});
|
|
125
122
|
},
|
|
126
123
|
/** Reveal a secret's plaintext value using a single-use challenge from `revealChallenge()`.
|
|
127
|
-
* `GET .../<name
|
|
128
|
-
*
|
|
129
|
-
* with `create`'s encode). */
|
|
124
|
+
* `GET .../<name>` with `X-K9O-Reveal-Challenge`. Decodes the wire's base64 `value` back to plaintext
|
|
125
|
+
* (symmetric with `create`'s encode). */
|
|
130
126
|
async reveal(name, opts) {
|
|
131
127
|
const envelope = await request(config, {
|
|
132
128
|
method: "GET",
|
|
@@ -136,9 +132,8 @@ function makeVaultNamespace(config) {
|
|
|
136
132
|
});
|
|
137
133
|
return { ...envelope, data: { ...envelope.data, value: decodeBase64Utf8(envelope.data.value) } };
|
|
138
134
|
},
|
|
139
|
-
/** Update a secret's value (bumps version)
|
|
140
|
-
*
|
|
141
|
-
* the OpenAPI schema documents — call `get()` after if you need the rest. */
|
|
135
|
+
/** Update a secret's value (bumps version), owner-only. `PUT .../<name>`.
|
|
136
|
+
* Returns only `{id, name, version}` (see `UpdateSecretResult`); call `get()` after for the rest. */
|
|
142
137
|
async update(name, input) {
|
|
143
138
|
return request(config, {
|
|
144
139
|
method: "PUT",
|
|
@@ -147,17 +142,16 @@ function makeVaultNamespace(config) {
|
|
|
147
142
|
body: { value: encodeBase64Utf8(input.value), ...input.metadata !== void 0 ? { metadata: input.metadata } : {} }
|
|
148
143
|
});
|
|
149
144
|
},
|
|
150
|
-
/** Rotate a secret's wrapping key (re-wraps under a fresh `kid`; content unchanged)
|
|
151
|
-
* `POST .../<name>?op=rotate
|
|
145
|
+
/** Rotate a secret's wrapping key (re-wraps under a fresh `kid`; content unchanged), owner-only.
|
|
146
|
+
* `POST .../<name>?op=rotate`. */
|
|
152
147
|
async rotate(name, opts) {
|
|
153
148
|
return request(config, {
|
|
154
149
|
method: "POST",
|
|
155
150
|
path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),
|
|
156
|
-
query: { op: "rotate", region: opts
|
|
151
|
+
query: { op: "rotate", region: opts?.region }
|
|
157
152
|
});
|
|
158
153
|
},
|
|
159
|
-
/** Move (rename) a secret's hierarchical path
|
|
160
|
-
* `POST .../<name>?op=move®ion=<region>`. */
|
|
154
|
+
/** Move (rename) a secret's hierarchical path, owner-only. `POST .../<name>?op=move`. */
|
|
161
155
|
async move(name, opts) {
|
|
162
156
|
return request(config, {
|
|
163
157
|
method: "POST",
|
|
@@ -166,12 +160,12 @@ function makeVaultNamespace(config) {
|
|
|
166
160
|
body: { newPath: opts.newPath }
|
|
167
161
|
});
|
|
168
162
|
},
|
|
169
|
-
/** Retire a secret
|
|
163
|
+
/** Retire a secret, owner-only. `DELETE .../<name>`. */
|
|
170
164
|
async delete(name, opts) {
|
|
171
165
|
return request(config, {
|
|
172
166
|
method: "DELETE",
|
|
173
167
|
path: projectPath(requireProjectSlug(config), `vault/secrets/${encodeURIComponent(name)}`),
|
|
174
|
-
query: { region: opts
|
|
168
|
+
query: { region: opts?.region }
|
|
175
169
|
});
|
|
176
170
|
}
|
|
177
171
|
}
|
|
@@ -186,24 +180,89 @@ function createKiboClient(config) {
|
|
|
186
180
|
}
|
|
187
181
|
|
|
188
182
|
// src/crypto.ts
|
|
183
|
+
import { KError as KError3 } from "@k9o/core/errors";
|
|
184
|
+
|
|
185
|
+
// src/wasm-loader.ts
|
|
189
186
|
import { KError as KError2 } from "@k9o/core/errors";
|
|
187
|
+
var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["ENOENT", "MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND"]);
|
|
188
|
+
function describe(e) {
|
|
189
|
+
return e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
|
190
|
+
}
|
|
191
|
+
function isMissing(e, path) {
|
|
192
|
+
if (typeof e !== "object" || e === null) return false;
|
|
193
|
+
const err = e;
|
|
194
|
+
if (typeof err.code !== "string" || !NOT_FOUND_CODES.has(err.code)) return false;
|
|
195
|
+
if (typeof err.path === "string") return err.path === path;
|
|
196
|
+
return typeof err.message === "string" && err.message.includes(path);
|
|
197
|
+
}
|
|
198
|
+
function withCause(err, cause) {
|
|
199
|
+
err.cause = cause;
|
|
200
|
+
return err;
|
|
201
|
+
}
|
|
202
|
+
async function loadZkGlue(deps) {
|
|
203
|
+
for (const path of deps.candidates) {
|
|
204
|
+
let mod;
|
|
205
|
+
try {
|
|
206
|
+
mod = await deps.importGlue(path);
|
|
207
|
+
} catch (e) {
|
|
208
|
+
if (isMissing(e, path)) continue;
|
|
209
|
+
throw withCause(
|
|
210
|
+
new KError2({
|
|
211
|
+
code: "KIBO_WASM_ERROR",
|
|
212
|
+
message: `the Kibo zero-knowledge core (wasm) is present at ${path} but failed to load: ${describe(e)}`,
|
|
213
|
+
context: { path, cause: describe(e) },
|
|
214
|
+
recommendation: "Reinstall @k9o/kibo; if it persists, load the core yourself and pass it to setZkWasm."
|
|
215
|
+
}),
|
|
216
|
+
e
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const m = mod;
|
|
220
|
+
if (typeof m?.zk_encrypt !== "function" || typeof m?.zk_decrypt !== "function") {
|
|
221
|
+
throw new KError2({
|
|
222
|
+
code: "KIBO_WASM_ERROR",
|
|
223
|
+
message: `the Kibo zero-knowledge core (wasm) loaded from ${path} does not export zk_encrypt and zk_decrypt`,
|
|
224
|
+
context: { path },
|
|
225
|
+
recommendation: "Reinstall @k9o/kibo; the bundled core does not match this version."
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return m;
|
|
229
|
+
}
|
|
230
|
+
throw new KError2({
|
|
231
|
+
code: "KIBO_WASM_ERROR",
|
|
232
|
+
message: `the Kibo zero-knowledge core (wasm) is not bundled in this build of @k9o/kibo: no file at ${deps.candidates.join(" or ")}; encrypt and decrypt are unavailable`,
|
|
233
|
+
context: { candidates: deps.candidates },
|
|
234
|
+
recommendation: "Install a release of @k9o/kibo that ships the core, or load it yourself and pass it to setZkWasm."
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function evalCommonJs(source, path, req, dirname) {
|
|
238
|
+
const module = { exports: {} };
|
|
239
|
+
const wrapper = new Function("exports", "require", "module", "__filename", "__dirname", source);
|
|
240
|
+
wrapper(module.exports, req, module, path, dirname);
|
|
241
|
+
return module.exports;
|
|
242
|
+
}
|
|
243
|
+
function nodeDeps(moduleUrl) {
|
|
244
|
+
const builtin = (n) => ["node", n].join(":");
|
|
245
|
+
const here = (rel) => decodeURIComponent(new URL(rel, moduleUrl).pathname.replace(/^\/([A-Za-z]:)/, "$1"));
|
|
246
|
+
return {
|
|
247
|
+
candidates: [here("../wasm/kibo_kms_rest_ffi.cjs"), here("../wasm/kibo_kms_rest_ffi.js")],
|
|
248
|
+
async importGlue(path) {
|
|
249
|
+
const fs = await import(builtin("fs"));
|
|
250
|
+
const nodeModule = await import(builtin("module"));
|
|
251
|
+
const nodePath = await import(builtin("path"));
|
|
252
|
+
const source = fs.readFileSync(path, "utf8");
|
|
253
|
+
return evalCommonJs(source, path, nodeModule.createRequire(path), nodePath.dirname(path));
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/crypto.ts
|
|
190
259
|
var cachedWasm;
|
|
191
260
|
function setZkWasm(mod) {
|
|
192
261
|
cachedWasm = mod;
|
|
193
262
|
}
|
|
194
263
|
async function loadWasm() {
|
|
195
264
|
if (cachedWasm) return cachedWasm;
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
cachedWasm = await import(pkg);
|
|
199
|
-
} catch {
|
|
200
|
-
throw new KError2({
|
|
201
|
-
code: "KIBO_WASM_ERROR",
|
|
202
|
-
message: "the Kibo zero-knowledge core (wasm) is not bundled in this build of @k9o/kibo; encrypt and decrypt are unavailable",
|
|
203
|
-
context: {},
|
|
204
|
-
recommendation: "Install a release of @k9o/kibo that ships the core, or load it yourself."
|
|
205
|
-
});
|
|
206
|
-
}
|
|
265
|
+
cachedWasm = await loadZkGlue(nodeDeps(import.meta.url));
|
|
207
266
|
return cachedWasm;
|
|
208
267
|
}
|
|
209
268
|
async function encrypt(input) {
|
|
@@ -229,14 +288,14 @@ async function decrypt(input) {
|
|
|
229
288
|
function toBytes(v) {
|
|
230
289
|
if (v instanceof Uint8Array) return v;
|
|
231
290
|
if (Array.isArray(v)) return Uint8Array.from(v);
|
|
232
|
-
throw new
|
|
291
|
+
throw new KError3({
|
|
233
292
|
code: "KIBO_WASM_DECODE_ERROR",
|
|
234
293
|
message: "ZK core returned a non-byte value where bytes were expected",
|
|
235
294
|
context: {}
|
|
236
295
|
});
|
|
237
296
|
}
|
|
238
297
|
function mapZkError(e) {
|
|
239
|
-
if (e instanceof
|
|
298
|
+
if (e instanceof KError3) return e;
|
|
240
299
|
const raw = e instanceof Error ? e.message : String(e);
|
|
241
300
|
const head = /^([A-Z][A-Z0-9_]*):\s*([\s\S]*)$/.exec(raw);
|
|
242
301
|
const code = head?.[1] ?? "KIBO_WASM_ERROR";
|
|
@@ -247,13 +306,12 @@ function mapZkError(e) {
|
|
|
247
306
|
context = { dimension: pv[1].trim(), requested: pv[2].trim(), declared: pv[3].trim() };
|
|
248
307
|
message = message.slice(0, pv.index).trim();
|
|
249
308
|
}
|
|
250
|
-
return new
|
|
309
|
+
return new KError3({ code, message, context });
|
|
251
310
|
}
|
|
252
311
|
export {
|
|
253
312
|
createKiboClient,
|
|
254
313
|
decrypt,
|
|
255
314
|
encrypt,
|
|
256
|
-
makeVaultNamespace,
|
|
257
315
|
setZkWasm
|
|
258
316
|
};
|
|
259
317
|
//# sourceMappingURL=index.js.map
|
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}` (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"]}
|
|
1
|
+
{"version":3,"sources":["../src/client-factory.ts","../src/vault.ts","../src/crypto.ts","../src/wasm-loader.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: `vault.secrets.*` and `vault.zkDeks.{store,fetch}`. */\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}` store and fetch the opaque customer-wrapped DEK envelope, project-scoped. There is\n * no cryptography in this client: it is a plain authenticated REST round-trip carrying an opaque blob (the\n * wrapping happens in the standalone `encrypt`/`decrypt`, which never call the platform).\n * `vault.secrets.*` is the typed secrets family.\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 wire `value` field is base64. */\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\n/** Bound on the pages `secrets.get` scans for an exact name. */\nconst MAX_GET_PAGES = 20;\n\n/** @internal */\nexport function makeVaultNamespace(config: HttpClientConfig) {\n const listSecrets = async (input?: ListSecretsInput): Promise<Envelope<ListSecretsResult>> =>\n 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 return {\n /** Opaque store/fetch of a customer-wrapped DEK envelope. */\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 (`/@<projectSlug>/vault/secrets`): list / get / create / revealChallenge / reveal /\n * update / rotate / move / delete.\n *\n * `region`: the gateway sets the residency region of every vault call itself and discards a caller-supplied\n * `region` query parameter, so it is optional on every method. When given, it is still sent (older gateways\n * required it).\n *\n * Response shapes for `update`/`rotate`/`move` are typed to what the gateway returns, not the OpenAPI's\n * `SecretSummary` (see `UpdateSecretResult`/`RotateSecretResult`/`MoveSecretResult` in `types.ts`).\n */\n secrets: {\n /** List secrets (metadata only, never ciphertext). `GET /@<projectSlug>/vault/secrets`. */\n list: listSecrets,\n\n /**\n * Read one secret's metadata (never its value; use `revealChallenge` + `reveal` for that).\n *\n * Vault answers a `GET /@<projectSlug>/vault/secrets/<name>` only as a reveal: with no\n * `X-K9O-Reveal-Challenge` header it refuses with 401 `K9O_REVEAL_CHALLENGE_REQUIRED`, whatever the caller's\n * rights. There is no plain metadata read on that route, so `get` reads the metadata the route that exists\n * for it returns: it lists by the exact name as prefix and picks the exact match. It throws `K9O_NOT_FOUND`\n * (status 404) when no secret has that name.\n */\n async get(name: string, _opts?: { region?: string }): Promise<Envelope<SecretSummary>> {\n let cursor: string | undefined;\n for (let page = 0; page < MAX_GET_PAGES; page++) {\n const env = await listSecrets({ prefix: name, ...(cursor !== undefined ? { cursor } : {}), limit: 100 });\n const hit = env.data.secrets.find((s) => s.name === name);\n if (hit !== undefined) return { ...env, data: hit };\n if (env.data.nextCursor === null || env.data.nextCursor === undefined) break;\n cursor = env.data.nextCursor;\n }\n throw new KError({\n code: \"K9O_NOT_FOUND\",\n message: `No secret named '${name}' in this project.`,\n context: { name },\n status: 404,\n });\n },\n\n /**\n * Create a secret under the caller's own auth.\n * `POST /@<projectSlug>/vault/secrets/<name>` -> the `sec_<base62>` summary.\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 // The wire `value` is base64 (a raw string is rejected as unparseable). `input.value` stays the\n // caller-facing plaintext; base64 is purely a wire detail handled here.\n body: { ...rest, value: encodeBase64Utf8(value) },\n });\n },\n\n /** Mint a single-use reveal challenge; pass it to the immediately-following `reveal()`.\n * `POST .../<name>?op=reveal-challenge`. The optional `reason` is accepted by the gateway and not forwarded. */\n async revealChallenge(name: string, opts?: { region?: string; reason?: 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 ...(opts?.reason !== undefined ? { body: { reason: opts.reason } } : {}),\n });\n },\n\n /** Reveal a secret's plaintext value using a single-use challenge from `revealChallenge()`.\n * `GET .../<name>` with `X-K9O-Reveal-Challenge`. Decodes the wire's base64 `value` back to plaintext\n * (symmetric 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>`.\n * Returns only `{id, name, version}` (see `UpdateSecretResult`); call `get()` after for 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`. */\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. `POST .../<name>?op=move`. */\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>`. */\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`) 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 * These are standalone exports (`import { encrypt, decrypt } from \"@k9o/kibo\"`), not methods of the client: they run\n * locally against the customer's own KMS and make no platform call. The `vault.zkDeks.{store,fetch}` surface\n * (envelope persistence) is separate and decoupled (the core never sees the store).\n */\n\nimport { KError } from \"@k9o/core/errors\";\nimport { loadZkGlue, nodeDeps } from \"./wasm-loader.ts\";\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 `kibo-kms-rest-ffi` (the core compiles\n * every KMS provider in; the `provider` input field selects at call time) and vendored under `wasm/` by the publish\n * pipeline. See `wasm-loader.ts` for how the CommonJS glue is loaded from this ESM package and for the failure modes.\n */\nasync function loadWasm(): Promise<ZkWasm> {\n if (cachedWasm) return cachedWasm;\n // A failure is not cached: the next call tries again.\n cachedWasm = await loadZkGlue(nodeDeps(import.meta.url));\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` 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","/**\n * Loads the nodejs-target glue that wasm-bindgen emits for the bound core.\n *\n * That glue is CommonJS (`exports.zk_encrypt = ...`, `require(\"fs\")`, a synchronous read of the sibling `_bg.wasm`).\n * This package is ESM (`\"type\": \"module\"`), so Node parses any `.js` under it as ESM and the glue dies with\n * `exports is not defined`; `import()` and `require()` of that file both hit it. The default loader therefore reads the\n * glue source and evaluates it with a CommonJS wrapper itself, which does not depend on the file extension or on the\n * nearest `package.json`.\n *\n * Failures are reported as what they are: no glue file at all, or a glue file that is there and failed (with the\n * underlying error attached as `cause`).\n */\n\nimport { KError } from \"@k9o/core/errors\";\nimport type { ZkWasm } from \"./crypto.ts\";\n\n/** What the loader needs from the host; injectable so the failure paths are testable without a wasm artefact. */\nexport interface WasmLoaderDeps {\n /** Absolute paths to try, in order. */\n candidates: string[];\n /** Load the glue at `path`. Must reject with an error whose `code` is `ENOENT`/`MODULE_NOT_FOUND`/`ERR_MODULE_NOT_FOUND`\n * and whose `path` (or message) names `path` when that file is missing. */\n importGlue(path: string): Promise<unknown>;\n}\n\nconst NOT_FOUND_CODES = new Set([\"ENOENT\", \"MODULE_NOT_FOUND\", \"ERR_MODULE_NOT_FOUND\"]);\n\nfunction describe(e: unknown): string {\n return e instanceof Error ? `${e.name}: ${e.message}` : String(e);\n}\n\n/** True when `e` says the file `path` itself is missing (not a file the glue went on to read). */\nfunction isMissing(e: unknown, path: string): boolean {\n if (typeof e !== \"object\" || e === null) return false;\n const err = e as { code?: unknown; path?: unknown; message?: unknown };\n if (typeof err.code !== \"string\" || !NOT_FOUND_CODES.has(err.code)) return false;\n if (typeof err.path === \"string\") return err.path === path;\n return typeof err.message === \"string\" && err.message.includes(path);\n}\n\nfunction withCause(err: KError, cause: unknown): KError {\n (err as { cause?: unknown }).cause = cause;\n return err;\n}\n\nexport async function loadZkGlue(deps: WasmLoaderDeps): Promise<ZkWasm> {\n for (const path of deps.candidates) {\n let mod: unknown;\n try {\n mod = await deps.importGlue(path);\n } catch (e) {\n if (isMissing(e, path)) continue;\n throw withCause(\n new KError({\n code: \"KIBO_WASM_ERROR\",\n message: `the Kibo zero-knowledge core (wasm) is present at ${path} but failed to load: ${describe(e)}`,\n context: { path, cause: describe(e) },\n recommendation: \"Reinstall @k9o/kibo; if it persists, load the core yourself and pass it to setZkWasm.\",\n }),\n e,\n );\n }\n const m = mod as Partial<ZkWasm> | null | undefined;\n if (typeof m?.zk_encrypt !== \"function\" || typeof m?.zk_decrypt !== \"function\") {\n throw new KError({\n code: \"KIBO_WASM_ERROR\",\n message: `the Kibo zero-knowledge core (wasm) loaded from ${path} does not export zk_encrypt and zk_decrypt`,\n context: { path },\n recommendation: \"Reinstall @k9o/kibo; the bundled core does not match this version.\",\n });\n }\n return m as ZkWasm;\n }\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: no file at ${deps.candidates.join(\" or \")}; encrypt and decrypt are unavailable`,\n context: { candidates: deps.candidates },\n recommendation: \"Install a release of @k9o/kibo that ships the core, or load it yourself and pass it to setZkWasm.\",\n });\n}\n\n/** Evaluate CommonJS source as a module living at `path`, with a `require` resolving from `path`. */\nexport function evalCommonJs(source: string, path: string, req: (id: string) => unknown, dirname: string): unknown {\n const module = { exports: {} as Record<string, unknown> };\n // eslint-disable-next-line no-new-func\n const wrapper = new Function(\"exports\", \"require\", \"module\", \"__filename\", \"__dirname\", source);\n wrapper(module.exports, req, module, path, dirname);\n return module.exports;\n}\n\n/** Node host: glue next to this package's `wasm/` directory, evaluated as CommonJS. */\nexport function nodeDeps(moduleUrl: string): WasmLoaderDeps {\n // The node builtins are named at run time so a browser/RN bundler never tries to resolve them: this path only runs on Node.\n const builtin = (n: string) => [\"node\", n].join(\":\");\n const here = (rel: string) => decodeURIComponent(new URL(rel, moduleUrl).pathname.replace(/^\\/([A-Za-z]:)/, \"$1\"));\n return {\n candidates: [here(\"../wasm/kibo_kms_rest_ffi.cjs\"), here(\"../wasm/kibo_kms_rest_ffi.js\")],\n async importGlue(path: string) {\n const fs = (await import(builtin(\"fs\"))) as typeof import(\"node:fs\");\n const nodeModule = (await import(builtin(\"module\"))) as typeof import(\"node:module\");\n const nodePath = (await import(builtin(\"path\"))) as typeof import(\"node:path\");\n const source = fs.readFileSync(path, \"utf8\"); // throws ENOENT with err.path === path when the glue is missing\n return evalCommonJs(source, path, nodeModule.createRequire(path), nodePath.dirname(path));\n },\n };\n}\n"],"mappings":"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","names":["KError","KError","KError"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@k9o/kibo` — Zero-Knowledge BYOK/HYOK vault SDK types.
|
|
3
3
|
*
|
|
4
|
-
* Surface: the ZK-DEK store/fetch. The wrapped
|
|
5
|
-
* envelope is produced client-side by the
|
|
6
|
-
*
|
|
7
|
-
* reads its interior — the strict-ZK invariant + envelope framing live in the
|
|
8
|
-
* core, never here.
|
|
4
|
+
* Surface: the ZK-DEK store/fetch and the secrets family. The wrapped
|
|
5
|
+
* envelope is produced client-side by the ZK core (compiled to wasm); this SDK ships it
|
|
6
|
+
* **verbatim and opaque** and never reads its interior.
|
|
9
7
|
*/
|
|
10
8
|
/** Customer KMS/HSM provider — non-secret routing metadata (mirrors the ZK FFI provider selector). */
|
|
11
9
|
type KmsProvider = "aws-kms" | "gcp-kms" | "azure-kv" | "hashicorp-vault";
|
|
@@ -37,7 +35,7 @@ type ZkDekRecord = {
|
|
|
37
35
|
keyVersionRef: string;
|
|
38
36
|
createdAt: string;
|
|
39
37
|
};
|
|
40
|
-
/**
|
|
38
|
+
/** Secret visibility. Write operations (create/update) stay owner-only regardless of this tag. */
|
|
41
39
|
type SecretVisibility = {
|
|
42
40
|
kind: "private";
|
|
43
41
|
} | {
|
|
@@ -45,19 +43,14 @@ type SecretVisibility = {
|
|
|
45
43
|
principals?: string[];
|
|
46
44
|
roles?: string[];
|
|
47
45
|
};
|
|
48
|
-
/** Input to create a vault secret
|
|
46
|
+
/** Input to create a vault secret: `POST /@<projectSlug>/vault/secrets/<name>`. */
|
|
49
47
|
type CreateSecretInput = {
|
|
50
48
|
/** Hierarchical path the secret is addressed under, e.g. `byos/s3-prod` (also the `:name` URL segment). */
|
|
51
49
|
name: string;
|
|
52
|
-
/** The plaintext to seal, as a regular (non-base64) string
|
|
53
|
-
* it for the wire itself (live-verified against staging: the route's OpenAPI description says
|
|
54
|
-
* "the plaintext to seal" but a raw non-base64 `value` is rejected as unparseable). */
|
|
50
|
+
/** The plaintext to seal, as a regular (non-base64) string; `vault.secrets.create` base64-encodes it for the wire. */
|
|
55
51
|
value: string;
|
|
56
|
-
/** The
|
|
57
|
-
|
|
58
|
-
* OpenAPI schema as of this writing). Distinct from any region carried inside `value`'s own
|
|
59
|
-
* envelope. */
|
|
60
|
-
region: string;
|
|
52
|
+
/** Optional. The gateway sets the residency region of the write itself and discards this; it is sent only when given. */
|
|
53
|
+
region?: string;
|
|
61
54
|
visibility?: SecretVisibility;
|
|
62
55
|
metadata?: Record<string, string>;
|
|
63
56
|
};
|
|
@@ -79,8 +72,7 @@ type SecretSummary = {
|
|
|
79
72
|
retiredAt: string | null;
|
|
80
73
|
metadata: Record<string, string>;
|
|
81
74
|
};
|
|
82
|
-
/** `client.kibo.vault.secrets.list()` input
|
|
83
|
-
* other secrets operation, list is NOT region-gated). */
|
|
75
|
+
/** `client.kibo.vault.secrets.list()` input. */
|
|
84
76
|
type ListSecretsInput = {
|
|
85
77
|
/** Filter to a path prefix. */
|
|
86
78
|
prefix?: string;
|
|
@@ -91,7 +83,7 @@ type ListSecretsResult = {
|
|
|
91
83
|
secrets: readonly SecretSummary[];
|
|
92
84
|
nextCursor: string | null;
|
|
93
85
|
};
|
|
94
|
-
/** releasing
|
|
86
|
+
/** The releasing node's observed attestation tier; present on every reveal. */
|
|
95
87
|
type AttestationTier = {
|
|
96
88
|
/** `attestation:destination:region` trust-tier URN. */
|
|
97
89
|
urn: string;
|
|
@@ -117,18 +109,15 @@ type RevealSecretResult = {
|
|
|
117
109
|
};
|
|
118
110
|
/** `update()` input. */
|
|
119
111
|
type UpdateSecretInput = {
|
|
120
|
-
/** The
|
|
121
|
-
|
|
122
|
-
region: string;
|
|
112
|
+
/** Optional. The gateway sets the residency region of the write itself and discards this; it is sent only when given. */
|
|
113
|
+
region?: string;
|
|
123
114
|
/** New plaintext — bumps `version`. The SDK base64-encodes it for the wire. */
|
|
124
115
|
value: string;
|
|
125
116
|
metadata?: Record<string, string>;
|
|
126
117
|
};
|
|
127
118
|
/**
|
|
128
|
-
* `update()` result
|
|
129
|
-
*
|
|
130
|
-
* Typed to what's actually observed rather than the aspirational doc shape; call `get()` afterward if
|
|
131
|
-
* the full summary is needed.
|
|
119
|
+
* `update()` result: the gateway returns only `{id, name, version}`, narrower than the OpenAPI `SecretSummary`.
|
|
120
|
+
* Call `get()` afterward if the full summary is needed.
|
|
132
121
|
*/
|
|
133
122
|
type UpdateSecretResult = {
|
|
134
123
|
id: string;
|
|
@@ -136,9 +125,8 @@ type UpdateSecretResult = {
|
|
|
136
125
|
version: number;
|
|
137
126
|
};
|
|
138
127
|
/**
|
|
139
|
-
* `rotate()` result
|
|
140
|
-
*
|
|
141
|
-
* `ledgerSequenceNumber` is undocumented; kept as-observed.
|
|
128
|
+
* `rotate()` result: not described by the OpenAPI schema (which declares a summary or a reveal challenge for the
|
|
129
|
+
* route); typed to what the gateway returns. `ledgerSequenceNumber` is undocumented.
|
|
142
130
|
*/
|
|
143
131
|
type RotateSecretResult = {
|
|
144
132
|
id: string;
|
|
@@ -148,9 +136,8 @@ type RotateSecretResult = {
|
|
|
148
136
|
ledgerSequenceNumber: number;
|
|
149
137
|
};
|
|
150
138
|
/**
|
|
151
|
-
* `move()` result
|
|
152
|
-
*
|
|
153
|
-
* sample was seen and a closed enum can't be asserted from one observation).
|
|
139
|
+
* `move()` result: not the OpenAPI `SecretSummary`; it has no `version` and carries an undocumented `event`
|
|
140
|
+
* field (`"SECRET_PATH_CHANGED"` observed; typed as `string`).
|
|
154
141
|
*/
|
|
155
142
|
type MoveSecretResult = {
|
|
156
143
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k9o/kibo",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Kibo
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Kibo - Zero-Knowledge BYOK/HYOK vault SDK. The vault client (secrets, wrapped data keys; composes into @k9o/core as client.kibo) plus standalone encrypt/decrypt against your own KMS, in WASM.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"directory": "packages/kibo"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@k9o/core": "1.
|
|
40
|
+
"@k9o/core": "1.3.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"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
|
|
15
|
+
/** Vault surface: `vault.secrets.*` and `vault.zkDeks.{store,fetch}`. */
|
|
16
16
|
vault: ReturnType<typeof makeVaultNamespace>;
|
|
17
17
|
};
|
|
18
18
|
|
package/src/crypto-types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TS contract for the ZK encrypt/decrypt FFI surface — mirrors the Rust
|
|
3
|
-
* `kibo-kms-rest-ffi::types` (the bound core
|
|
3
|
+
* `kibo-kms-rest-ffi::types` (the bound core) field-for-field.
|
|
4
4
|
*
|
|
5
5
|
* These types cross the wasm boundary as plain JS objects (serde-wasm-bindgen),
|
|
6
6
|
* so the field names + enum wire-tokens must match the Rust serde exactly:
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* after use and never logged or returned. The platform never sees plaintext.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
// The customer KMS provider — the same routing selector as the
|
|
16
|
+
// The customer KMS provider — the same routing selector as the ZK-DEK store
|
|
17
17
|
// surface. Reuse the canonical definition (no duplication).
|
|
18
18
|
import type { KmsProvider } from "./types.ts";
|
|
19
19
|
export type { KmsProvider };
|
|
@@ -41,7 +41,7 @@ export interface KiboAllowedOracleModes {
|
|
|
41
41
|
direct: boolean;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** Project-declared crypto-posture floor. Omit fields for the
|
|
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;
|