vaultkeeper 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -134,6 +134,21 @@ declare class IdentityMismatchError extends VaultError {
134
134
  readonly currentHash: string;
135
135
  constructor(message: string, previousHash: string, currentHash: string);
136
136
  }
137
+ /**
138
+ * Thrown when a caller requests a signing/verification algorithm that is not
139
+ * in the allowed set (e.g. `'md5'`).
140
+ */
141
+ declare class InvalidAlgorithmError extends VaultError {
142
+ /**
143
+ * The algorithm that was requested.
144
+ */
145
+ readonly algorithm: string;
146
+ /**
147
+ * The set of algorithms that are allowed.
148
+ */
149
+ readonly allowed: string[];
150
+ constructor(message: string, algorithm: string, allowed: string[]);
151
+ }
137
152
  /**
138
153
  * Thrown during initialization when a required system dependency (e.g. OpenSSL
139
154
  * or a native credential helper) is missing or incompatible.
@@ -377,6 +392,8 @@ interface BackendConfig {
377
392
  plugin?: boolean | undefined;
378
393
  /** Filesystem path used by file-based backends. */
379
394
  path?: string | undefined;
395
+ /** Backend-specific options collected during interactive setup. */
396
+ options?: Record<string, string> | undefined;
380
397
  }
381
398
 
382
399
  /**
@@ -449,6 +466,47 @@ interface ListableBackend extends SecretBackend {
449
466
  */
450
467
  declare function isListableBackend(backend: SecretBackend): backend is ListableBackend;
451
468
 
469
+ /**
470
+ * Types for the backend setup protocol.
471
+ *
472
+ * @packageDocumentation
473
+ */
474
+ /**
475
+ * A question yielded by a backend setup generator.
476
+ * @public
477
+ */
478
+ interface SetupQuestion {
479
+ /** Machine key for BackendConfig.options */
480
+ readonly key: string;
481
+ /** Human-readable prompt */
482
+ readonly prompt: string;
483
+ /** Present for selection questions; when absent, the answer is a free-form string. */
484
+ readonly choices?: readonly SetupChoice[];
485
+ }
486
+ /**
487
+ * A choice within a setup question.
488
+ * @public
489
+ */
490
+ interface SetupChoice {
491
+ /** Persisted value */
492
+ readonly value: string;
493
+ /** Display label */
494
+ readonly label: string;
495
+ }
496
+ /**
497
+ * Result returned when a backend setup generator completes.
498
+ * @public
499
+ */
500
+ interface SetupResult {
501
+ /** Merge into BackendConfig.options */
502
+ readonly options: Record<string, string>;
503
+ }
504
+ /**
505
+ * Factory that creates a backend setup generator.
506
+ * @public
507
+ */
508
+ type BackendSetupFactory = () => AsyncGenerator<SetupQuestion, SetupResult, string>;
509
+
452
510
  /**
453
511
  * Registry for secret backend implementations.
454
512
  *
@@ -469,6 +527,7 @@ declare function isListableBackend(backend: SecretBackend): backend is ListableB
469
527
  */
470
528
  declare class BackendRegistry {
471
529
  private static backends;
530
+ private static setups;
472
531
  /**
473
532
  * Register a backend factory.
474
533
  * @param type - Backend type identifier
@@ -479,7 +538,7 @@ declare class BackendRegistry {
479
538
  * Create a backend instance by type.
480
539
  * @param type - Backend type identifier
481
540
  * @returns A SecretBackend instance
482
- * @throws Error if the backend type is not registered
541
+ * @throws {@link BackendUnavailableError} if the backend type is not registered
483
542
  */
484
543
  static create(type: string): SecretBackend;
485
544
  /**
@@ -500,6 +559,36 @@ declare class BackendRegistry {
500
559
  * @public
501
560
  */
502
561
  static getAvailableTypes(): Promise<string[]>;
562
+ /**
563
+ * Register a setup factory for a backend type.
564
+ * @param type - Backend type identifier
565
+ * @param factory - Factory function that creates a setup generator
566
+ */
567
+ static registerSetup(type: string, factory: BackendSetupFactory): void;
568
+ /**
569
+ * Get the setup factory for a backend type, if one is registered.
570
+ * @param type - Backend type identifier
571
+ * @returns The setup factory, or `undefined` if none is registered
572
+ */
573
+ static getSetup(type: string): BackendSetupFactory | undefined;
574
+ /**
575
+ * Check whether a setup factory is registered for the given backend type.
576
+ * @param type - Backend type identifier
577
+ * @returns `true` if a setup factory is registered
578
+ */
579
+ static hasSetup(type: string): boolean;
580
+ /**
581
+ * Clear all registered backend factories.
582
+ * Intended for use in tests only.
583
+ * @internal
584
+ */
585
+ static clearBackends(): void;
586
+ /**
587
+ * Clear all registered setup factories.
588
+ * Intended for use in tests only.
589
+ * @internal
590
+ */
591
+ static clearSetups(): void;
503
592
  }
