vaultkeeper 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +611 -0
- package/dist/index.cjs +2621 -715
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1368 -139
- package/dist/index.d.ts +1368 -139
- package/dist/index.js +2593 -710
- package/dist/index.js.map +1 -1
- package/dist/one-password-worker.js +249 -39
- package/dist/one-password-worker.js.map +1 -1
- package/package.json +27 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Buffer } from 'node:buffer';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Error hierarchy for vaultkeeper.
|
|
3
6
|
*/
|
|
@@ -37,6 +40,69 @@ declare class DeviceNotPresentError extends VaultError {
|
|
|
37
40
|
declare class AuthorizationDeniedError extends VaultError {
|
|
38
41
|
constructor(message: string);
|
|
39
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Thrown when an operation requires a backend capability (e.g.
|
|
45
|
+
* presence-per-use) that the active backend cannot provide.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* This is a configuration/backend mismatch, not a runtime authorization
|
|
49
|
+
* failure: the requirement was asserted against a backend whose configured
|
|
50
|
+
* instance does not advertise the capability, so no credential, session, or
|
|
51
|
+
* device is ever touched before this is thrown. It is fixable by switching to
|
|
52
|
+
* (or reconfiguring) a backend that provides the capability — inspect
|
|
53
|
+
* {@link NotCapableError.capability} for which one was required and
|
|
54
|
+
* {@link NotCapableError.backendType} for the backend that lacked it. Distinct
|
|
55
|
+
* from {@link AuthorizationDeniedError} (a human/token rejection) and
|
|
56
|
+
* {@link PresenceDeclinedError} (a human declined a fresh action).
|
|
57
|
+
*
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
declare class NotCapableError extends VaultError {
|
|
61
|
+
/** The `type` identifier of the active backend that lacked the capability. */
|
|
62
|
+
readonly backendType: string;
|
|
63
|
+
/**
|
|
64
|
+
* The machine-readable capability key that was required but not advertised
|
|
65
|
+
* (e.g. `'presencePerUse'`).
|
|
66
|
+
*/
|
|
67
|
+
readonly capability: string;
|
|
68
|
+
constructor(message: string, backendType: string, capability: string);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Thrown when a required fresh, per-use human presence action was explicitly
|
|
72
|
+
* declined by the human (e.g. a biometric or touch prompt was cancelled).
|
|
73
|
+
*
|
|
74
|
+
* @remarks
|
|
75
|
+
* Distinct from {@link AuthorizationDeniedError}, which signals a token or
|
|
76
|
+
* capability rejection, and from {@link PresenceTimeoutError}, which signals the
|
|
77
|
+
* device was present but no action happened in time. A declined presence action
|
|
78
|
+
* means the human was asked and said no.
|
|
79
|
+
*
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
declare class PresenceDeclinedError extends VaultError {
|
|
83
|
+
/** The `type` identifier of the backend that requested the presence action. */
|
|
84
|
+
readonly backendType: string;
|
|
85
|
+
constructor(message: string, backendType: string);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Thrown when a required fresh, per-use human presence action did not happen
|
|
89
|
+
* within the allotted time — the device was present and ready, but no touch,
|
|
90
|
+
* tap, or biometric approval was performed before the timeout elapsed.
|
|
91
|
+
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* Distinct from {@link DeviceNotPresentError} (the device itself was absent) and
|
|
94
|
+
* from {@link PresenceDeclinedError} (the human actively declined). Inspect
|
|
95
|
+
* {@link PresenceTimeoutError.timeoutMs} for how long the operation waited.
|
|
96
|
+
*
|
|
97
|
+
* @public
|
|
98
|
+
*/
|
|
99
|
+
declare class PresenceTimeoutError extends VaultError {
|
|
100
|
+
/** The `type` identifier of the backend that requested the presence action. */
|
|
101
|
+
readonly backendType: string;
|
|
102
|
+
/** How long (in milliseconds) the operation waited for the presence action. */
|
|
103
|
+
readonly timeoutMs: number;
|
|
104
|
+
constructor(message: string, backendType: string, timeoutMs: number);
|
|
105
|
+
}
|
|
40
106
|
/**
|
|
41
107
|
* Thrown when no configured backend is available or reachable.
|
|
42
108
|
* Inspect `reason` for a machine-readable cause and `attempted` for the list
|
|
@@ -135,8 +201,43 @@ declare class IdentityMismatchError extends VaultError {
|
|
|
135
201
|
constructor(message: string, previousHash: string, currentHash: string);
|
|
136
202
|
}
|
|
137
203
|
/**
|
|
138
|
-
* Thrown
|
|
139
|
-
*
|
|
204
|
+
* Thrown by {@link VaultKeeper.setup} when the caller does not make an
|
|
205
|
+
* unambiguous executable-trust decision.
|
|
206
|
+
*
|
|
207
|
+
* `setup()` deliberately has no default trust behaviour: the caller must either
|
|
208
|
+
* pass a real `executablePath` (which runs trust-on-first-use verification) or
|
|
209
|
+
* explicitly opt out with `skipTrust: true` (a development-only escape hatch).
|
|
210
|
+
* Supplying neither — or both at once — throws this error rather than silently
|
|
211
|
+
* skipping verification. Passing the retired `'dev'` sentinel as `executablePath`
|
|
212
|
+
* also throws this error. Inspect {@link ExecutableTrustRequiredError.reason} to
|
|
213
|
+
* distinguish the cases.
|
|
214
|
+
*
|
|
215
|
+
* @public
|
|
216
|
+
*/
|
|
217
|
+
declare class ExecutableTrustRequiredError extends VaultError {
|
|
218
|
+
/**
|
|
219
|
+
* Machine-readable discriminator for why the trust choice was rejected.
|
|
220
|
+
* `'missing-choice'` means neither `executablePath` nor `skipTrust: true`
|
|
221
|
+
* was provided, so no trust decision was expressed. `'conflicting-choice'`
|
|
222
|
+
* means both `executablePath` and `skipTrust: true` were provided, which
|
|
223
|
+
* are mutually exclusive intents. `'legacy-dev-sentinel'` means
|
|
224
|
+
* `executablePath` was the retired literal `'dev'` opt-out sentinel, which is
|
|
225
|
+
* no longer supported and must be replaced with `skipTrust: true`.
|
|
226
|
+
*/
|
|
227
|
+
readonly reason: 'missing-choice' | 'conflicting-choice' | 'legacy-dev-sentinel';
|
|
228
|
+
constructor(message: string, reason: 'missing-choice' | 'conflicting-choice' | 'legacy-dev-sentinel');
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Thrown when spawning or running a subprocess fails.
|
|
232
|
+
*
|
|
233
|
+
* This covers a delegated `exec()` call failing due to an invalid request
|
|
234
|
+
* (e.g. a `{{secret}}` placeholder in the `command` field) or a
|
|
235
|
+
* process-level error (e.g. the command binary is not found or cannot be
|
|
236
|
+
* spawned) — and also vaultkeeper's own internal tool invocations (doctor
|
|
237
|
+
* version probes, the keychain/secret-tool/dpapi/yubikey credential helpers),
|
|
238
|
+
* which run through the same subprocess utility. Callers that need to handle
|
|
239
|
+
* only delegated-exec failures should scope their `catch` to the code paths
|
|
240
|
+
* that call `exec()`, not discriminate on this type alone.
|
|
140
241
|
*
|
|
141
242
|
* @public
|
|
142
243
|
*/
|
|
@@ -148,9 +249,10 @@ declare class ExecError extends VaultError {
|
|
|
148
249
|
constructor(message: string, command: string);
|
|
149
250
|
}
|
|
150
251
|
/**
|
|
151
|
-
* Thrown when a JWE string cannot be
|
|
152
|
-
* malformed (
|
|
153
|
-
*
|
|
252
|
+
* Thrown when a JWE string is invalid or cannot be processed — for example,
|
|
253
|
+
* it is structurally malformed (wrong number of segments, invalid
|
|
254
|
+
* Base64URL), decryption fails (wrong key, tampered ciphertext), or the
|
|
255
|
+
* decrypted payload does not match the expected claims schema.
|
|
154
256
|
*
|
|
155
257
|
* @public
|
|
156
258
|
*/
|
|
@@ -167,8 +269,10 @@ declare class AccessorConsumedError extends VaultError {
|
|
|
167
269
|
constructor(message: string);
|
|
168
270
|
}
|
|
169
271
|
/**
|
|
170
|
-
* Thrown when a caller requests a signing
|
|
171
|
-
*
|
|
272
|
+
* Thrown when a caller requests a signing key algorithm that is not a
|
|
273
|
+
* supported JOSE algorithm identifier. The signing algorithm registry uses
|
|
274
|
+
* strict JOSE identifiers (currently `'EdDSA'`); an unrecognized value is
|
|
275
|
+
* rejected rather than defaulted.
|
|
172
276
|
*
|
|
173
277
|
* @public
|
|
174
278
|
*/
|
|
@@ -183,6 +287,183 @@ declare class InvalidAlgorithmError extends VaultError {
|
|
|
183
287
|
readonly allowed: string[];
|
|
184
288
|
constructor(message: string, algorithm: string, allowed: string[]);
|
|
185
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Thrown when signing-key material cannot be parsed. Two paths raise it. During
|
|
292
|
+
* verification, the supplied public key is not a structurally parseable SPKI PEM
|
|
293
|
+
* public key (or a private key was passed where a public key is required); this
|
|
294
|
+
* is an operational fault distinct from a signature that simply does not verify,
|
|
295
|
+
* which returns `false`. During key use, a stored signing key decrypts cleanly
|
|
296
|
+
* but is not valid private key material — corrupt or tampered on disk — when
|
|
297
|
+
* exporting its public half or signing with it (`getPublicKey`/`signWithKey`).
|
|
298
|
+
* The message never echoes any part of the key material.
|
|
299
|
+
*
|
|
300
|
+
* @public
|
|
301
|
+
*/
|
|
302
|
+
declare class InvalidKeyMaterialError extends VaultError {
|
|
303
|
+
constructor(message: string);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Thrown when a named signing key does not exist in the active backend — for
|
|
307
|
+
* example `key export` or `sign` is asked for a name that was never enrolled
|
|
308
|
+
* with `key create`. This is distinct from {@link SecretNotFoundError}: signing
|
|
309
|
+
* keys occupy their own namespace and are never returned as ordinary secrets.
|
|
310
|
+
*
|
|
311
|
+
* @public
|
|
312
|
+
*/
|
|
313
|
+
declare class SigningKeyNotFoundError extends VaultError {
|
|
314
|
+
/**
|
|
315
|
+
* The signing-key name that was requested (the caller-facing `--name`, not
|
|
316
|
+
* the internal namespaced identifier).
|
|
317
|
+
*/
|
|
318
|
+
readonly keyName: string;
|
|
319
|
+
constructor(message: string, keyName: string);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Thrown when `key create` (or `createSigningKey`) is asked to enroll a signing
|
|
323
|
+
* key under a name that already exists. Enrollment never silently overwrites an
|
|
324
|
+
* existing key, because a regenerated keypair would invalidate every public key
|
|
325
|
+
* that was previously exported and pinned by a verifier.
|
|
326
|
+
*
|
|
327
|
+
* @public
|
|
328
|
+
*/
|
|
329
|
+
declare class SigningKeyAlreadyExistsError extends VaultError {
|
|
330
|
+
/**
|
|
331
|
+
* The signing-key name that already exists (the caller-facing `--name`, not
|
|
332
|
+
* the internal namespaced identifier).
|
|
333
|
+
*/
|
|
334
|
+
readonly keyName: string;
|
|
335
|
+
constructor(message: string, keyName: string);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Thrown when a signing operation (`key create`, `key export`, `sign`) is
|
|
339
|
+
* requested against a backend that does not implement the signing contract.
|
|
340
|
+
* Signing is never silently emulated on a backend that cannot perform it in a
|
|
341
|
+
* key-stays-backend-side manner; inspect {@link SigningNotSupportedError.builtInSigningBackends}
|
|
342
|
+
* for the built-in backend types that do.
|
|
343
|
+
*
|
|
344
|
+
* @public
|
|
345
|
+
*/
|
|
346
|
+
declare class SigningNotSupportedError extends VaultError {
|
|
347
|
+
/** The type identifier of the active backend that cannot sign. */
|
|
348
|
+
readonly backendType: string;
|
|
349
|
+
/**
|
|
350
|
+
* The **built-in** backend type identifiers known to implement the signing
|
|
351
|
+
* contract (currently just the `file` backend). This is deliberately a
|
|
352
|
+
* static list of built-ins, not a live capability survey: a consumer that
|
|
353
|
+
* registers its own {@link SigningBackend} is not enumerated here, because
|
|
354
|
+
* discovering that would require instantiating every registered backend —
|
|
355
|
+
* a side effect that must not happen on an error path. A caller can still
|
|
356
|
+
* point a user at a working built-in, and custom backends may implement the
|
|
357
|
+
* contract independently.
|
|
358
|
+
*/
|
|
359
|
+
readonly builtInSigningBackends: string[];
|
|
360
|
+
constructor(message: string, backendType: string, builtInSigningBackends: string[]);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Thrown when an encrypted-at-rest secret entry cannot be decrypted — e.g.
|
|
364
|
+
* the stored ciphertext is truncated/corrupted or the AES-GCM auth tag fails
|
|
365
|
+
* to verify. The message never echoes any part of the secret or key
|
|
366
|
+
* material.
|
|
367
|
+
*
|
|
368
|
+
* @public
|
|
369
|
+
*/
|
|
370
|
+
declare class DecryptionError extends VaultError {
|
|
371
|
+
/**
|
|
372
|
+
* The path of the encrypted entry that failed to decrypt.
|
|
373
|
+
*/
|
|
374
|
+
readonly path: string;
|
|
375
|
+
constructor(message: string, path: string);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Thrown when a delegated `fetch()` call fails before a `Response` can be
|
|
379
|
+
* produced — for example the URL is malformed or the underlying network
|
|
380
|
+
* request fails (DNS failure, connection refused, TLS error).
|
|
381
|
+
*
|
|
382
|
+
* @public
|
|
383
|
+
*/
|
|
384
|
+
declare class FetchError extends VaultError {
|
|
385
|
+
/**
|
|
386
|
+
* The unresolved URL template that fetch failed to request, with
|
|
387
|
+
* `{{secret}}`/`{{secret:name}}` placeholders left intact. The
|
|
388
|
+
* placeholder-resolved URL is deliberately never stored here, so an
|
|
389
|
+
* injected secret is never exposed.
|
|
390
|
+
*/
|
|
391
|
+
readonly url: string;
|
|
392
|
+
constructor(message: string, url: string);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Thrown when a config value fails structural or semantic validation (e.g. a
|
|
396
|
+
* whitespace-only `BackendConfig.path`).
|
|
397
|
+
*/
|
|
398
|
+
declare class ConfigValidationError extends VaultError {
|
|
399
|
+
/**
|
|
400
|
+
* The dotted/bracketed path to the offending config field (e.g.
|
|
401
|
+
* `'backends[0].path'`).
|
|
402
|
+
*/
|
|
403
|
+
readonly field: string;
|
|
404
|
+
/**
|
|
405
|
+
* The path of the config file that failed validation, when the error
|
|
406
|
+
* originated from loading a file on disk (via `loadConfig`) rather than
|
|
407
|
+
* from validating an in-memory value directly. This is `configDir` joined
|
|
408
|
+
* with `config.json` exactly as provided to `loadConfig` — it is not
|
|
409
|
+
* guaranteed to be absolute (`loadConfig` does not resolve a relative
|
|
410
|
+
* `configDir`).
|
|
411
|
+
*/
|
|
412
|
+
readonly configFilePath: string | undefined;
|
|
413
|
+
constructor(message: string, field: string, configFilePath?: string);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Thrown when a config's `backends[].type` names a backend that is not
|
|
417
|
+
* registered with the {@link BackendRegistry}.
|
|
418
|
+
*
|
|
419
|
+
* A specialization of {@link ConfigValidationError}: an unknown backend type is
|
|
420
|
+
* a semantic schema failure (the config parses and is structurally valid, but
|
|
421
|
+
* names a backend that cannot be created), so it fails config validation the
|
|
422
|
+
* same way `version !== 1` does. It carries the offending `backendType` and the
|
|
423
|
+
* set of `knownTypes` so a consumer — notably `doctor` — can give the same
|
|
424
|
+
* "Available types: …" guidance the runtime {@link BackendUnavailableError}
|
|
425
|
+
* gives, without parsing the human-readable message. This closes the gap where
|
|
426
|
+
* a config with an unknown backend type parsed as valid JSON and passed
|
|
427
|
+
* `doctor` with a false "System ready.", only for the next real command to
|
|
428
|
+
* throw {@link BackendUnavailableError} (issue #215).
|
|
429
|
+
*/
|
|
430
|
+
declare class UnknownBackendTypeError extends ConfigValidationError {
|
|
431
|
+
/** The unregistered backend type named in the config. */
|
|
432
|
+
readonly backendType: string;
|
|
433
|
+
/**
|
|
434
|
+
* The backend type identifiers that were registered when validation ran —
|
|
435
|
+
* the valid options to offer in remediation.
|
|
436
|
+
*/
|
|
437
|
+
readonly knownTypes: string[];
|
|
438
|
+
constructor(message: string, field: string, backendType: string, knownTypes: string[], configFilePath?: string);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Thrown when a config file's contents cannot be parsed as JSON.
|
|
442
|
+
*
|
|
443
|
+
* The `message` already embeds the file path, the parse location (when the
|
|
444
|
+
* underlying `SyntaxError` exposes one), and a remediation hint that either
|
|
445
|
+
* points at `vaultkeeper config init` (via the separate `@vaultkeeper/cli`
|
|
446
|
+
* package — this library ships no CLI of its own) or at repairing/replacing
|
|
447
|
+
* the config directly through this library's JS API — see issues #68, #100.
|
|
448
|
+
* `path` and `location` are also exposed individually for callers (e.g.
|
|
449
|
+
* `doctor`) that want to report them as structured fields rather than
|
|
450
|
+
* re-parsing the message.
|
|
451
|
+
*/
|
|
452
|
+
declare class ConfigParseError extends VaultError {
|
|
453
|
+
/**
|
|
454
|
+
* The path of the config file that failed to parse. This is `configDir`
|
|
455
|
+
* joined with `config.json` exactly as provided to `loadConfig` — it is
|
|
456
|
+
* not guaranteed to be absolute (`loadConfig` does not resolve a relative
|
|
457
|
+
* `configDir`).
|
|
458
|
+
*/
|
|
459
|
+
readonly path: string;
|
|
460
|
+
/**
|
|
461
|
+
* A human-readable parse location (e.g. `'line 3, column 12'`), when one
|
|
462
|
+
* could be derived from the underlying `SyntaxError`.
|
|
463
|
+
*/
|
|
464
|
+
readonly location: string | undefined;
|
|
465
|
+
constructor(message: string, path: string, location: string | undefined);
|
|
466
|
+
}
|
|
186
467
|
/**
|
|
187
468
|
* Thrown during initialization when a required system dependency (e.g. OpenSSL
|
|
188
469
|
* or a native credential helper) is missing or incompatible.
|
|
@@ -195,20 +476,49 @@ declare class SetupError extends VaultError {
|
|
|
195
476
|
constructor(message: string, dependency: string);
|
|
196
477
|
}
|
|
197
478
|
/**
|
|
198
|
-
* Thrown when a filesystem operation fails
|
|
199
|
-
* problem
|
|
479
|
+
* Thrown when a filesystem operation fails. Common causes include a
|
|
480
|
+
* permission or access problem, for example the config directory is not
|
|
481
|
+
* writable, but the underlying failure may be any Node.js errno condition,
|
|
482
|
+
* for example the disk is full or a file was expected but a directory was
|
|
483
|
+
* found. Inspect the code property for the specific errno code when one is
|
|
484
|
+
* available.
|
|
200
485
|
*/
|
|
201
486
|
declare class FilesystemError extends VaultError {
|
|
202
487
|
/**
|
|
203
|
-
* The
|
|
488
|
+
* The path of the file or directory that caused the error, as provided by
|
|
489
|
+
* the caller. Not guaranteed to be absolute — e.g. `loadConfig` throws this
|
|
490
|
+
* with `configDir` joined with `config.json` exactly as given, without
|
|
491
|
+
* resolving a relative `configDir`.
|
|
204
492
|
*/
|
|
205
493
|
readonly path: string;
|
|
206
494
|
/**
|
|
207
|
-
* The
|
|
208
|
-
*
|
|
495
|
+
* The file operation or access mode that was being attempted when the
|
|
496
|
+
* failure occurred, for example 'read', 'write', 'delete', or 'rwx' for a
|
|
497
|
+
* directory create/access check. Despite the field name, this does not
|
|
498
|
+
* imply the failure was itself a permission problem — it names the
|
|
499
|
+
* attempted operation regardless of the underlying errno, which may be a
|
|
500
|
+
* non-permission code such as ENOSPC or EISDIR.
|
|
209
501
|
*/
|
|
210
502
|
readonly permission: string;
|
|
211
|
-
|
|
503
|
+
/**
|
|
504
|
+
* The Node.js errno code from the underlying filesystem failure, for
|
|
505
|
+
* example EACCES, EPERM, ENOSPC, or EISDIR. Undefined when the error was
|
|
506
|
+
* constructed without an underlying cause, or when that cause did not
|
|
507
|
+
* expose a string errno code. Prefer this over parsing the message text,
|
|
508
|
+
* which is not a contractual format.
|
|
509
|
+
*/
|
|
510
|
+
readonly code: string | undefined;
|
|
511
|
+
/**
|
|
512
|
+
* @param message - Human-readable description of the failure.
|
|
513
|
+
* @param filePath - The path of the file or directory that caused the error.
|
|
514
|
+
* @param permission - The file operation or access mode being attempted,
|
|
515
|
+
* for example 'read', 'write', 'delete', or 'rwx'. See the `permission`
|
|
516
|
+
* property for why this need not indicate an actual permission problem.
|
|
517
|
+
* @param cause - The underlying error that was caught, if any. Recorded as
|
|
518
|
+
* the standard `Error.cause` and used to populate `code` when it exposes a
|
|
519
|
+
* string errno code.
|
|
520
|
+
*/
|
|
521
|
+
constructor(message: string, filePath: string, permission: string, cause?: unknown);
|
|
212
522
|
}
|
|
213
523
|
/**
|
|
214
524
|
* Thrown when a key rotation is requested while a previous rotation is still
|
|
@@ -221,12 +531,91 @@ declare class RotationInProgressError extends VaultError {
|
|
|
221
531
|
/**
|
|
222
532
|
* Shared types and interfaces for vaultkeeper.
|
|
223
533
|
*/
|
|
534
|
+
|
|
224
535
|
/** Trust tier for executable identity verification. */
|
|
225
536
|
type TrustTier = 1 | 2 | 3;
|
|
226
537
|
/** Key status in the rotation lifecycle. */
|
|
227
538
|
type KeyStatus = 'current' | 'previous' | 'deprecated';
|
|
228
|
-
/**
|
|
229
|
-
|
|
539
|
+
/**
|
|
540
|
+
* Status of a preflight check.
|
|
541
|
+
*
|
|
542
|
+
* `'invalid'` applies specifically to the `config` check: the config file
|
|
543
|
+
* exists but fails to parse or fails schema validation (see issue #68).
|
|
544
|
+
*/
|
|
545
|
+
type PreflightCheckStatus = 'ok' | 'missing' | 'version-unsupported' | 'invalid';
|
|
546
|
+
/**
|
|
547
|
+
* The kind of error that made a preflight check fail, as a stable
|
|
548
|
+
* machine-readable discriminant. `'config-parse'` means the config file
|
|
549
|
+
* could not be parsed as JSON; `'config-validation'` means it parsed but
|
|
550
|
+
* failed schema validation; `'config-unknown-backend'` is a specific
|
|
551
|
+
* validation failure where `backends[].type` names a backend that is not
|
|
552
|
+
* registered, carrying the offending type and the valid options;
|
|
553
|
+
* `'config-read'` means the config file could not be read at all (for example
|
|
554
|
+
* a permission failure on the file or its parent directory) — a different
|
|
555
|
+
* remediation from parse/validation, since overwriting the file with
|
|
556
|
+
* `config init --force` cannot fix a read-permission problem.
|
|
557
|
+
*/
|
|
558
|
+
type PreflightCheckErrorKind = 'config-parse' | 'config-validation' | 'config-unknown-backend' | 'config-read';
|
|
559
|
+
/**
|
|
560
|
+
* Structured, remediation-free error context for a failed preflight check.
|
|
561
|
+
*
|
|
562
|
+
* This carries the machine-readable facts a caller needs to build its own
|
|
563
|
+
* audience-appropriate remediation message, so a consumer never has to parse
|
|
564
|
+
* the human-readable `reason` prose. It is currently populated only for the
|
|
565
|
+
* `config` check when the config file is present but invalid.
|
|
566
|
+
*
|
|
567
|
+
* The `reason` field intentionally keeps the library's own remediation text
|
|
568
|
+
* (which points a library consumer at installing the CLI); a consumer that
|
|
569
|
+
* ships its own CLI should read this structured field instead and phrase the
|
|
570
|
+
* remediation itself.
|
|
571
|
+
*/
|
|
572
|
+
interface PreflightCheckError {
|
|
573
|
+
/** The kind of failure, as a stable machine-readable discriminant. */
|
|
574
|
+
kind: PreflightCheckErrorKind;
|
|
575
|
+
/**
|
|
576
|
+
* Path to the config file that failed to parse or validate, as derived from
|
|
577
|
+
* the doctor call's `configDir`. Not guaranteed to be absolute — it is
|
|
578
|
+
* `configDir` joined with `config.json` exactly as given, so it is relative
|
|
579
|
+
* when `configDir` is relative.
|
|
580
|
+
*/
|
|
581
|
+
configPath: string;
|
|
582
|
+
/**
|
|
583
|
+
* Human-readable parse location within the config file (for example
|
|
584
|
+
* `line 3, column 12`), present only for a `'config-parse'` failure.
|
|
585
|
+
*/
|
|
586
|
+
location?: string | undefined;
|
|
587
|
+
/**
|
|
588
|
+
* The dotted/bracketed path to the offending config field (for example
|
|
589
|
+
* `backends` or `backends[0].path`), present for a `'config-validation'` or
|
|
590
|
+
* `'config-unknown-backend'` failure. This is the validation analogue of
|
|
591
|
+
* `location`: it lets a consumer point the user at exactly which field
|
|
592
|
+
* failed schema validation, the way `location` points at a parse position,
|
|
593
|
+
* without reusing the human-readable `reason` prose (which carries the
|
|
594
|
+
* library's own "install @vaultkeeper/cli" remediation).
|
|
595
|
+
*/
|
|
596
|
+
field?: string | undefined;
|
|
597
|
+
/**
|
|
598
|
+
* The unregistered backend type named in `backends[].type`, present only for
|
|
599
|
+
* a `'config-unknown-backend'` failure. Lets a consumer echo the offending
|
|
600
|
+
* type in its remediation without parsing the `reason` prose.
|
|
601
|
+
*/
|
|
602
|
+
backendType?: string | undefined;
|
|
603
|
+
/**
|
|
604
|
+
* The backend type identifiers that were registered when validation ran —
|
|
605
|
+
* the valid options — present only for a `'config-unknown-backend'` failure.
|
|
606
|
+
* Lets a consumer offer the same "Available types: …" guidance the runtime
|
|
607
|
+
* `BackendUnavailableError` gives.
|
|
608
|
+
*/
|
|
609
|
+
knownBackendTypes?: readonly string[] | undefined;
|
|
610
|
+
/**
|
|
611
|
+
* The Node.js errno code (for example `EACCES`, `EPERM`, `EISDIR`) from the
|
|
612
|
+
* underlying filesystem failure, present only for a `'config-read'` failure
|
|
613
|
+
* and only when the cause exposed a string errno code. Lets a consumer
|
|
614
|
+
* distinguish a permission problem from another read failure when phrasing
|
|
615
|
+
* the remediation.
|
|
616
|
+
*/
|
|
617
|
+
code?: string | undefined;
|
|
618
|
+
}
|
|
230
619
|
/** Result of a preflight check for a single dependency. */
|
|
231
620
|
interface PreflightCheck {
|
|
232
621
|
/** Human-readable name of the dependency being checked. */
|
|
@@ -237,11 +626,30 @@ interface PreflightCheck {
|
|
|
237
626
|
version?: string | undefined;
|
|
238
627
|
/** Human-readable explanation of why the status is not `'ok'`. */
|
|
239
628
|
reason?: string | undefined;
|
|
629
|
+
/**
|
|
630
|
+
* Structured, remediation-free error context when this check failed with a
|
|
631
|
+
* recognized error, so a caller can build its own remediation message
|
|
632
|
+
* instead of parsing the `reason` prose. Populated only for the `config`
|
|
633
|
+
* check when the config file is present but invalid.
|
|
634
|
+
*/
|
|
635
|
+
error?: PreflightCheckError | undefined;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* A {@link PreflightCheck} scoped by whether its dependency is required for
|
|
639
|
+
* the active/configured backend(s). Plugin-backend checks (`op`, `ykman`)
|
|
640
|
+
* are `required: false` when their backend isn't enabled — a non-`'ok'`
|
|
641
|
+
* status there is informational, not a system-readiness blocker (issue
|
|
642
|
+
* #116). They are promoted to `required: true` when their backend is
|
|
643
|
+
* explicitly enabled (e.g. `--backend yubikey` requires `ykman`).
|
|
644
|
+
*/
|
|
645
|
+
interface ScopedPreflightCheck extends PreflightCheck {
|
|
646
|
+
/** Whether this dependency is required by the active/configured backend(s). */
|
|
647
|
+
required: boolean;
|
|
240
648
|
}
|
|
241
649
|
/** Aggregated result from all preflight checks. */
|
|
242
650
|
interface PreflightResult {
|
|
243
651
|
/** Individual check results, one per dependency inspected. */
|
|
244
|
-
checks:
|
|
652
|
+
checks: ScopedPreflightCheck[];
|
|
245
653
|
/** `true` if all required checks passed and the system is ready. */
|
|
246
654
|
ready: boolean;
|
|
247
655
|
/** Non-fatal advisory messages about optional missing dependencies. */
|
|
@@ -260,51 +668,69 @@ interface VaultResponse {
|
|
|
260
668
|
* Request for delegated HTTP fetch.
|
|
261
669
|
*
|
|
262
670
|
* String values in `url`, `headers`, and `body` may include the placeholder
|
|
263
|
-
* `{{secret}}
|
|
264
|
-
* before the request
|
|
671
|
+
* `{{secret}}` (single-token mode) or `{{secret:name}}` (multi-token mode),
|
|
672
|
+
* which are replaced with actual secret values immediately before the request
|
|
673
|
+
* is sent.
|
|
265
674
|
*/
|
|
266
675
|
interface FetchRequest {
|
|
267
676
|
/**
|
|
268
|
-
* The target URL. May contain `{{secret}}` which is
|
|
269
|
-
* value before the fetch is executed
|
|
677
|
+
* The target URL. May contain `{{secret}}` or `{{secret:name}}` which is
|
|
678
|
+
* replaced with the secret value before the fetch is executed.
|
|
270
679
|
*/
|
|
271
680
|
url: string;
|
|
272
681
|
/** HTTP method (defaults to `'GET'` when omitted). */
|
|
273
682
|
method?: string | undefined;
|
|
274
683
|
/**
|
|
275
|
-
* Request headers. Any header value may contain `{{secret}}
|
|
276
|
-
* replaced with the secret value before
|
|
684
|
+
* Request headers. Any header value may contain `{{secret}}` or
|
|
685
|
+
* `{{secret:name}}`, which is replaced with the secret value before
|
|
686
|
+
* the request is sent.
|
|
277
687
|
*/
|
|
278
688
|
headers?: Record<string, string> | undefined;
|
|
279
689
|
/**
|
|
280
|
-
* Request body. May contain `{{secret}}`, which is
|
|
281
|
-
* value before the request is sent.
|
|
690
|
+
* Request body. May contain `{{secret}}` or `{{secret:name}}`, which is
|
|
691
|
+
* replaced with the secret value before the request is sent.
|
|
282
692
|
*/
|
|
283
693
|
body?: string | undefined;
|
|
284
694
|
}
|
|
285
695
|
/**
|
|
286
696
|
* Request for delegated command execution.
|
|
287
697
|
*
|
|
288
|
-
* String values in `
|
|
289
|
-
*
|
|
290
|
-
* is
|
|
698
|
+
* String values in `env` may include the placeholder `{{secret}}`
|
|
699
|
+
* (single-token mode) or `{{secret:name}}` (multi-token mode), which are
|
|
700
|
+
* replaced with actual secret values immediately before the command is
|
|
701
|
+
* spawned. Placeholders are **not** supported in `command` or `args` —
|
|
702
|
+
* `VaultKeeper.exec()` throws `ExecError` if one appears there.
|
|
291
703
|
*/
|
|
292
704
|
interface ExecRequest {
|
|
293
705
|
/** The command (binary) to execute. */
|
|
294
706
|
command: string;
|
|
295
707
|
/**
|
|
296
|
-
* Command-line arguments.
|
|
297
|
-
*
|
|
708
|
+
* Command-line arguments. Secret placeholders (`{{secret}}` or
|
|
709
|
+
* `{{secret:name}}`) are **not** supported here — process arguments are
|
|
710
|
+
* visible to other processes via `ps` and often collected in logs and
|
|
711
|
+
* telemetry. `VaultKeeper.exec()` throws `ExecError` if a placeholder
|
|
712
|
+
* appears in any argument. Use `env` to inject secrets instead.
|
|
298
713
|
*/
|
|
299
714
|
args?: string[] | undefined;
|
|
300
715
|
/**
|
|
301
716
|
* Additional environment variables to merge into the child process
|
|
302
|
-
* environment. Any value may contain `{{secret}}
|
|
303
|
-
* the secret value before the command is spawned.
|
|
717
|
+
* environment. Any value may contain `{{secret}}` or `{{secret:name}}`,
|
|
718
|
+
* which is replaced with the secret value before the command is spawned.
|
|
304
719
|
*/
|
|
305
720
|
env?: Record<string, string> | undefined;
|
|
306
721
|
/** Working directory for the spawned process. */
|
|
307
722
|
cwd?: string | undefined;
|
|
723
|
+
/**
|
|
724
|
+
* Whether to redact injected secret values from the captured `stdout` and
|
|
725
|
+
* `stderr` before they are returned. Defaults to `true`: every occurrence of
|
|
726
|
+
* an injected secret value in the captured output is replaced with
|
|
727
|
+
* `[REDACTED]`, so the raw secret never appears in {@link ExecResult} even
|
|
728
|
+
* when the spawned command echoes it. Set to `false` to receive the raw,
|
|
729
|
+
* unredacted output — only for callers that genuinely need it (for example
|
|
730
|
+
* output that legitimately contains the secret and must be preserved), since
|
|
731
|
+
* doing so forfeits the redaction guarantee.
|
|
732
|
+
*/
|
|
733
|
+
redact?: boolean | undefined;
|
|
308
734
|
}
|
|
309
735
|
/** Result from delegated command execution. */
|
|
310
736
|
interface ExecResult {
|
|
@@ -318,10 +744,11 @@ interface ExecResult {
|
|
|
318
744
|
/**
|
|
319
745
|
* Callback-based secret accessor with auto-zeroing.
|
|
320
746
|
*
|
|
321
|
-
* The accessor is backed by a
|
|
322
|
-
* `Buffer` containing the secret to
|
|
323
|
-
* the callback
|
|
324
|
-
*
|
|
747
|
+
* The accessor is backed by a Proxy and is single-use via an internal
|
|
748
|
+
* consumed flag. Calling `read()` passes a `Buffer` containing the secret to
|
|
749
|
+
* the callback, then zeroes the buffer after the callback returns and passes
|
|
750
|
+
* the callback's return value through. The accessor can only be read once; a
|
|
751
|
+
* second call throws.
|
|
325
752
|
*/
|
|
326
753
|
interface SecretAccessor {
|
|
327
754
|
/**
|
|
@@ -331,51 +758,81 @@ interface SecretAccessor {
|
|
|
331
758
|
* as UTF-8. The buffer is zeroed immediately after the callback returns, so
|
|
332
759
|
* callers must not store a reference to it beyond the callback scope.
|
|
333
760
|
*
|
|
334
|
-
*
|
|
761
|
+
* The callback's return value is passed through, so a caller-derived result
|
|
762
|
+
* (for example `buf.toString()` or a hash) flows out of `read()`:
|
|
763
|
+
* `const digest = accessor.read((buf) => sha256(buf))`. To preserve the
|
|
764
|
+
* zero-copy, auto-zeroing contract, derive a new value inside the callback —
|
|
765
|
+
* never return the raw `buf` itself, which is zeroed before `read()` returns.
|
|
766
|
+
*
|
|
767
|
+
* @param callback - Function that receives the secret buffer and returns a
|
|
768
|
+
* caller-derived value.
|
|
769
|
+
* @returns Whatever the callback returns.
|
|
335
770
|
* @throws {Error} If the accessor has already been consumed.
|
|
336
771
|
*/
|
|
337
|
-
read(callback: (buf: Buffer) =>
|
|
772
|
+
read<T>(callback: (buf: Buffer) => T): T;
|
|
338
773
|
}
|
|
339
774
|
/**
|
|
340
|
-
*
|
|
775
|
+
* A signing algorithm identifier from the strict JOSE registry (RFC 7518).
|
|
341
776
|
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
777
|
+
* Only `'EdDSA'` (Ed25519) is supported today; the identifier is intentionally
|
|
778
|
+
* a strict JOSE `alg` value so future algorithms (`'ES256'`, `'RS256'`, …) each
|
|
779
|
+
* bind to their proper curve/key type rather than an ambiguous label.
|
|
344
780
|
*/
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
781
|
+
type SigningAlgorithm = 'EdDSA';
|
|
782
|
+
/**
|
|
783
|
+
* The public half of an enrolled signing key.
|
|
784
|
+
*
|
|
785
|
+
* Returned by `key create` / `key export` and used to verify detached
|
|
786
|
+
* signatures independently of vaultkeeper.
|
|
787
|
+
*/
|
|
788
|
+
interface SigningPublicKey {
|
|
789
|
+
/** SPKI (SubjectPublicKeyInfo) PEM encoding of the public key. */
|
|
790
|
+
publicKeyPem: string;
|
|
791
|
+
/** The JOSE algorithm this key signs with. */
|
|
792
|
+
algorithm: SigningAlgorithm;
|
|
348
793
|
/**
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* Non-Edwards keys (RSA, EC) default to `'sha256'` when omitted.
|
|
352
|
-
* Weak algorithms (e.g. `'md5'`, `'sha1'`) are rejected.
|
|
794
|
+
* Stable key identifier: the base64url-encoded SHA-256 of the SPKI DER. Used
|
|
795
|
+
* as the JWS `kid` protected-header value so a verifier can select the key.
|
|
353
796
|
*/
|
|
354
|
-
|
|
797
|
+
kid: string;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Request to sign a caller-supplied payload with a named signing key.
|
|
801
|
+
*
|
|
802
|
+
* The `payload` is arbitrary bytes to be signed with detachment (RFC 7797) —
|
|
803
|
+
* it is never stored and never treated as a secret. Strings are UTF-8-encoded
|
|
804
|
+
* before signing.
|
|
805
|
+
*/
|
|
806
|
+
interface SignRequest {
|
|
807
|
+
/** The payload bytes to sign. Strings are treated as UTF-8. */
|
|
808
|
+
payload: string | Buffer;
|
|
355
809
|
}
|
|
356
|
-
/**
|
|
810
|
+
/**
|
|
811
|
+
* Result of a signing operation.
|
|
812
|
+
*
|
|
813
|
+
* The signature is a detached-payload Compact JWS (RFC 7515 §7.2.2 + RFC 7797
|
|
814
|
+
* `b64:false`, `crit:["b64"]`): `<protected>..<signature>`, with the payload
|
|
815
|
+
* omitted. Any standards-compliant JOSE library can verify it given the
|
|
816
|
+
* detached payload and the public key.
|
|
817
|
+
*/
|
|
357
818
|
interface SignResult {
|
|
358
|
-
/**
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Algorithm label describing how the signature was produced.
|
|
362
|
-
* For Edwards keys this is the key type (e.g. `'ed25519'`).
|
|
363
|
-
* For other keys this matches the `algorithm` field from the request
|
|
364
|
-
* (or the default `'sha256'`).
|
|
365
|
-
*/
|
|
366
|
-
algorithm: string;
|
|
819
|
+
/** The detached-payload compact JWS (`<protected>..<signature>`). */
|
|
820
|
+
jws: string;
|
|
367
821
|
}
|
|
368
822
|
/**
|
|
369
|
-
* Request for signature verification.
|
|
823
|
+
* Request for detached-signature verification.
|
|
370
824
|
*
|
|
371
|
-
* This is a
|
|
372
|
-
* no VaultKeeper instance or capability token is needed.
|
|
825
|
+
* This is a fully offline operation that only requires public key material —
|
|
826
|
+
* no VaultKeeper instance, backend, config, or capability token is needed.
|
|
373
827
|
*/
|
|
374
828
|
interface VerifyRequest {
|
|
375
|
-
/** The
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
|
|
829
|
+
/** The detached payload bytes that were signed. Strings are treated as UTF-8. */
|
|
830
|
+
payload: string | Buffer;
|
|
831
|
+
/**
|
|
832
|
+
* The detached-payload compact JWS produced by {@link SignResult.jws}
|
|
833
|
+
* (`<protected>..<signature>`).
|
|
834
|
+
*/
|
|
835
|
+
jws: string;
|
|
379
836
|
/**
|
|
380
837
|
* PEM-encoded public key (SPKI format) as a string.
|
|
381
838
|
*
|
|
@@ -383,11 +840,6 @@ interface VerifyRequest {
|
|
|
383
840
|
* accepted by this interface.
|
|
384
841
|
*/
|
|
385
842
|
publicKey: string;
|
|
386
|
-
/**
|
|
387
|
-
* Override the hash algorithm. Ignored for Ed25519/Ed448 keys.
|
|
388
|
-
* Non-Edwards keys default to `'sha256'` when omitted.
|
|
389
|
-
*/
|
|
390
|
-
algorithm?: string | undefined;
|
|
391
843
|
}
|
|
392
844
|
/** Vaultkeeper configuration file structure. */
|
|
393
845
|
interface VaultConfig {
|
|
@@ -430,20 +882,19 @@ interface BackendConfig {
|
|
|
430
882
|
options?: Record<string, string> | undefined;
|
|
431
883
|
}
|
|
432
884
|
|
|
433
|
-
/**
|
|
434
|
-
* Backend abstraction layer types for vaultkeeper.
|
|
435
|
-
*/
|
|
436
885
|
/**
|
|
437
886
|
* Factory function for creating a SecretBackend instance.
|
|
438
887
|
*
|
|
439
888
|
* @remarks
|
|
440
889
|
* Factories may optionally accept a {@link BackendConfig} to configure the
|
|
441
|
-
* backend from the user's vaultkeeper config file
|
|
442
|
-
*
|
|
890
|
+
* backend from the user's vaultkeeper config file, and the resolved config
|
|
891
|
+
* directory (the same directory config and key material are read from) so
|
|
892
|
+
* file-based backends can default their storage under it. Factories that do
|
|
893
|
+
* not need either can ignore the parameters.
|
|
443
894
|
*
|
|
444
895
|
* @public
|
|
445
896
|
*/
|
|
446
|
-
type BackendFactory = (config?: BackendConfig) => SecretBackend;
|
|
897
|
+
type BackendFactory = (config?: BackendConfig, configDir?: string) => SecretBackend;
|
|
447
898
|
/**
|
|
448
899
|
* Abstraction interface for all secret storage backends.
|
|
449
900
|
*
|
|
@@ -505,6 +956,170 @@ interface ListableBackend extends SecretBackend {
|
|
|
505
956
|
* @public
|
|
506
957
|
*/
|
|
507
958
|
declare function isListableBackend(backend: SecretBackend): backend is ListableBackend;
|
|
959
|
+
/**
|
|
960
|
+
* A keyed backend operation that a presence-per-use requirement can gate.
|
|
961
|
+
*
|
|
962
|
+
* @remarks
|
|
963
|
+
* Used by {@link BackendCapabilities.presenceEnforcedOperations} to express that
|
|
964
|
+
* an instance forces a fresh per-use action for only *some* operations. `'read'`
|
|
965
|
+
* covers the secret read behind `setup`/`exec`; `'store'`, `'delete'`, and
|
|
966
|
+
* `'sign'` are the write, removal, and signing paths.
|
|
967
|
+
*
|
|
968
|
+
* @public
|
|
969
|
+
*/
|
|
970
|
+
type PresenceOperation = 'read' | 'store' | 'delete' | 'sign';
|
|
971
|
+
/**
|
|
972
|
+
* The set of security capabilities a configured backend instance advertises.
|
|
973
|
+
*
|
|
974
|
+
* @remarks
|
|
975
|
+
* Capabilities describe what a **specific configured instance** guarantees, not
|
|
976
|
+
* what its backend *type* is generally able to do. Two instances of the same
|
|
977
|
+
* backend type can report different capabilities depending on their
|
|
978
|
+
* configuration (e.g. a YubiKey slot with a touch policy vs. one without, or
|
|
979
|
+
* 1Password in `per-access` vs. `session` mode). Never derive a capability from
|
|
980
|
+
* the backend's `type` alone.
|
|
981
|
+
*
|
|
982
|
+
* The shape is intentionally open to extension: new capability flags may be
|
|
983
|
+
* added over time, so consumers should read only the fields they understand and
|
|
984
|
+
* treat a missing/unknown field as absent.
|
|
985
|
+
*
|
|
986
|
+
* @public
|
|
987
|
+
*/
|
|
988
|
+
interface BackendCapabilities {
|
|
989
|
+
/**
|
|
990
|
+
* `true` when this configured instance can force a distinct, fresh physical
|
|
991
|
+
* human action (e.g. a YubiKey touch, a gpg-smartcard tap, or a 1Password
|
|
992
|
+
* per-use biometric approval) — a deliberate action taken *for this operation,
|
|
993
|
+
* right now*, not merely "a vault was unlocked at some point."
|
|
994
|
+
*
|
|
995
|
+
* The guarantee is **operation-scoped**, not blanket: when `presencePerUse` is
|
|
996
|
+
* `true`, that fresh action is available and **non-bypassably enforced** for
|
|
997
|
+
* exactly the operations listed in
|
|
998
|
+
* {@link BackendCapabilities.presenceEnforcedOperations} (all keyed operations
|
|
999
|
+
* when that field is omitted). For a covered operation, a
|
|
1000
|
+
* {@link https://github.com/mike-north/vaultkeeper/issues/122 | `--require-presence-per-use`}
|
|
1001
|
+
* request drives a fresh action that cannot be satisfied from a cached or
|
|
1002
|
+
* session-unlocked state. For an operation **outside** that set, the request is
|
|
1003
|
+
* **refused** with a `NotCapableError` — it is never silently satisfied from a
|
|
1004
|
+
* cached unlock. A backend that only caches an unlock, or that is
|
|
1005
|
+
* encryption-only with no per-use action, reports `false`.
|
|
1006
|
+
*
|
|
1007
|
+
* Backends that do not implement {@link PresenceCapableBackend} are treated as
|
|
1008
|
+
* `false` by {@link getBackendCapabilities} — an unknown backend never
|
|
1009
|
+
* silently claims presence.
|
|
1010
|
+
*/
|
|
1011
|
+
readonly presencePerUse: boolean;
|
|
1012
|
+
/**
|
|
1013
|
+
* The keyed operations for which this instance actually forces a fresh per-use
|
|
1014
|
+
* human action. When **omitted**, a `presencePerUse: true` instance is taken
|
|
1015
|
+
* to force presence for **all** keyed operations — the default for a touch
|
|
1016
|
+
* device (e.g. a YubiKey whose challenge-response touch fires on every
|
|
1017
|
+
* `store`/`retrieve`/`delete`).
|
|
1018
|
+
*
|
|
1019
|
+
* A backend that can force presence for only *some* operations must list
|
|
1020
|
+
* exactly those, so a `--require-presence-per-use` request for an **uncovered**
|
|
1021
|
+
* operation fails closed with a `NotCapableError` rather than silently passing
|
|
1022
|
+
* without a fresh action. For example, 1Password `per-access` forces a fresh
|
|
1023
|
+
* biometric on reads (`setup`/`exec`) but routes `store`/`delete` through the
|
|
1024
|
+
* cached session client, so it reports `['read']` — a flagged `store`/`delete`
|
|
1025
|
+
* is then correctly refused.
|
|
1026
|
+
*
|
|
1027
|
+
* Ignored when {@link BackendCapabilities.presencePerUse} is `false`.
|
|
1028
|
+
*/
|
|
1029
|
+
readonly presenceEnforcedOperations?: readonly PresenceOperation[];
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Backend that can report its security {@link BackendCapabilities} for its
|
|
1033
|
+
* configured instance.
|
|
1034
|
+
*
|
|
1035
|
+
* @remarks
|
|
1036
|
+
* This is an optional extension interface, mirroring {@link ListableBackend} and
|
|
1037
|
+
* {@link SigningBackend}: it is **not** a required member of
|
|
1038
|
+
* {@link SecretBackend}. Prefer {@link getBackendCapabilities} over calling
|
|
1039
|
+
* {@link PresenceCapableBackend.getCapabilities} directly, so a backend that
|
|
1040
|
+
* does not implement the interface safely defaults to no capabilities rather
|
|
1041
|
+
* than being assumed to have them.
|
|
1042
|
+
*
|
|
1043
|
+
* {@link PresenceCapableBackend.getCapabilities} is asynchronous and describes
|
|
1044
|
+
* the **current configured/live state** of the instance — it must reflect
|
|
1045
|
+
* configuration (or a live device/session probe) rather than a hardcoded
|
|
1046
|
+
* per-type answer, and must not itself trigger a human-presence prompt.
|
|
1047
|
+
*
|
|
1048
|
+
* @public
|
|
1049
|
+
*/
|
|
1050
|
+
interface PresenceCapableBackend extends SecretBackend {
|
|
1051
|
+
/**
|
|
1052
|
+
* Report the capabilities of this configured instance.
|
|
1053
|
+
* @returns The instance's {@link BackendCapabilities}.
|
|
1054
|
+
*/
|
|
1055
|
+
getCapabilities(): Promise<BackendCapabilities>;
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Type guard for backends that implement the capability-reporting contract.
|
|
1059
|
+
* @public
|
|
1060
|
+
*/
|
|
1061
|
+
declare function isPresenceCapableBackend(backend: SecretBackend): backend is PresenceCapableBackend;
|
|
1062
|
+
/**
|
|
1063
|
+
* Resolve a backend's {@link BackendCapabilities}, defaulting safely for
|
|
1064
|
+
* backends that do not implement {@link PresenceCapableBackend}.
|
|
1065
|
+
*
|
|
1066
|
+
* @remarks
|
|
1067
|
+
* A backend without the capability interface reports `{ presencePerUse: false }`
|
|
1068
|
+
* — an unknown backend never silently claims a security guarantee it cannot
|
|
1069
|
+
* prove. This is the only supported way to query capabilities; callers must not
|
|
1070
|
+
* assume a capability from a backend's `type`.
|
|
1071
|
+
*
|
|
1072
|
+
* @param backend - The backend instance to query.
|
|
1073
|
+
* @returns The instance's capabilities, or the safe default for a backend that
|
|
1074
|
+
* does not implement {@link PresenceCapableBackend}.
|
|
1075
|
+
* @public
|
|
1076
|
+
*/
|
|
1077
|
+
declare function getBackendCapabilities(backend: SecretBackend): Promise<BackendCapabilities>;
|
|
1078
|
+
/**
|
|
1079
|
+
* Backend that can enroll and use signing keys entirely on its own side.
|
|
1080
|
+
*
|
|
1081
|
+
* @remarks
|
|
1082
|
+
* Signing keys are a distinct resource from secrets: a private key must never
|
|
1083
|
+
* flow through {@link SecretBackend.store}/{@link SecretBackend.retrieve} or a
|
|
1084
|
+
* capability token's claims. A signing backend generates the keypair, exposes
|
|
1085
|
+
* only the public half, and performs the signature itself — the private key
|
|
1086
|
+
* never leaves the backend. This is what keeps a key out of any JWE claims
|
|
1087
|
+
* token and lets a presence-per-use backend enforce its guarantee for signing.
|
|
1088
|
+
*
|
|
1089
|
+
* Implementations must keep signing keys in a namespace that cannot collide
|
|
1090
|
+
* with or be read as ordinary secrets.
|
|
1091
|
+
*
|
|
1092
|
+
* @public
|
|
1093
|
+
*/
|
|
1094
|
+
interface SigningBackend extends SecretBackend {
|
|
1095
|
+
/**
|
|
1096
|
+
* Enroll a new signing keypair under `id`.
|
|
1097
|
+
* @param id - Namespaced signing-key identifier.
|
|
1098
|
+
* @param algorithm - The JOSE algorithm to generate a key for.
|
|
1099
|
+
* @throws If a signing key already exists under `id`, or `algorithm` is not
|
|
1100
|
+
* supported by this backend.
|
|
1101
|
+
*/
|
|
1102
|
+
generateSigningKey(id: string, algorithm: SigningAlgorithm): Promise<void>;
|
|
1103
|
+
/**
|
|
1104
|
+
* Return the public half of the signing key stored under `id`.
|
|
1105
|
+
* @param id - Namespaced signing-key identifier.
|
|
1106
|
+
* @throws A `SigningKeyNotFoundError` if no signing key exists under `id`.
|
|
1107
|
+
*/
|
|
1108
|
+
getPublicKey(id: string): Promise<SigningPublicKey>;
|
|
1109
|
+
/**
|
|
1110
|
+
* Sign `data` with the private key stored under `id`, returning the raw
|
|
1111
|
+
* signature bytes. The private key never leaves the backend.
|
|
1112
|
+
* @param id - Namespaced signing-key identifier.
|
|
1113
|
+
* @param data - The exact bytes to sign (e.g. a JWS signing input).
|
|
1114
|
+
* @throws A `SigningKeyNotFoundError` if no signing key exists under `id`.
|
|
1115
|
+
*/
|
|
1116
|
+
signWithKey(id: string, data: Buffer): Promise<Buffer>;
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Type guard for backends that implement the signing contract.
|
|
1120
|
+
* @public
|
|
1121
|
+
*/
|
|
1122
|
+
declare function isSigningBackend(backend: SecretBackend): backend is SigningBackend;
|
|
508
1123
|
|
|
509
1124
|
/**
|
|
510
1125
|
* Types for the backend setup protocol.
|
|
@@ -570,10 +1185,12 @@ declare class BackendRegistry {
|
|
|
570
1185
|
* Create a backend instance by type.
|
|
571
1186
|
* @param type - Backend type identifier
|
|
572
1187
|
* @param config - Optional backend configuration forwarded to the factory
|
|
1188
|
+
* @param configDir - Optional resolved config directory forwarded to the
|
|
1189
|
+
* factory, so file-based backends can default their storage under it
|
|
573
1190
|
* @returns A SecretBackend instance
|
|
574
1191
|
* @throws {@link BackendUnavailableError} if the backend type is not registered
|
|
575
1192
|
*/
|
|
576
|
-
static create(type: string, config?: BackendConfig): SecretBackend;
|
|
1193
|
+
static create(type: string, config?: BackendConfig, configDir?: string): SecretBackend;
|
|
577
1194
|
/**
|
|
578
1195
|
* Get all registered backend type identifiers.
|
|
579
1196
|
* @returns Array of backend type identifiers
|
|
@@ -632,7 +1249,23 @@ declare class BackendRegistry {
|
|
|
632
1249
|
* class fields enforce that no property on the token object leaks data.
|
|
633
1250
|
*/
|
|
634
1251
|
|
|
635
|
-
/**
|
|
1252
|
+
/**
|
|
1253
|
+
* An opaque handle to authorized secret claims.
|
|
1254
|
+
*
|
|
1255
|
+
* A `CapabilityToken` is produced by {@link VaultKeeper.authorize} and
|
|
1256
|
+
* deliberately exposes no readable data. The underlying claims — including the
|
|
1257
|
+
* secret value — are held in a module-private `WeakMap` that this class does
|
|
1258
|
+
* not reference, and its private fields keep any property from leaking them
|
|
1259
|
+
* (`toString()` returns only a debug identifier). There is intentionally no
|
|
1260
|
+
* public API for reading the claims directly.
|
|
1261
|
+
*
|
|
1262
|
+
* To use the secret, pass the token to a {@link VaultKeeper} access method —
|
|
1263
|
+
* {@link VaultKeeper.getSecret}, {@link VaultKeeper.fetch},
|
|
1264
|
+
* {@link VaultKeeper.exec}, or {@link VaultKeeper.sign} — which resolve the
|
|
1265
|
+
* claims internally.
|
|
1266
|
+
*
|
|
1267
|
+
* @public
|
|
1268
|
+
*/
|
|
636
1269
|
declare class CapabilityToken {
|
|
637
1270
|
#private;
|
|
638
1271
|
constructor();
|
|
@@ -675,6 +1308,18 @@ interface RunDoctorOptions {
|
|
|
675
1308
|
* (backward-compatible behavior).
|
|
676
1309
|
*/
|
|
677
1310
|
backends?: BackendConfig[];
|
|
1311
|
+
/**
|
|
1312
|
+
* When provided (and `backends` is not explicitly given), doctor loads and
|
|
1313
|
+
* validates the config file under this directory, adding a `config`
|
|
1314
|
+
* preflight check to the result. A present-but-invalid config file (parse
|
|
1315
|
+
* or schema failure) becomes a failing, required check — with the
|
|
1316
|
+
* underlying error's message (file path, parse location, remediation hint)
|
|
1317
|
+
* as `reason` — so an invalid config is visible in `doctor`'s output and
|
|
1318
|
+
* fails the overall `ready` result (issue #68). A missing config file is
|
|
1319
|
+
* not an error: `loadConfig` resolves platform defaults and the check
|
|
1320
|
+
* reports `ok`.
|
|
1321
|
+
*/
|
|
1322
|
+
configDir?: string;
|
|
678
1323
|
}
|
|
679
1324
|
/**
|
|
680
1325
|
* Run all platform-appropriate preflight checks and aggregate the results.
|
|
@@ -686,28 +1331,201 @@ declare function runDoctor(options?: RunDoctorOptions): Promise<PreflightResult>
|
|
|
686
1331
|
* VaultKeeper main class — wires together all vaultkeeper subsystems.
|
|
687
1332
|
*/
|
|
688
1333
|
|
|
689
|
-
/**
|
|
1334
|
+
/**
|
|
1335
|
+
* Map of named secrets to their capability tokens.
|
|
1336
|
+
*
|
|
1337
|
+
* Use with `exec()` or `fetch()` to inject multiple secrets into a single
|
|
1338
|
+
* request. Each key becomes the name referenced in `{{secret:name}}`
|
|
1339
|
+
* placeholders.
|
|
1340
|
+
*
|
|
1341
|
+
* @example
|
|
1342
|
+
* ```ts
|
|
1343
|
+
* const { token: apiToken } = await vault.authorize(apiJwe)
|
|
1344
|
+
* const { token: dbToken } = await vault.authorize(dbJwe)
|
|
1345
|
+
*
|
|
1346
|
+
* await vault.exec(
|
|
1347
|
+
* { apiKey: apiToken, dbPass: dbToken },
|
|
1348
|
+
* { command: 'deploy', env: { API_KEY: '{{secret:apiKey}}', DB: '{{secret:dbPass}}' } },
|
|
1349
|
+
* )
|
|
1350
|
+
* ```
|
|
1351
|
+
*
|
|
1352
|
+
* @public
|
|
1353
|
+
*/
|
|
1354
|
+
type SecretTokenMap = Record<string, CapabilityToken>;
|
|
1355
|
+
/**
|
|
1356
|
+
* Options for initializing VaultKeeper.
|
|
1357
|
+
*
|
|
1358
|
+
* @remarks
|
|
1359
|
+
* When neither `config` nor a config file (at `configDir`) is present, and
|
|
1360
|
+
* {@link VaultKeeperOptions.backend} is not set, the active backend falls back
|
|
1361
|
+
* to the safe zero-config default resolved by {@link defaultBackendType} — the
|
|
1362
|
+
* `file` backend, on every platform, so a missing config never silently
|
|
1363
|
+
* targets the real OS credential store. Inspect
|
|
1364
|
+
* {@link VaultKeeper.activeBackendType} after `init()` to confirm which backend
|
|
1365
|
+
* a given instance resolved to. To use the OS-native store instead, opt in via
|
|
1366
|
+
* an explicit config, or `vaultkeeper config init --backend <type>` from the
|
|
1367
|
+
* separate `@vaultkeeper/cli` package (see {@link platformNativeBackendType}).
|
|
1368
|
+
* When `backend` is set instead, see that option's own JSDoc for the fallback
|
|
1369
|
+
* config used in its place.
|
|
1370
|
+
*/
|
|
690
1371
|
interface VaultKeeperOptions {
|
|
691
1372
|
/** Override the config directory. */
|
|
692
1373
|
configDir?: string | undefined;
|
|
693
1374
|
/** Supply config directly, skipping file load. */
|
|
694
1375
|
config?: VaultConfig | undefined;
|
|
1376
|
+
/**
|
|
1377
|
+
* Inject a {@link SecretBackend} instance directly, bypassing the global
|
|
1378
|
+
* {@link BackendRegistry} and `config.backends` resolution entirely.
|
|
1379
|
+
*
|
|
1380
|
+
* This is the primary hook for tests and embedders that want to store and
|
|
1381
|
+
* retrieve secrets without registering a backend globally or hand
|
|
1382
|
+
* assembling a full {@link VaultConfig}. When set, this backend instance is
|
|
1383
|
+
* used for every `store()`/`retrieve()`/`setup()` call.
|
|
1384
|
+
*
|
|
1385
|
+
* **Precedence with `config`/`configDir`:**
|
|
1386
|
+
* - `backend` always wins over the backend that `config.backends` (or the
|
|
1387
|
+
* config loaded from `configDir`) would otherwise resolve — the
|
|
1388
|
+
* `backends` array is never consulted when `backend` is set.
|
|
1389
|
+
* - Other config fields (`keyRotation`, `defaults`, `developmentMode`)
|
|
1390
|
+
* still come from `config`, or from the config loaded from `configDir`,
|
|
1391
|
+
* when either is provided.
|
|
1392
|
+
* - If `backend` is set and `config` is omitted, a minimal built-in
|
|
1393
|
+
* default config is used instead of loading one from `configDir` — so a
|
|
1394
|
+
* caller that only needs an injected backend never has to construct a
|
|
1395
|
+
* {@link VaultConfig} or touch `configDir` at all.
|
|
1396
|
+
*/
|
|
1397
|
+
backend?: SecretBackend | undefined;
|
|
695
1398
|
/** Skip the doctor preflight check. */
|
|
696
1399
|
skipDoctor?: boolean | undefined;
|
|
697
1400
|
}
|
|
698
|
-
/**
|
|
699
|
-
|
|
1401
|
+
/**
|
|
1402
|
+
* Options common to every backend-touching operation that can be gated on a
|
|
1403
|
+
* fresh, per-use human presence action.
|
|
1404
|
+
*
|
|
1405
|
+
* @remarks
|
|
1406
|
+
* When `requirePresencePerUse` is `true`, the operation refuses — with a
|
|
1407
|
+
* {@link NotCapableError}, before any credential, session, or device is touched
|
|
1408
|
+
* — unless the active backend's configured instance advertises
|
|
1409
|
+
* {@link BackendCapabilities.presencePerUse}. When the backend is capable, the
|
|
1410
|
+
* operation proceeds through the backend's ordinary path, which (by the meaning
|
|
1411
|
+
* of the capability) forces a distinct fresh human action for this specific
|
|
1412
|
+
* call; a cached or session-unlocked state can never satisfy it. Capabilities
|
|
1413
|
+
* are queried fresh on every call and never cached across operations.
|
|
1414
|
+
*
|
|
1415
|
+
* @public
|
|
1416
|
+
*/
|
|
1417
|
+
interface PresenceRequirementOptions {
|
|
1418
|
+
/**
|
|
1419
|
+
* Require the active backend to force a fresh, per-use human presence action
|
|
1420
|
+
* for this operation. Defaults to `false` (no presence requirement).
|
|
1421
|
+
*/
|
|
1422
|
+
requirePresencePerUse?: boolean | undefined;
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Options for {@link VaultKeeper.setup} that are independent of the mandatory
|
|
1426
|
+
* executable-trust choice. Intersected with that choice to form
|
|
1427
|
+
* {@link SetupOptions}.
|
|
1428
|
+
*
|
|
1429
|
+
* @public
|
|
1430
|
+
*/
|
|
1431
|
+
interface SetupOptionsBase extends PresenceRequirementOptions {
|
|
700
1432
|
/** TTL in minutes for the JWE. */
|
|
701
1433
|
ttlMinutes?: number | undefined;
|
|
702
1434
|
/** Usage limit (null for unlimited). */
|
|
703
1435
|
useLimit?: number | null | undefined;
|
|
704
|
-
/** Executable path for identity binding. Use "dev" for dev mode. */
|
|
705
|
-
executablePath?: string | undefined;
|
|
706
1436
|
/** Trust tier override. */
|
|
707
1437
|
trustTier?: TrustTier | undefined;
|
|
708
1438
|
/** Backend type to use. */
|
|
709
1439
|
backendType?: string | undefined;
|
|
710
1440
|
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Options for the setup operation.
|
|
1443
|
+
*
|
|
1444
|
+
* @remarks
|
|
1445
|
+
* The executable-trust choice is **mandatory and mutually exclusive**, and the
|
|
1446
|
+
* type system enforces it: `SetupOptions` is {@link SetupOptionsBase}
|
|
1447
|
+
* intersected with a choice of **exactly one** of `executablePath` (run
|
|
1448
|
+
* trust-on-first-use verification — the production choice) or `skipTrust: true`
|
|
1449
|
+
* (deliberately skip verification — development only). An options object with
|
|
1450
|
+
* **neither** field, or with **both**, fails to typecheck; and because
|
|
1451
|
+
* {@link VaultKeeper.setup}'s options argument is required, `vault.setup('NAME')`
|
|
1452
|
+
* and `vault.setup('NAME', {})` are compile-time type errors rather than
|
|
1453
|
+
* runtime-only failures. {@link ExecutableTrustRequiredError} remains a runtime
|
|
1454
|
+
* backstop for callers without static typing (e.g. plain JavaScript), and is
|
|
1455
|
+
* still thrown if `executablePath` is the retired legacy `'dev'` sentinel.
|
|
1456
|
+
*
|
|
1457
|
+
* @public
|
|
1458
|
+
*/
|
|
1459
|
+
type SetupOptions = SetupOptionsBase & ({
|
|
1460
|
+
/**
|
|
1461
|
+
* Path to the calling executable, used to bind the minted token to that
|
|
1462
|
+
* executable's identity. `setup()` runs trust-on-first-use (TOFU)
|
|
1463
|
+
* verification: the file is hashed (SHA-256) and checked against the
|
|
1464
|
+
* local trust manifest. This is the safe, production choice.
|
|
1465
|
+
*
|
|
1466
|
+
* A path registered via `setDevelopmentMode` is still exempted from
|
|
1467
|
+
* hashing (the established development-mode allowlist); any other path
|
|
1468
|
+
* is verified. Mutually exclusive with `skipTrust`.
|
|
1469
|
+
*
|
|
1470
|
+
* **Rebuild caveat:** for a compiled or bundled entry point the file's
|
|
1471
|
+
* hash changes on every rebuild, so binding to a dev build target (e.g.
|
|
1472
|
+
* `process.argv[1]`) makes the next `setup()` after a recompile throw
|
|
1473
|
+
* `IdentityMismatchError`. In production point this at a **stable**
|
|
1474
|
+
* artifact — a released binary, or `process.execPath` to trust the Node
|
|
1475
|
+
* runtime; for a frequently-rebuilt local caller you want to keep
|
|
1476
|
+
* verifying, use `setDevelopmentMode` instead.
|
|
1477
|
+
*/
|
|
1478
|
+
executablePath: string;
|
|
1479
|
+
skipTrust?: never;
|
|
1480
|
+
} | {
|
|
1481
|
+
/**
|
|
1482
|
+
* Development-only escape hatch: skip executable-trust (TOFU)
|
|
1483
|
+
* verification. The minted token carries no executable identity binding.
|
|
1484
|
+
*
|
|
1485
|
+
* **Security warning:** a token minted with `skipTrust: true` is not
|
|
1486
|
+
* bound to any calling executable, so any process that obtains the JWE
|
|
1487
|
+
* can redeem it. Use this only in local development or tests — never in
|
|
1488
|
+
* production. Prefer `executablePath` so executable trust is actually
|
|
1489
|
+
* enforced. Mutually exclusive with `executablePath`.
|
|
1490
|
+
*/
|
|
1491
|
+
skipTrust: true;
|
|
1492
|
+
executablePath?: never;
|
|
1493
|
+
});
|
|
1494
|
+
/**
|
|
1495
|
+
* Trust status of an executable, as recorded in the trust-on-first-use (TOFU)
|
|
1496
|
+
* trust manifest.
|
|
1497
|
+
*
|
|
1498
|
+
* Returned by {@link VaultKeeper.approveExecutable} and
|
|
1499
|
+
* {@link VaultKeeper.checkExecutableTrust}.
|
|
1500
|
+
*
|
|
1501
|
+
* @public
|
|
1502
|
+
*/
|
|
1503
|
+
interface ExecutableTrustStatus {
|
|
1504
|
+
/**
|
|
1505
|
+
* Whether the executable's current hash is approved in the trust manifest.
|
|
1506
|
+
* When `false`, callers must obtain approval (e.g. an interactive prompt)
|
|
1507
|
+
* before granting secret access.
|
|
1508
|
+
*/
|
|
1509
|
+
trusted: boolean;
|
|
1510
|
+
/** SHA-256 hex digest of the executable's current contents. */
|
|
1511
|
+
hash: string;
|
|
1512
|
+
/**
|
|
1513
|
+
* `true` when the executable is already known to the manifest but its
|
|
1514
|
+
* current hash does not match any approved value — a TOFU conflict. A
|
|
1515
|
+
* conflicting executable is never trusted; callers must re-approve or deny.
|
|
1516
|
+
*/
|
|
1517
|
+
hashMismatch: boolean;
|
|
1518
|
+
/**
|
|
1519
|
+
* Hashes recorded as approved for this executable in the trust manifest, in
|
|
1520
|
+
* approval order (empty when the executable has never been approved). When
|
|
1521
|
+
* {@link ExecutableTrustStatus.hashMismatch} is `true`, these are the prior
|
|
1522
|
+
* approved values that {@link ExecutableTrustStatus.hash} no longer matches;
|
|
1523
|
+
* the last element is the most recently approved hash.
|
|
1524
|
+
*/
|
|
1525
|
+
approvedHashes: readonly string[];
|
|
1526
|
+
/** Human-readable description of how trust was (or was not) established. */
|
|
1527
|
+
reason: string;
|
|
1528
|
+
}
|
|
711
1529
|
/**
|
|
712
1530
|
* Main entry point for vaultkeeper. Orchestrates backends, keys, JWE tokens,
|
|
713
1531
|
* identity verification, and access patterns.
|
|
@@ -718,6 +1536,12 @@ declare class VaultKeeper {
|
|
|
718
1536
|
/**
|
|
719
1537
|
* Initialize a new VaultKeeper instance.
|
|
720
1538
|
* Runs doctor checks (unless skipped), loads config, and sets up the key manager.
|
|
1539
|
+
*
|
|
1540
|
+
* The configured secret backend is resolved lazily on first use, not during
|
|
1541
|
+
* `init()`. Trust-only operations (e.g. {@link VaultKeeper.approveExecutable},
|
|
1542
|
+
* {@link VaultKeeper.checkExecutableTrust}) therefore succeed even when the
|
|
1543
|
+
* configured backend or plugin is unavailable or unregistered; a
|
|
1544
|
+
* misconfigured backend surfaces only when a secret operation is invoked.
|
|
721
1545
|
*/
|
|
722
1546
|
static init(options?: VaultKeeperOptions): Promise<VaultKeeper>;
|
|
723
1547
|
/**
|
|
@@ -730,6 +1554,59 @@ declare class VaultKeeper {
|
|
|
730
1554
|
* @param options - Optional doctor options (e.g. `{ backends }` to scope checks).
|
|
731
1555
|
*/
|
|
732
1556
|
static doctor(options?: RunDoctorOptions): Promise<PreflightResult>;
|
|
1557
|
+
/**
|
|
1558
|
+
* The type identifier of the active backend — the first enabled backend in
|
|
1559
|
+
* the resolved configuration (the one `store()`, `delete()`, and `setup()`
|
|
1560
|
+
* operate on).
|
|
1561
|
+
*
|
|
1562
|
+
* @remarks
|
|
1563
|
+
* This is a pure, side-effect-free view of the resolved configuration: it
|
|
1564
|
+
* reads the type of the first enabled backend without instantiating the
|
|
1565
|
+
* backend or requiring it to be registered or healthy. Reading it therefore
|
|
1566
|
+
* never throws for an unavailable or unregistered backend — unlike a secret
|
|
1567
|
+
* operation — so it is safe to call purely to introspect an instance.
|
|
1568
|
+
*
|
|
1569
|
+
* When a backend was injected via `init({ backend })`, config-based
|
|
1570
|
+
* resolution is bypassed entirely and this reports the injected instance's
|
|
1571
|
+
* declared `type` (see {@link SecretBackend}) — or the stable sentinel
|
|
1572
|
+
* `'custom'` if it declares an empty type. It never throws in the injected
|
|
1573
|
+
* path.
|
|
1574
|
+
*
|
|
1575
|
+
* Use it to confirm which backend an instance resolved to, especially when
|
|
1576
|
+
* no config file exists and the safe zero-config default applies (see
|
|
1577
|
+
* {@link defaultBackendType}). With no config this reads `file` on every
|
|
1578
|
+
* platform, so secret operations never silently target the real OS
|
|
1579
|
+
* credential store; opt into the native store (see
|
|
1580
|
+
* {@link platformNativeBackendType}) via explicit config to change this.
|
|
1581
|
+
*
|
|
1582
|
+
* @throws A {@link BackendUnavailableError} only when the configuration has
|
|
1583
|
+
* no enabled backend at all (a configuration error, not a backend fault).
|
|
1584
|
+
* This can only happen in the config-driven path; an injected backend never
|
|
1585
|
+
* throws here.
|
|
1586
|
+
*
|
|
1587
|
+
* @public
|
|
1588
|
+
*/
|
|
1589
|
+
get activeBackendType(): string;
|
|
1590
|
+
/**
|
|
1591
|
+
* Report the {@link BackendCapabilities} of the active backend's configured
|
|
1592
|
+
* instance — the same backend {@link VaultKeeper.store}, {@link VaultKeeper.setup},
|
|
1593
|
+
* and {@link VaultKeeper.sign} operate on.
|
|
1594
|
+
*
|
|
1595
|
+
* @remarks
|
|
1596
|
+
* Unlike the pure {@link VaultKeeper.activeBackendType} getter, this resolves
|
|
1597
|
+
* (instantiates) the backend, because capabilities are a property of the
|
|
1598
|
+
* configured instance, not of the type. The answer reflects configured/live
|
|
1599
|
+
* state — e.g. a YubiKey slot's touch policy or 1Password's access mode — and
|
|
1600
|
+
* a backend that does not implement {@link PresenceCapableBackend} reports the
|
|
1601
|
+
* safe default (`{ presencePerUse: false }`) via {@link getBackendCapabilities}.
|
|
1602
|
+
* Reading this never triggers a human-presence prompt.
|
|
1603
|
+
*
|
|
1604
|
+
* @returns The active backend instance's capabilities.
|
|
1605
|
+
* @throws A {@link BackendUnavailableError} if no backend is enabled or the
|
|
1606
|
+
* configured backend cannot be built.
|
|
1607
|
+
* @public
|
|
1608
|
+
*/
|
|
1609
|
+
getActiveBackendCapabilities(): Promise<BackendCapabilities>;
|
|
733
1610
|
/**
|
|
734
1611
|
* Store a secret in the configured backend.
|
|
735
1612
|
*
|
|
@@ -737,11 +1614,20 @@ declare class VaultKeeper {
|
|
|
737
1614
|
* `store()` method. If a secret with the same name already exists, it is
|
|
738
1615
|
* overwritten.
|
|
739
1616
|
*
|
|
740
|
-
* @param name - Identifier for the secret.
|
|
1617
|
+
* @param name - Identifier for the secret. Must not contain `':'` — that
|
|
1618
|
+
* character is reserved for the internal `signing-key:` namespace, so a
|
|
1619
|
+
* secret name can never collide with a signing key.
|
|
741
1620
|
* @param value - The secret value to store.
|
|
1621
|
+
* @param options - Optional {@link PresenceRequirementOptions}. When
|
|
1622
|
+
* `requirePresencePerUse` is set, the store is refused with a
|
|
1623
|
+
* {@link NotCapableError} before the backend is touched unless the active
|
|
1624
|
+
* backend forces a fresh per-use human action.
|
|
1625
|
+
* @throws {VaultError} If `name` is empty or contains `':'`.
|
|
1626
|
+
* @throws {@link NotCapableError} If `options.requirePresencePerUse` is set
|
|
1627
|
+
* and the active backend is not presence-per-use capable.
|
|
742
1628
|
* @public
|
|
743
1629
|
*/
|
|
744
|
-
store(name: string, value: string): Promise<void>;
|
|
1630
|
+
store(name: string, value: string, options?: PresenceRequirementOptions): Promise<void>;
|
|
745
1631
|
/**
|
|
746
1632
|
* Delete a secret from the configured backend.
|
|
747
1633
|
*
|
|
@@ -749,17 +1635,80 @@ declare class VaultKeeper {
|
|
|
749
1635
|
* `delete()` method.
|
|
750
1636
|
*
|
|
751
1637
|
* @param name - Identifier for the secret to delete.
|
|
1638
|
+
* @param options - Optional {@link PresenceRequirementOptions}. When
|
|
1639
|
+
* `requirePresencePerUse` is set, the delete is refused with a
|
|
1640
|
+
* {@link NotCapableError} before the backend is touched unless the active
|
|
1641
|
+
* backend forces a fresh per-use human action.
|
|
1642
|
+
* @throws {@link NotCapableError} If `options.requirePresencePerUse` is set
|
|
1643
|
+
* and the active backend is not presence-per-use capable.
|
|
1644
|
+
* @public
|
|
1645
|
+
*/
|
|
1646
|
+
delete(name: string, options?: PresenceRequirementOptions): Promise<void>;
|
|
1647
|
+
/**
|
|
1648
|
+
* Check whether a secret exists in the active backend, without retrieving
|
|
1649
|
+
* its value, minting a token, or touching the TOFU trust manifest.
|
|
1650
|
+
*
|
|
1651
|
+
* @remarks
|
|
1652
|
+
* This is a lightweight precondition check intended to run before any
|
|
1653
|
+
* interactive or trust-gating logic (e.g. `exec`'s caller-approval prompt),
|
|
1654
|
+
* so a nonexistent secret is reported immediately and unambiguously instead
|
|
1655
|
+
* of being masked by an unrelated approval failure — see issue #69.
|
|
1656
|
+
*
|
|
1657
|
+
* @param name - Identifier for the secret.
|
|
1658
|
+
* @returns `true` if the secret exists, `false` otherwise.
|
|
752
1659
|
* @public
|
|
753
1660
|
*/
|
|
754
|
-
|
|
1661
|
+
secretExists(name: string): Promise<boolean>;
|
|
755
1662
|
/**
|
|
756
1663
|
* Read a stored secret from the backend and mint a JWE token that encapsulates it.
|
|
757
1664
|
*
|
|
758
|
-
* @
|
|
759
|
-
*
|
|
1665
|
+
* @remarks
|
|
1666
|
+
* `setup()` requires an explicit executable-trust decision — it has no
|
|
1667
|
+
* default and never silently skips verification. Pass `executablePath` (the
|
|
1668
|
+
* calling executable's real path) to run trust-on-first-use verification, or
|
|
1669
|
+
* `skipTrust: true` to deliberately skip it in development. The {@link SetupOptions}
|
|
1670
|
+
* type enforces this choice at compile time (exactly one, and the options
|
|
1671
|
+
* argument is required); {@link ExecutableTrustRequiredError} is the runtime
|
|
1672
|
+
* backstop for untyped callers.
|
|
1673
|
+
*
|
|
1674
|
+
* **Seeing a compile error here?** A bare `vault.setup('NAME')` or
|
|
1675
|
+
* `vault.setup('NAME', {})` fails to typecheck (e.g. TS2554 "Expected 2
|
|
1676
|
+
* arguments, but got 1" or TS2345 "Argument … is not assignable") precisely
|
|
1677
|
+
* because the mandatory trust choice is missing. The fix is to add **exactly
|
|
1678
|
+
* one** of `executablePath: '<path>'` (verify the caller — production) or
|
|
1679
|
+
* `skipTrust: true` (skip verification — development only). Supplying both
|
|
1680
|
+
* fails to typecheck for the same reason.
|
|
1681
|
+
*
|
|
1682
|
+
* @example
|
|
1683
|
+
* ```ts
|
|
1684
|
+
* // Production: bind the token to a STABLE executable so a swapped binary is
|
|
1685
|
+
* // rejected. Point executablePath at a released binary, or process.execPath
|
|
1686
|
+
* // to trust the Node runtime. Do NOT use process.argv[1] for a compiled entry
|
|
1687
|
+
* // point — its hash changes on every rebuild, so the next setup() after a
|
|
1688
|
+
* // recompile throws IdentityMismatchError (use setDevelopmentMode or
|
|
1689
|
+
* // skipTrust for a frequently-rebuilt local caller).
|
|
1690
|
+
* const jwe = await vault.setup('MY_API_KEY', { executablePath: '/usr/local/bin/my-tool' })
|
|
1691
|
+
*
|
|
1692
|
+
* // Local development: skip verification so rebuilds don't reject the caller.
|
|
1693
|
+
* const devJwe = await vault.setup('MY_API_KEY', { skipTrust: true })
|
|
1694
|
+
* ```
|
|
1695
|
+
*
|
|
1696
|
+
* @param secretName - Identifier for the secret. Must not contain `':'` (the
|
|
1697
|
+
* reserved `signing-key:` namespace separator).
|
|
1698
|
+
* @param options - Setup options; must carry exactly one of `executablePath`
|
|
1699
|
+
* or `skipTrust: true`
|
|
760
1700
|
* @returns Compact JWE string
|
|
1701
|
+
* @throws {VaultError} If `secretName` is empty or contains `':'`.
|
|
1702
|
+
* @throws {@link ExecutableTrustRequiredError} If neither `executablePath`
|
|
1703
|
+
* nor `skipTrust: true` is provided, if both are, or if `executablePath` is
|
|
1704
|
+
* the retired legacy `'dev'` opt-out sentinel (use `skipTrust: true`).
|
|
1705
|
+
* @throws {@link IdentityMismatchError} If `executablePath`'s current hash no
|
|
1706
|
+
* longer matches a previously approved value (TOFU conflict).
|
|
1707
|
+
* @throws {@link FilesystemError} If `executablePath` cannot be read or hashed
|
|
1708
|
+
* for verification, or the trust manifest cannot be read or written while
|
|
1709
|
+
* recording the executable.
|
|
761
1710
|
*/
|
|
762
|
-
setup(secretName: string, options
|
|
1711
|
+
setup(secretName: string, options: SetupOptions): Promise<string>;
|
|
763
1712
|
/**
|
|
764
1713
|
* Decrypt a JWE, validate claims, verify executable identity, and return
|
|
765
1714
|
* an opaque CapabilityToken.
|
|
@@ -775,40 +1724,65 @@ declare class VaultKeeper {
|
|
|
775
1724
|
vaultResponse: VaultResponse;
|
|
776
1725
|
}>;
|
|
777
1726
|
/**
|
|
778
|
-
* Execute a delegated HTTP fetch, injecting
|
|
1727
|
+
* Execute a delegated HTTP fetch, injecting secrets from the token(s).
|
|
779
1728
|
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
* is executed. The raw secret is never exposed in the return value.
|
|
1729
|
+
* **Single token:** every `{{secret}}` placeholder in `request.url`,
|
|
1730
|
+
* `request.headers`, and `request.body` is replaced with the secret value.
|
|
783
1731
|
*
|
|
784
|
-
* @
|
|
785
|
-
*
|
|
786
|
-
*
|
|
1732
|
+
* **Token map ({@link SecretTokenMap}):** every `{{secret:name}}` placeholder
|
|
1733
|
+
* is replaced with the secret from the corresponding named token.
|
|
1734
|
+
*
|
|
1735
|
+
* The raw secret is never exposed in the return value.
|
|
1736
|
+
*
|
|
1737
|
+
* @param token - A single `CapabilityToken` or a `SecretTokenMap` mapping
|
|
1738
|
+
* names to tokens obtained from `authorize()`.
|
|
1739
|
+
* @param request - The fetch request template with placeholders.
|
|
787
1740
|
* @returns The `Response` from the underlying `fetch()` call, together with
|
|
788
1741
|
* the vault metadata (`vaultResponse`).
|
|
789
|
-
* @throws {
|
|
790
|
-
* instance.
|
|
1742
|
+
* @throws {AuthorizationDeniedError} If any token is invalid or was not
|
|
1743
|
+
* created by this vault instance.
|
|
1744
|
+
* @throws {VaultError} If a named placeholder references an unknown
|
|
1745
|
+
* secret name.
|
|
1746
|
+
* @throws {FetchError} If the URL is malformed or the underlying network
|
|
1747
|
+
* request fails.
|
|
791
1748
|
*/
|
|
792
|
-
fetch(token: CapabilityToken, request: FetchRequest): Promise<{
|
|
1749
|
+
fetch(token: CapabilityToken | SecretTokenMap, request: FetchRequest): Promise<{
|
|
793
1750
|
response: Response;
|
|
794
1751
|
vaultResponse: VaultResponse;
|
|
795
1752
|
}>;
|
|
796
1753
|
/**
|
|
797
|
-
* Execute a delegated command, injecting
|
|
1754
|
+
* Execute a delegated command, injecting secrets from the token(s).
|
|
798
1755
|
*
|
|
799
|
-
*
|
|
800
|
-
*
|
|
801
|
-
* The raw secret is never exposed in the return value.
|
|
1756
|
+
* **Single token:** every `{{secret}}` placeholder in `request.env` values
|
|
1757
|
+
* is replaced with the secret value.
|
|
802
1758
|
*
|
|
803
|
-
* @
|
|
804
|
-
*
|
|
805
|
-
*
|
|
1759
|
+
* **Token map ({@link SecretTokenMap}):** every `{{secret:name}}` placeholder
|
|
1760
|
+
* is replaced with the secret from the corresponding named token.
|
|
1761
|
+
*
|
|
1762
|
+
* Secret placeholders are not supported in `request.command` or
|
|
1763
|
+
* `request.args` — process arguments are visible to other processes via
|
|
1764
|
+
* `ps` and often collected in logs and telemetry.
|
|
1765
|
+
*
|
|
1766
|
+
* The raw secret is never exposed in the return value: by default the
|
|
1767
|
+
* captured `stdout`/`stderr` is scrubbed of every injected secret value
|
|
1768
|
+
* (replaced with `[REDACTED]`), so a command that echoes the secret does not
|
|
1769
|
+
* leak it back. Pass `request.redact = false` to receive raw, unredacted
|
|
1770
|
+
* output — only when a caller genuinely needs it, since that forfeits the
|
|
1771
|
+
* guarantee.
|
|
1772
|
+
*
|
|
1773
|
+
* @param token - A single `CapabilityToken` or a `SecretTokenMap` mapping
|
|
1774
|
+
* names to tokens obtained from `authorize()`.
|
|
1775
|
+
* @param request - The exec request template with placeholders. Set
|
|
1776
|
+
* `redact: false` to opt out of output redaction.
|
|
806
1777
|
* @returns The command result (`stdout`, `stderr`, `exitCode`) together with
|
|
807
1778
|
* the vault metadata (`vaultResponse`).
|
|
808
|
-
* @throws {
|
|
809
|
-
* instance.
|
|
1779
|
+
* @throws {AuthorizationDeniedError} If any token is invalid or was not
|
|
1780
|
+
* created by this vault instance.
|
|
1781
|
+
* @throws {ExecError} If the command cannot be started (e.g. ENOENT),
|
|
1782
|
+
* a placeholder references an unknown secret name, or a secret
|
|
1783
|
+
* placeholder appears in the `command` or `args` field.
|
|
810
1784
|
*/
|
|
811
|
-
exec(token: CapabilityToken, request: ExecRequest): Promise<{
|
|
1785
|
+
exec(token: CapabilityToken | SecretTokenMap, request: ExecRequest): Promise<{
|
|
812
1786
|
result: ExecResult;
|
|
813
1787
|
vaultResponse: VaultResponse;
|
|
814
1788
|
}>;
|
|
@@ -821,49 +1795,108 @@ declare class VaultKeeper {
|
|
|
821
1795
|
*
|
|
822
1796
|
* @param token - A `CapabilityToken` obtained from `authorize()`.
|
|
823
1797
|
* @returns A `SecretAccessor` that can be read exactly once.
|
|
824
|
-
* @throws {
|
|
825
|
-
* instance
|
|
1798
|
+
* @throws {AuthorizationDeniedError} If `token` is invalid or was not created
|
|
1799
|
+
* by this vault instance, or if it is a signing-key token — a signing key
|
|
1800
|
+
* carries no secret and cannot be read through `getSecret()` (use `sign()`).
|
|
826
1801
|
*/
|
|
827
1802
|
getSecret(token: CapabilityToken): SecretAccessor;
|
|
828
1803
|
/**
|
|
829
|
-
*
|
|
1804
|
+
* Enroll a new signing keypair under `name` in the active backend.
|
|
830
1805
|
*
|
|
831
|
-
* The
|
|
832
|
-
*
|
|
833
|
-
*
|
|
834
|
-
*
|
|
1806
|
+
* The keypair is generated and stored entirely backend-side (see
|
|
1807
|
+
* {@link SigningBackend}); the private key never enters vault claims, a
|
|
1808
|
+
* capability token, or the caller's process. Signing keys occupy a namespace
|
|
1809
|
+
* distinct from secrets, so a signing key and a secret can share a name
|
|
1810
|
+
* without colliding, and a signing key can never be read as a secret.
|
|
835
1811
|
*
|
|
836
|
-
* @param
|
|
837
|
-
*
|
|
838
|
-
* @
|
|
839
|
-
*
|
|
840
|
-
* @throws {
|
|
841
|
-
*
|
|
842
|
-
* @throws {
|
|
843
|
-
*
|
|
844
|
-
|
|
845
|
-
|
|
1812
|
+
* @param name - Caller-facing signing key name. Must not contain `':'` (the
|
|
1813
|
+
* `signing-key:` namespace separator).
|
|
1814
|
+
* @param algorithm - The JOSE signing algorithm (currently only `'EdDSA'`).
|
|
1815
|
+
* @returns The public half of the newly enrolled key.
|
|
1816
|
+
* @throws {SigningNotSupportedError} If the active backend cannot sign.
|
|
1817
|
+
* @throws {InvalidAlgorithmError} If `algorithm` is not supported.
|
|
1818
|
+
* @throws {SigningKeyAlreadyExistsError} If a signing key already exists under `name`.
|
|
1819
|
+
* @throws {VaultError} If `name` is empty or contains `':'`.
|
|
1820
|
+
* @public
|
|
1821
|
+
*/
|
|
1822
|
+
createSigningKey(name: string, algorithm: SigningAlgorithm): Promise<SigningPublicKey>;
|
|
1823
|
+
/**
|
|
1824
|
+
* Export the SPKI PEM public key for the signing key named `name`.
|
|
1825
|
+
*
|
|
1826
|
+
* @param name - Caller-facing signing key name. Must not contain `':'`.
|
|
1827
|
+
* @returns The public key material (SPKI PEM, algorithm, kid).
|
|
1828
|
+
* @throws {SigningNotSupportedError} If the active backend cannot sign.
|
|
1829
|
+
* @throws {SigningKeyNotFoundError} If no signing key exists under `name`.
|
|
1830
|
+
* @throws {VaultError} If `name` is empty or contains `':'`.
|
|
1831
|
+
* @public
|
|
1832
|
+
*/
|
|
1833
|
+
exportPublicKey(name: string): Promise<SigningPublicKey>;
|
|
1834
|
+
/**
|
|
1835
|
+
* Mint a signing-key capability token for the key named `name`.
|
|
1836
|
+
*
|
|
1837
|
+
* The returned token carries only `{ kid, backendRef, keyType: 'signing-key' }`
|
|
1838
|
+
* — never any key material — and is accepted only by {@link VaultKeeper.sign}.
|
|
1839
|
+
* Passing it to `getSecret`/`fetch`/`exec` is rejected.
|
|
1840
|
+
*
|
|
1841
|
+
* @param name - Caller-facing signing key name. Must not contain `':'`.
|
|
1842
|
+
* @returns An opaque {@link CapabilityToken} usable with `sign()`.
|
|
1843
|
+
* @throws {SigningNotSupportedError} If the active backend cannot sign.
|
|
1844
|
+
* @throws {SigningKeyNotFoundError} If no signing key exists under `name`.
|
|
1845
|
+
* @throws {VaultError} If `name` is empty or contains `':'`.
|
|
1846
|
+
* @public
|
|
1847
|
+
*/
|
|
1848
|
+
authorizeSigningKey(name: string): Promise<CapabilityToken>;
|
|
1849
|
+
/**
|
|
1850
|
+
* Sign a caller-supplied payload with a signing-key capability token.
|
|
1851
|
+
*
|
|
1852
|
+
* The signature is produced backend-side via {@link SigningBackend.signWithKey}
|
|
1853
|
+
* — the private key never leaves the backend and never appears in the token,
|
|
1854
|
+
* the claims, or this process. The result is a detached-payload Compact JWS
|
|
1855
|
+
* (RFC 7515 §7.2.2 + RFC 7797 `b64:false`, `crit:["b64"]`, `alg:EdDSA`) that
|
|
1856
|
+
* any standards-compliant JOSE library can verify given the payload and the
|
|
1857
|
+
* public key.
|
|
1858
|
+
*
|
|
1859
|
+
* @param token - A signing-key `CapabilityToken` from {@link VaultKeeper.authorizeSigningKey}.
|
|
1860
|
+
* @param request - The payload to sign.
|
|
1861
|
+
* @param options - Optional {@link PresenceRequirementOptions}. When
|
|
1862
|
+
* `requirePresencePerUse` is set, the signature is refused with a
|
|
1863
|
+
* {@link NotCapableError} before the backend is touched unless the active
|
|
1864
|
+
* backend forces a fresh per-use human action. When capable, a fresh
|
|
1865
|
+
* backend `signWithKey` round-trip is performed for this call — no cached
|
|
1866
|
+
* key material can satisfy it (the private key never leaves the backend).
|
|
1867
|
+
* @returns The detached compact JWS and vault metadata.
|
|
1868
|
+
* @throws {AuthorizationDeniedError} If `token` is invalid or is not a
|
|
1869
|
+
* signing-key token (e.g. an ordinary secret token).
|
|
1870
|
+
* @throws {SigningNotSupportedError} If the active backend cannot sign.
|
|
1871
|
+
* @throws {SigningKeyNotFoundError} If the referenced key no longer exists.
|
|
1872
|
+
* @throws {@link NotCapableError} If `options.requirePresencePerUse` is set
|
|
1873
|
+
* and the active backend is not presence-per-use capable.
|
|
1874
|
+
* @public
|
|
1875
|
+
*/
|
|
1876
|
+
sign(token: CapabilityToken, request: SignRequest, options?: PresenceRequirementOptions): Promise<{
|
|
846
1877
|
result: SignResult;
|
|
847
1878
|
vaultResponse: VaultResponse;
|
|
848
1879
|
}>;
|
|
849
1880
|
/**
|
|
850
|
-
* Verify a
|
|
851
|
-
*
|
|
852
|
-
* This is a static method — no VaultKeeper instance, secrets, or
|
|
853
|
-
* capability tokens are required. It is safe to call from CI or any
|
|
854
|
-
* context that has access to public key material.
|
|
1881
|
+
* Verify a detached-payload Compact JWS against a public key — fully offline.
|
|
855
1882
|
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
1883
|
+
* This is a static, asynchronous method: no VaultKeeper instance, backend,
|
|
1884
|
+
* config, or capability token is required, so it is safe to call in CI or any
|
|
1885
|
+
* context holding only public material.
|
|
858
1886
|
*
|
|
859
|
-
*
|
|
860
|
-
*
|
|
1887
|
+
* Returns `false` for a signature that does not verify — a tampered payload,
|
|
1888
|
+
* the wrong key, or a structurally malformed JWS. It throws
|
|
1889
|
+
* {@link InvalidKeyMaterialError} only when the public key itself is not
|
|
1890
|
+
* parseable (or a private key was supplied) — an operational fault distinct
|
|
1891
|
+
* from a bad signature.
|
|
861
1892
|
*
|
|
862
|
-
* @param request - The
|
|
863
|
-
* algorithm override.
|
|
1893
|
+
* @param request - The detached payload, the JWS, and the SPKI PEM public key.
|
|
864
1894
|
* @returns `true` if the signature is valid, `false` otherwise.
|
|
1895
|
+
* @throws {InvalidKeyMaterialError} If `request.publicKey` is not parseable
|
|
1896
|
+
* SPKI public key material.
|
|
1897
|
+
* @public
|
|
865
1898
|
*/
|
|
866
|
-
static verify(request: VerifyRequest): boolean
|
|
1899
|
+
static verify(request: VerifyRequest): Promise<boolean>;
|
|
867
1900
|
/**
|
|
868
1901
|
* Rotate the current encryption key.
|
|
869
1902
|
*
|
|
@@ -897,6 +1930,202 @@ declare class VaultKeeper {
|
|
|
897
1930
|
* to remove it.
|
|
898
1931
|
*/
|
|
899
1932
|
setDevelopmentMode(executablePath: string, enabled: boolean): Promise<void>;
|
|
1933
|
+
/**
|
|
1934
|
+
* Approve an executable for trust-on-first-use by recording its current
|
|
1935
|
+
* SHA-256 hash in the trust manifest.
|
|
1936
|
+
*
|
|
1937
|
+
* After approval, {@link VaultKeeper.setup} and
|
|
1938
|
+
* {@link VaultKeeper.checkExecutableTrust} recognize the executable (matched
|
|
1939
|
+
* by its resolved absolute path and content hash) as trusted, so callers can
|
|
1940
|
+
* skip an interactive approval prompt.
|
|
1941
|
+
*
|
|
1942
|
+
* The operation is idempotent: approving the same, unchanged executable more
|
|
1943
|
+
* than once leaves a single manifest entry.
|
|
1944
|
+
*
|
|
1945
|
+
* @param executablePath - Path to the executable to approve. Resolved to an
|
|
1946
|
+
* absolute path before hashing and recording.
|
|
1947
|
+
* @returns The recorded trust status (always `trusted: true`).
|
|
1948
|
+
* @throws {FilesystemError} If the executable does not exist or cannot be read.
|
|
1949
|
+
* @public
|
|
1950
|
+
*/
|
|
1951
|
+
approveExecutable(executablePath: string): Promise<ExecutableTrustStatus>;
|
|
1952
|
+
/**
|
|
1953
|
+
* Check whether an executable is trusted according to the trust manifest,
|
|
1954
|
+
* without modifying the manifest.
|
|
1955
|
+
*
|
|
1956
|
+
* This is a read-only probe. Unlike {@link VaultKeeper.setup}, it never
|
|
1957
|
+
* records a hash, so it can be used to decide whether an interactive approval
|
|
1958
|
+
* prompt is required before proceeding.
|
|
1959
|
+
*
|
|
1960
|
+
* @param executablePath - Path to the executable to check. Resolved to an
|
|
1961
|
+
* absolute path before hashing and lookup.
|
|
1962
|
+
* @returns The current trust status. `trusted` is `true` only when the
|
|
1963
|
+
* executable's current hash matches an approved manifest entry.
|
|
1964
|
+
* @throws {FilesystemError} If the executable does not exist or cannot be read.
|
|
1965
|
+
* @public
|
|
1966
|
+
*/
|
|
1967
|
+
checkExecutableTrust(executablePath: string): Promise<ExecutableTrustStatus>;
|
|
900
1968
|
}
|
|
901
1969
|
|
|
902
|
-
|
|
1970
|
+
/**
|
|
1971
|
+
* Secret redaction for captured process output.
|
|
1972
|
+
*
|
|
1973
|
+
* The single source of truth for how an injected secret value is scrubbed from
|
|
1974
|
+
* text. Both the library's delegated `exec()` (which buffers full output) and
|
|
1975
|
+
* the CLI's streaming `RedactingStream` (which redacts live) route their
|
|
1976
|
+
* substitution through {@link redactSecrets}, so the redaction behavior can
|
|
1977
|
+
* never drift between the two surfaces.
|
|
1978
|
+
*/
|
|
1979
|
+
/**
|
|
1980
|
+
* The token substituted for a redacted secret value.
|
|
1981
|
+
*
|
|
1982
|
+
* @public
|
|
1983
|
+
*/
|
|
1984
|
+
declare const REDACTED = "[REDACTED]";
|
|
1985
|
+
/**
|
|
1986
|
+
* Replace every occurrence of each secret value in `text` with `replacement`.
|
|
1987
|
+
*
|
|
1988
|
+
* This is used to scrub captured child-process `stdout`/`stderr` so a secret
|
|
1989
|
+
* injected into a delegated command never surfaces in the returned output.
|
|
1990
|
+
*
|
|
1991
|
+
* Before redacting, the secret set is normalized so masking is complete and
|
|
1992
|
+
* order-independent:
|
|
1993
|
+
*
|
|
1994
|
+
* - **Empty/whitespace-only values are dropped.** An empty string matches
|
|
1995
|
+
* between every character, and a whitespace-only value matches ubiquitous
|
|
1996
|
+
* whitespace, so redacting either would blank the text rather than a secret.
|
|
1997
|
+
* - **Duplicates are removed** so each distinct value is processed once.
|
|
1998
|
+
* - **Longer values are redacted first.** If one secret is a substring or
|
|
1999
|
+
* prefix of another — common for keys that share a prefix — redacting the
|
|
2000
|
+
* shorter one first would leave the longer secret's remaining suffix visible
|
|
2001
|
+
* (redacting `"abc"` before `"abc123"` yields `"[REDACTED]123"`, leaking
|
|
2002
|
+
* `"123"`). Sorting by length descending guarantees each longer value is
|
|
2003
|
+
* fully masked before any shorter substring pass runs.
|
|
2004
|
+
*
|
|
2005
|
+
* Both arguments are treated fully literally: the secret is matched as a
|
|
2006
|
+
* plain substring (never as a regular expression), and `replacement` is
|
|
2007
|
+
* inserted verbatim — `String.prototype.replaceAll`'s `$`-substitution
|
|
2008
|
+
* patterns are disabled, since a replacement containing `$&` would otherwise
|
|
2009
|
+
* re-expand to the matched secret and silently defeat the redaction.
|
|
2010
|
+
*
|
|
2011
|
+
* @param text - The text to scrub.
|
|
2012
|
+
* @param secrets - The secret values to redact. Every non-empty, non-whitespace
|
|
2013
|
+
* value has all of its occurrences replaced.
|
|
2014
|
+
* @param replacement - The token to substitute for each occurrence, inserted
|
|
2015
|
+
* literally. Defaults to {@link REDACTED}.
|
|
2016
|
+
* @returns `text` with every occurrence of each redactable secret replaced.
|
|
2017
|
+
*
|
|
2018
|
+
* @public
|
|
2019
|
+
*/
|
|
2020
|
+
declare function redactSecrets(text: string, secrets: readonly string[], replacement?: string): string;
|
|
2021
|
+
|
|
2022
|
+
/**
|
|
2023
|
+
* Configuration loading, validation, and defaults for vaultkeeper.
|
|
2024
|
+
*/
|
|
2025
|
+
|
|
2026
|
+
/**
|
|
2027
|
+
* Return the platform-appropriate default config directory, ignoring the
|
|
2028
|
+
* `VAULTKEEPER_CONFIG_DIR` environment variable.
|
|
2029
|
+
*
|
|
2030
|
+
* This is the location vaultkeeper falls back to when neither a CLI
|
|
2031
|
+
* `--config-dir` flag nor the `VAULTKEEPER_CONFIG_DIR` environment variable
|
|
2032
|
+
* is set: `%APPDATA%/vaultkeeper` on Windows, `~/.config/vaultkeeper`
|
|
2033
|
+
* elsewhere.
|
|
2034
|
+
*
|
|
2035
|
+
* Unlike `getDefaultConfigDir`, this deliberately does not consult
|
|
2036
|
+
* `VAULTKEEPER_CONFIG_DIR`, so a caller can tell whether an active config
|
|
2037
|
+
* directory differs from the machine default even when that difference came
|
|
2038
|
+
* from the environment variable — for example, to decide whether a printed
|
|
2039
|
+
* remediation command must carry an explicit `--config-dir` so it still
|
|
2040
|
+
* targets the right file in a fresh shell that does not have the environment
|
|
2041
|
+
* variable set.
|
|
2042
|
+
*
|
|
2043
|
+
* @public
|
|
2044
|
+
*/
|
|
2045
|
+
declare function getPlatformDefaultConfigDir(): string;
|
|
2046
|
+
/**
|
|
2047
|
+
* Return the platform-appropriate default config directory.
|
|
2048
|
+
*
|
|
2049
|
+
* Resolution order: the `VAULTKEEPER_CONFIG_DIR` environment variable, then
|
|
2050
|
+
* the platform default (see `getPlatformDefaultConfigDir`). Consumers that
|
|
2051
|
+
* also support a higher-precedence override (e.g. a CLI flag) should check
|
|
2052
|
+
* that first and only fall back to this function when no override was
|
|
2053
|
+
* supplied.
|
|
2054
|
+
*
|
|
2055
|
+
* @public
|
|
2056
|
+
*/
|
|
2057
|
+
declare function getDefaultConfigDir(): string;
|
|
2058
|
+
/**
|
|
2059
|
+
* The backend type vaultkeeper uses by default when no backend is explicitly
|
|
2060
|
+
* configured — the **`file`** backend, on every platform.
|
|
2061
|
+
*
|
|
2062
|
+
* @remarks
|
|
2063
|
+
* The zero-config default is deliberately the portable, self-contained
|
|
2064
|
+
* AES-256-GCM encrypted file backend rather than the platform-native OS
|
|
2065
|
+
* credential store. This guarantees that a bare {@link VaultKeeper.init} — or a
|
|
2066
|
+
* `vaultkeeper config init` (run via the separate `@vaultkeeper/cli` package)
|
|
2067
|
+
* with no `--backend` flag — can never silently write a secret into the
|
|
2068
|
+
* user's real login keychain (or Windows DPAPI store) before they have
|
|
2069
|
+
* chosen to. It also matches the WASM SDK, which always uses the file
|
|
2070
|
+
* backend.
|
|
2071
|
+
*
|
|
2072
|
+
* The OS-native store is still available as an explicit opt-in: pass
|
|
2073
|
+
* `--backend keychain` (macOS) / `--backend dpapi` (Windows) to
|
|
2074
|
+
* `vaultkeeper config init` (via `@vaultkeeper/cli`), or set
|
|
2075
|
+
* `{ type: 'keychain' | 'dpapi' }` directly in a config object/file. Use
|
|
2076
|
+
* {@link platformNativeBackendType} to discover which native store the current
|
|
2077
|
+
* platform offers.
|
|
2078
|
+
*
|
|
2079
|
+
* @returns The zero-config default backend type identifier (`'file'`).
|
|
2080
|
+
* @public
|
|
2081
|
+
*/
|
|
2082
|
+
declare function defaultBackendType(): string;
|
|
2083
|
+
/**
|
|
2084
|
+
* Resolve the OS-native credential store type for the current platform.
|
|
2085
|
+
*
|
|
2086
|
+
* @remarks
|
|
2087
|
+
* This is **not** the zero-config default — {@link defaultBackendType} (always
|
|
2088
|
+
* `'file'`) is. This function reports which platform-native store a user can
|
|
2089
|
+
* explicitly opt into (e.g. via `vaultkeeper config init --backend keychain`
|
|
2090
|
+
* from the separate `@vaultkeeper/cli` package, or `{ type: 'keychain' }` in
|
|
2091
|
+
* a config object/file passed directly to this library):
|
|
2092
|
+
*
|
|
2093
|
+
* - **macOS** → `keychain` (macOS Keychain)
|
|
2094
|
+
* - **Windows** → `dpapi` (Windows DPAPI)
|
|
2095
|
+
* - **Linux** → `secret-tool` (Secret Service via `libsecret`; opting in
|
|
2096
|
+
* requires the `libsecret-tools` package)
|
|
2097
|
+
* - **any other platform** → `file` (no built-in native store integration, so
|
|
2098
|
+
* the portable AES-256-GCM encrypted file backend is the only option)
|
|
2099
|
+
*
|
|
2100
|
+
* Use it to tell the user which native store is available on their platform, or
|
|
2101
|
+
* to label the opt-in. It never affects what an unconfigured vault resolves to
|
|
2102
|
+
* — that is always {@link defaultBackendType} (`file`).
|
|
2103
|
+
*
|
|
2104
|
+
* @returns The OS-native backend type identifier for the current platform.
|
|
2105
|
+
* @public
|
|
2106
|
+
*/
|
|
2107
|
+
declare function platformNativeBackendType(): string;
|
|
2108
|
+
/**
|
|
2109
|
+
* Load the vaultkeeper config from disk, falling back to platform defaults
|
|
2110
|
+
* only when the config file is missing (`ENOENT`).
|
|
2111
|
+
*
|
|
2112
|
+
* Any other read failure (e.g. `EACCES`, `EISDIR`) is a genuinely broken or
|
|
2113
|
+
* unreadable config and is rethrown as a {@link FilesystemError} rather than
|
|
2114
|
+
* silently defaulted — silently defaulting on a permissions error would hide
|
|
2115
|
+
* the problem from `doctor` and `config show` (issue #68). A present file
|
|
2116
|
+
* that fails to parse as JSON throws {@link ConfigParseError}; a present file
|
|
2117
|
+
* that parses but fails schema validation throws {@link ConfigValidationError}.
|
|
2118
|
+
* All three error messages include the config file path and a remediation
|
|
2119
|
+
* hint naming `vaultkeeper config init --force` (via the separate
|
|
2120
|
+
* `@vaultkeeper/cli` package) as well as the JS-API alternative of repairing
|
|
2121
|
+
* or replacing the config directly — the supported recovery paths for an
|
|
2122
|
+
* existing-but-broken config (issues #97, #100).
|
|
2123
|
+
*
|
|
2124
|
+
* @param configDir - Directory containing config.json. Defaults to
|
|
2125
|
+
* `getDefaultConfigDir()`, which itself honors `VAULTKEEPER_CONFIG_DIR`
|
|
2126
|
+
* before falling back to the platform-appropriate path.
|
|
2127
|
+
* @public
|
|
2128
|
+
*/
|
|
2129
|
+
declare function loadConfig(configDir?: string): Promise<VaultConfig>;
|
|
2130
|
+
|
|
2131
|
+
export { AccessorConsumedError, AuthorizationDeniedError, type BackendCapabilities, type BackendConfig, type BackendFactory, BackendLockedError, BackendRegistry, type BackendSetupFactory, BackendUnavailableError, CapabilityToken, ConfigParseError, ConfigValidationError, DecryptionError, DeviceNotPresentError, ExecError, type ExecRequest, type ExecResult, ExecutableTrustRequiredError, type ExecutableTrustStatus, FetchError, type FetchRequest, FilesystemError, IdentityMismatchError, InvalidAlgorithmError, InvalidKeyMaterialError, InvalidTokenError, KeyRevokedError, KeyRotatedError, type KeyStatus, type ListableBackend, NotCapableError, type Platform, PluginNotFoundError, type PreflightCheck, type PreflightCheckError, type PreflightCheckErrorKind, type PreflightCheckStatus, type PreflightResult, type PresenceCapableBackend, PresenceDeclinedError, type PresenceOperation, type PresenceRequirementOptions, PresenceTimeoutError, REDACTED, RotationInProgressError, type RunDoctorOptions, type ScopedPreflightCheck, type SecretAccessor, type SecretBackend, SecretNotFoundError, type SecretTokenMap, type SetupChoice, SetupError, type SetupOptions, type SetupOptionsBase, type SetupQuestion, type SetupResult, type SignRequest, type SignResult, type SigningAlgorithm, type SigningBackend, SigningKeyAlreadyExistsError, SigningKeyNotFoundError, SigningNotSupportedError, type SigningPublicKey, TokenExpiredError, TokenRevokedError, type TrustTier, UnknownBackendTypeError, UsageLimitExceededError, type VaultConfig, VaultError, VaultKeeper, type VaultKeeperOptions, type VaultResponse, type VerifyRequest, defaultBackendType, getBackendCapabilities, getDefaultConfigDir, getPlatformDefaultConfigDir, isListableBackend, isPresenceCapableBackend, isSigningBackend, loadConfig, platformNativeBackendType, redactSecrets, runDoctor };
|