504
593
 
505
594
  /**
@@ -645,6 +734,8 @@ declare class VaultKeeper {
645
734
  * with the vault metadata (`vaultResponse`).
646
735
  * @throws {VaultError} If `token` is invalid or was not created by this
647
736
  * vault instance.
737
+ * @throws {InvalidAlgorithmError} If `request.algorithm` is not in the
738
+ * allowed set (e.g. `'md5'`).
648
739
  */
649
740
  sign(token: CapabilityToken, request: SignRequest): Promise<{
650
741
  result: SignResult;
@@ -657,8 +748,11 @@ declare class VaultKeeper {
657
748
  * capability tokens are required. It is safe to call from CI or any
658
749
  * context that has access to public key material.
659
750
  *
660
- * Never throws. Returns `false` for invalid key material, malformed
661
- * signatures, or any verification failure.
751
+ * Returns `false` for invalid key material, malformed signatures, or
752
+ * any verification failure (except disallowed algorithms, which throw).
753
+ *
754
+ * @throws {InvalidAlgorithmError} If `request.algorithm` is not in the
755
+ * allowed set (e.g. `'md5'`).
662
756
  *
663
757
  * @param request - The data, signature, public key, and optional
664
758
  * algorithm override.
@@ -700,4 +794,4 @@ declare class VaultKeeper {
700
794
  setDevelopmentMode(executablePath: string, enabled: boolean): Promise<void>;
701
795
  }
702
796
 
703
- export { AuthorizationDeniedError, type BackendConfig, type BackendFactory, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, type ExecRequest, type ExecResult, type FetchRequest, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, type KeyStatus, type ListableBackend, PluginNotFoundError, type PreflightCheck, type PreflightCheckStatus, type PreflightResult, RotationInProgressError, type SecretAccessor, type SecretBackend, SecretNotFoundError, SetupError, type SetupOptions, type SignRequest, type SignResult, TokenExpiredError, TokenRevokedError, type TrustTier, UsageLimitExceededError, type VaultConfig, VaultError, VaultKeeper, type VaultKeeperOptions, type VaultResponse, type VerifyRequest, isListableBackend };
797
+ export { AuthorizationDeniedError, type BackendConfig, type BackendFactory, BackendLockedError, BackendRegistry, type BackendSetupFactory, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, type ExecRequest, type ExecResult, type FetchRequest, FilesystemError, IdentityMismatchError, InvalidAlgorithmError, KeyRevokedError, KeyRotatedError, type KeyStatus, type ListableBackend, PluginNotFoundError, type PreflightCheck, type PreflightCheckStatus, type PreflightResult, RotationInProgressError, type SecretAccessor, type SecretBackend, SecretNotFoundError, type SetupChoice, SetupError, type SetupOptions, type SetupQuestion, type SetupResult, type SignRequest, type SignResult, TokenExpiredError, TokenRevokedError, type TrustTier, UsageLimitExceededError, type VaultConfig, VaultError, VaultKeeper, type VaultKeeperOptions, type VaultResponse, type VerifyRequest, isListableBackend };
package/dist/index.d.ts CHANGED
@@ -134,6 +134,21 @@ declare class IdentityMismatchError extends VaultError {
134
134
  readonly currentHash: string;
135
135
  constructor(message: string, previousHash: string, currentHash: string);
136
136
  }
137
+ /**
138
+ * Thrown when a caller requests a signing/verification algorithm that is not
139
+ * in the allowed set (e.g. `'md5'`).
140
+ */
141
+ declare class InvalidAlgorithmError extends VaultError {
142
+ /**
143
+ * The algorithm that was requested.
144
+ */
145
+ readonly algorithm: string;
146
+ /**
147
+ * The set of algorithms that are allowed.
148
+ */
149
+ readonly allowed: string[];
150
+ constructor(message: string, algorithm: string, allowed: string[]);
151
+ }
137
152
  /**
138
153
  * Thrown during initialization when a required system dependency (e.g. OpenSSL
139
154
  * or a native credential helper) is missing or incompatible.
@@ -377,6 +392,8 @@ interface BackendConfig {
377
392
  plugin?: boolean | undefined;
378
393
  /** Filesystem path used by file-based backends. */
379
394
  path?: string | undefined;
395
+ /** Backend-specific options collected during interactive setup. */
396
+ options?: Record<string, string> | undefined;
380
397
  }
381
398
 
382
399
  /**
@@ -449,6 +466,47 @@ interface ListableBackend extends SecretBackend {
449
466
  */
450
467
  declare function isListableBackend(backend: SecretBackend): backend is ListableBackend;
451
468
 
469
+ /**
470
+ * Types for the backend setup protocol.
471
+ *
472
+ * @packageDocumentation
473
+ */
474
+ /**
475
+ * A question yielded by a backend setup generator.
476
+ * @public
477
+ */
478
+ interface SetupQuestion {
479
+ /** Machine key for BackendConfig.options */
480
+ readonly key: string;
481
+ /** Human-readable prompt */
482
+ readonly prompt: string;
483
+ /** Present for selection questions; when absent, the answer is a free-form string. */
484
+ readonly choices?: readonly SetupChoice[];
485
+ }
486
+ /**
487
+ * A choice within a setup question.
488
+ * @public
489
+ */
490
+ interface SetupChoice {
491
+ /** Persisted value */
492
+ readonly value: string;
493
+ /** Display label */
494
+ readonly label: string;
495
+ }
496
+ /**
497
+ * Result returned when a backend setup generator completes.
498
+ * @public
499
+ */
500
+ interface SetupResult {
501
+ /** Merge into BackendConfig.options */
502
+ readonly options: Record<string, string>;
503
+ }
504
+ /**
505
+ * Factory that creates a backend setup generator.
506
+ * @public
507
+ */
508
+ type BackendSetupFactory = () => AsyncGenerator<SetupQuestion, SetupResult, string>;
509
+
452
510
  /**
453
511
  * Registry for secret backend implementations.
454
512
  *
@@ -469,6 +527,7 @@ declare function isListableBackend(backend: SecretBackend): backend is ListableB
469
527
  */
470
528
  declare class BackendRegistry {
471
529
  private static backends;
530
+ private static setups;
472
531
  /**
473
532
  * Register a backend factory.
474
533
  * @param type - Backend type identifier
@@ -479,7 +538,7 @@ declare class BackendRegistry {
479
538
  * Create a backend instance by type.
480
539
  * @param type - Backend type identifier
481
540
  * @returns A SecretBackend instance
482
- * @throws Error if the backend type is not registered
541
+ * @throws {@link BackendUnavailableError} if the backend type is not registered
483
542
  */
484
543
  static create(type: string): SecretBackend;
485
544
  /**
@@ -500,6 +559,36 @@ declare class BackendRegistry {
500
559
  * @public
501
560
  */
502
561
  static getAvailableTypes(): Promise<string[]>;
562
+ /**
563
+ * Register a setup factory for a backend type.
564
+ * @param type - Backend type identifier
565
+ * @param factory - Factory function that creates a setup generator
566
+ */
567
+ static registerSetup(type: string, factory: BackendSetupFactory): void;
568
+ /**
569
+ * Get the setup factory for a backend type, if one is registered.
570
+ * @param type - Backend type identifier
571
+ * @returns The setup factory, or `undefined` if none is registered
572
+ */
573
+ static getSetup(type: string): BackendSetupFactory | undefined;
574
+ /**
575
+ * Check whether a setup factory is registered for the given backend type.
576
+ * @param type - Backend type identifier
577
+ * @returns `true` if a setup factory is registered
578
+ */
579
+ static hasSetup(type: string): boolean;
580
+ /**
581
+ * Clear all registered backend factories.
582
+ * Intended for use in tests only.
583
+ * @internal
584
+ */
585
+ static clearBackends(): void;
586
+ /**
587
+ * Clear all registered setup factories.
588
+ * Intended for use in tests only.
589
+ * @internal
590
+ */
591
+ static clearSetups(): void;
503
592
  }
504
593
 
505
594
  /**
@@ -645,6 +734,8 @@ declare class VaultKeeper {
645
734
  * with the vault metadata (`vaultResponse`).
646
735
  * @throws {VaultError} If `token` is invalid or was not created by this
647
736
  * vault instance.
737
+ * @throws {InvalidAlgorithmError} If `request.algorithm` is not in the
738
+ * allowed set (e.g. `'md5'`).
648
739
  */
649
740
  sign(token: CapabilityToken, request: SignRequest): Promise<{
650
741
  result: SignResult;
@@ -657,8 +748,11 @@ declare class VaultKeeper {
657
748
  * capability tokens are required. It is safe to call from CI or any
658
749
  * context that has access to public key material.
659
750
  *
660
- * Never throws. Returns `false` for invalid key material, malformed
661
- * signatures, or any verification failure.
751
+ * Returns `false` for invalid key material, malformed signatures, or
752
+ * any verification failure (except disallowed algorithms, which throw).
753
+ *
754
+ * @throws {InvalidAlgorithmError} If `request.algorithm` is not in the
755
+ * allowed set (e.g. `'md5'`).
662
756
  *
663
757
  * @param request - The data, signature, public key, and optional
664
758
  * algorithm override.
@@ -700,4 +794,4 @@ declare class VaultKeeper {
700
794
  setDevelopmentMode(executablePath: string, enabled: boolean): Promise<void>;
701
795
  }
702
796
 
703
- export { AuthorizationDeniedError, type BackendConfig, type BackendFactory, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, type ExecRequest, type ExecResult, type FetchRequest, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, type KeyStatus, type ListableBackend, PluginNotFoundError, type PreflightCheck, type PreflightCheckStatus, type PreflightResult, RotationInProgressError, type SecretAccessor, type SecretBackend, SecretNotFoundError, SetupError, type SetupOptions, type SignRequest, type SignResult, TokenExpiredError, TokenRevokedError, type TrustTier, UsageLimitExceededError, type VaultConfig, VaultError, VaultKeeper, type VaultKeeperOptions, type VaultResponse, type VerifyRequest, isListableBackend };
797
+ export { AuthorizationDeniedError, type BackendConfig, type BackendFactory, BackendLockedError, BackendRegistry, type BackendSetupFactory, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, type ExecRequest, type ExecResult, type FetchRequest, FilesystemError, IdentityMismatchError, InvalidAlgorithmError, KeyRevokedError, KeyRotatedError, type KeyStatus, type ListableBackend, PluginNotFoundError, type PreflightCheck, type PreflightCheckStatus, type PreflightResult, RotationInProgressError, type SecretAccessor, type SecretBackend, SecretNotFoundError, type SetupChoice, SetupError, type SetupOptions, type SetupQuestion, type SetupResult, type SignRequest, type SignResult, TokenExpiredError, TokenRevokedError, type TrustTier, UsageLimitExceededError, type VaultConfig, VaultError, VaultKeeper, type VaultKeeperOptions, type VaultResponse, type VerifyRequest, isListableBackend };
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import * as fs5 from 'fs/promises';
3
3
  import * as path5 from 'path';
4
4
  import * as os4 from 'os';
5
5
  import * as crypto4 from 'crypto';
6
+ import 'url';
6
7
  import * as fs4 from 'fs';
7
8
  import { CompactEncrypt, compactDecrypt } from 'jose';
8
9
 
@@ -136,6 +137,22 @@ var IdentityMismatchError = class extends VaultError {
136
137
  this.currentHash = currentHash;
137
138
  }
138
139
  };
140
+ var InvalidAlgorithmError = class extends VaultError {
141
+ /**
142
+ * The algorithm that was requested.
143
+ */
144
+ algorithm;
145
+ /**
146
+ * The set of algorithms that are allowed.
147
+ */
148
+ allowed;
149
+ constructor(message, algorithm, allowed) {
150
+ super(message);
151
+ this.name = "InvalidAlgorithmError";
152
+ this.algorithm = algorithm;
153
+ this.allowed = allowed;
154
+ }
155
+ };
139
156
  var SetupError = class extends VaultError {
140
157
  /**
141
158
  * The name of the dependency that caused the setup failure.
@@ -179,6 +196,7 @@ function isListableBackend(backend) {
179
196
  // src/backend/registry.ts
180
197
  var BackendRegistry = class {
181
198
  static backends = /* @__PURE__ */ new Map();
199
+ static setups = /* @__PURE__ */ new Map();
182
200
  /**
183
201
  * Register a backend factory.
184
202
  * @param type - Backend type identifier
@@ -191,7 +209,7 @@ var BackendRegistry = class {
191
209
  * Create a backend instance by type.
192
210
  * @param type - Backend type identifier
193
211
  * @returns A SecretBackend instance
194
- * @throws Error if the backend type is not registered
212
+ * @throws {@link BackendUnavailableError} if the backend type is not registered
195
213
  */
196
214
  static create(type) {
197
215
  const factory = this.backends.get(type);
@@ -238,6 +256,46 @@ var BackendRegistry = class {
238
256
  );
239
257
  return results.filter((type) => type !== null);
240
258
  }
259
+ /**
260
+ * Register a setup factory for a backend type.
261
+ * @param type - Backend type identifier
262
+ * @param factory - Factory function that creates a setup generator
263
+ */
264
+ static registerSetup(type, factory) {
265
+ this.setups.set(type, factory);
266
+ }
267
+ /**
268
+ * Get the setup factory for a backend type, if one is registered.
269
+ * @param type - Backend type identifier
270
+ * @returns The setup factory, or `undefined` if none is registered
271
+ */
272
+ static getSetup(type) {
273
+ return this.setups.get(type);
274
+ }
275
+ /**
276
+ * Check whether a setup factory is registered for the given backend type.
277
+ * @param type - Backend type identifier
278
+ * @returns `true` if a setup factory is registered
279
+ */
280
+ static hasSetup(type) {
281
+ return this.setups.has(type);
282
+ }
283
+ /**
284
+ * Clear all registered backend factories.
285
+ * Intended for use in tests only.
286
+ * @internal
287
+ */
288
+ static clearBackends() {
289
+ this.backends.clear();
290
+ }
291
+ /**
292
+ * Clear all registered setup factories.
293
+ * Intended for use in tests only.
294
+ * @internal
295
+ */
296
+ static clearSetups() {
297
+ this.setups.clear();
298
+ }
241
299
  };
242
300
  async function execCommand(command, args, options) {
243
301
  const result = await execCommandFull(command, args);
@@ -487,6 +545,21 @@ function validateBackendEntry(entry, index) {
487
545
  }
488
546
  result.path = entry.path;
489
547
  }
548
+ if (entry.options !== void 0) {
549
+ if (!isObject(entry.options)) {
550
+ throw new Error(`backends[${String(index)}].options must be an object`);
551
+ }
552
+ const opts = {};
553
+ for (const [k, v] of Object.entries(entry.options)) {
554
+ if (typeof v !== "string") {
555
+ throw new Error(
556
+ `backends[${String(index)}].options["${k}"] must be a string`
557
+ );
558
+ }
559
+ opts[k] = v;
560
+ }
561
+ result.options = opts;
562
+ }
490
563
  return result;
491
564
  }
492
565
  function validateConfig(config) {
@@ -997,10 +1070,12 @@ function resolveAlgorithmForKey(key, override) {
997
1070
  if (keyType === "ed25519" || keyType === "ed448") {
998
1071
  return { signAlg: null, label: keyType };
999
1072
  }
1000
- const alg = override ?? "sha256";
1073
+ const alg = (override ?? "sha256").toLowerCase();
1001
1074
  if (!ALLOWED_ALGORITHMS.has(alg)) {
1002
- throw new VaultError(
1003
- `Unsupported signing algorithm '${alg}'. Allowed: ${[...ALLOWED_ALGORITHMS].join(", ")}`
1075
+ throw new InvalidAlgorithmError(
1076
+ `Unsupported algorithm '${alg}'. Allowed: ${[...ALLOWED_ALGORITHMS].join(", ")}`,
1077
+ alg,
1078
+ [...ALLOWED_ALGORITHMS]
1004
1079
  );
1005
1080
  }
1006
1081
  return { signAlg: alg, label: alg };
@@ -1024,6 +1099,9 @@ function delegatedVerify(request) {
1024
1099
  } catch {
1025
1100
  return false;
1026
1101
  }
1102
+ if (typeof request.publicKey === "string" && request.publicKey.includes("PRIVATE KEY")) {
1103
+ return false;
1104
+ }
1027
1105
  const { signAlg } = resolveAlgorithmForKey(key, request.algorithm);
1028
1106
  const sig = Buffer.from(request.signature, "base64");
1029
1107
  try {
@@ -1405,6 +1483,8 @@ var VaultKeeper = class _VaultKeeper {
1405
1483
  * with the vault metadata (`vaultResponse`).
1406
1484
  * @throws {VaultError} If `token` is invalid or was not created by this
1407
1485
  * vault instance.
1486
+ * @throws {InvalidAlgorithmError} If `request.algorithm` is not in the
1487
+ * allowed set (e.g. `'md5'`).
1408
1488
  */
1409
1489
  async sign(token, request) {
1410
1490
  const claims = validateCapabilityToken(token);
@@ -1422,8 +1502,11 @@ var VaultKeeper = class _VaultKeeper {
1422
1502
  * capability tokens are required. It is safe to call from CI or any
1423
1503
  * context that has access to public key material.
1424
1504
  *
1425
- * Never throws. Returns `false` for invalid key material, malformed
1426
- * signatures, or any verification failure.
1505
+ * Returns `false` for invalid key material, malformed signatures, or
1506
+ * any verification failure (except disallowed algorithms, which throw).
1507
+ *
1508
+ * @throws {InvalidAlgorithmError} If `request.algorithm` is not in the
1509
+ * allowed set (e.g. `'md5'`).
1427
1510
  *
1428
1511
  * @param request - The data, signature, public key, and optional
1429
1512
  * algorithm override.
@@ -1548,6 +1631,6 @@ var VaultKeeper = class _VaultKeeper {
1548
1631
  }
1549
1632
  };
1550
1633
 
1551
- export { AuthorizationDeniedError, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, FilesystemError, IdentityMismatchError, KeyRevokedError, KeyRotatedError, PluginNotFoundError, RotationInProgressError, SecretNotFoundError, SetupError, TokenExpiredError, TokenRevokedError, UsageLimitExceededError, VaultError, VaultKeeper, isListableBackend };
1634
+ export { AuthorizationDeniedError, BackendLockedError, BackendRegistry, BackendUnavailableError, CapabilityToken, DeviceNotPresentError, FilesystemError, IdentityMismatchError, InvalidAlgorithmError, KeyRevokedError, KeyRotatedError, PluginNotFoundError, RotationInProgressError, SecretNotFoundError, SetupError, TokenExpiredError, TokenRevokedError, UsageLimitExceededError, VaultError, VaultKeeper, isListableBackend };
1552
1635
  //# sourceMappingURL=index.js.map
1553
1636
  //# sourceMappingURL=index.js.map