vaultkeeper 1.0.0 → 1.0.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/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
- import { spawn } from 'child_process';
2
- import * as fs5 from 'fs/promises';
3
- import * as path5 from 'path';
1
+ import * as fs from 'fs/promises';
2
+ import * as path3 from 'path';
3
+ import { join, dirname, resolve } from 'path';
4
4
  import * as os4 from 'os';
5
- import * as crypto4 from 'crypto';
6
- import 'url';
5
+ import * as crypto from 'crypto';
6
+ import { spawn } from 'child_process';
7
+ import { fileURLToPath } from 'url';
7
8
  import * as fs4 from 'fs';
9
+ import { existsSync, readFileSync } from 'fs';
8
10
  import { CompactEncrypt, compactDecrypt } from 'jose';
9
11
 
10
12
  // src/errors.ts
@@ -188,11 +190,6 @@ var RotationInProgressError = class extends VaultError {
188
190
  }
189
191
  };
190
192
 
191
- // src/backend/types.ts
192
- function isListableBackend(backend) {
193
- return "list" in backend && typeof backend.list === "function";
194
- }
195
-
196
193
  // src/backend/registry.ts
197
194
  var BackendRegistry = class {
198
195
  static backends = /* @__PURE__ */ new Map();
@@ -208,10 +205,11 @@ var BackendRegistry = class {
208
205
  /**
209
206
  * Create a backend instance by type.
210
207
  * @param type - Backend type identifier
208
+ * @param config - Optional backend configuration forwarded to the factory
211
209
  * @returns A SecretBackend instance
212
210
  * @throws {@link BackendUnavailableError} if the backend type is not registered
213
211
  */
214
- static create(type) {
212
+ static create(type, config) {
215
213
  const factory = this.backends.get(type);
216
214
  if (factory === void 0) {
217
215
  throw new BackendUnavailableError(
@@ -220,7 +218,7 @@ var BackendRegistry = class {
220
218
  Array.from(this.backends.keys())
221
219
  );
222
220
  }
223
- return factory();
221
+ return factory(config);
224
222
  }
225
223
  /**
226
224
  * Get all registered backend type identifiers.
@@ -297,17 +295,158 @@ var BackendRegistry = class {
297
295
  this.setups.clear();
298
296
  }
299
297
  };
298
+ var STORAGE_DIR_NAME = path3.join(".vaultkeeper", "file");
299
+ var KEY_FILE = ".key";
300
+ var GCM_IV_BYTES = 12;
301
+ var GCM_KEY_BYTES = 32;
302
+ var GCM_TAG_LENGTH = 128;
303
+ function getStorageDir() {
304
+ return path3.join(os4.homedir(), STORAGE_DIR_NAME);
305
+ }
306
+ function getEntryPath(storageDir, id) {
307
+ const safeId = Buffer.from(id, "utf8").toString("hex");
308
+ return path3.join(storageDir, `${safeId}.enc`);
309
+ }
310
+ async function ensureStorageDir(storageDir) {
311
+ try {
312
+ await fs.mkdir(storageDir, { recursive: true, mode: 448 });
313
+ } catch (err) {
314
+ if (err instanceof Error && "code" in err && err.code !== "EEXIST") {
315
+ throw new FilesystemError(
316
+ `Failed to create storage directory: ${storageDir}`,
317
+ storageDir,
318
+ "rwx"
319
+ );
320
+ }
321
+ }
322
+ }
323
+ async function getOrCreateKey(storageDir) {
324
+ const keyPath = path3.join(storageDir, KEY_FILE);
325
+ try {
326
+ const data = await fs.readFile(keyPath);
327
+ return data;
328
+ } catch (err) {
329
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
330
+ const key = crypto.randomBytes(GCM_KEY_BYTES);
331
+ await fs.writeFile(keyPath, key, { mode: 384 });
332
+ return key;
333
+ }
334
+ throw err;
335
+ }
336
+ }
337
+ function encryptGcm(key, plaintext) {
338
+ const iv = crypto.randomBytes(GCM_IV_BYTES);
339
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv, {
340
+ authTagLength: GCM_TAG_LENGTH / 8
341
+ });
342
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
343
+ const authTag = cipher.getAuthTag();
344
+ return [iv.toString("base64"), authTag.toString("base64"), encrypted.toString("base64")].join(":");
345
+ }
346
+ function decryptGcm(key, encoded) {
347
+ const parts = encoded.split(":");
348
+ if (parts.length !== 3) {
349
+ throw new Error("Invalid encrypted file format: expected iv:authTag:ciphertext");
350
+ }
351
+ const [ivB64, authTagB64, ciphertextB64] = parts;
352
+ if (ivB64 === void 0 || authTagB64 === void 0 || ciphertextB64 === void 0) {
353
+ throw new Error("Invalid encrypted file format: missing part");
354
+ }
355
+ const iv = Buffer.from(ivB64, "base64");
356
+ const authTag = Buffer.from(authTagB64, "base64");
357
+ const ciphertext = Buffer.from(ciphertextB64, "base64");
358
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv, {
359
+ authTagLength: GCM_TAG_LENGTH / 8
360
+ });
361
+ decipher.setAuthTag(authTag);
362
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
363
+ return decrypted.toString("utf8");
364
+ }
365
+ var FileBackend = class {
366
+ type = "file";
367
+ displayName = "Encrypted File Store";
368
+ async isAvailable() {
369
+ try {
370
+ const storageDir = getStorageDir();
371
+ await ensureStorageDir(storageDir);
372
+ return true;
373
+ } catch {
374
+ return false;
375
+ }
376
+ }
377
+ async store(id, secret) {
378
+ const storageDir = getStorageDir();
379
+ await ensureStorageDir(storageDir);
380
+ const key = await getOrCreateKey(storageDir);
381
+ const entryPath = getEntryPath(storageDir, id);
382
+ const encrypted = encryptGcm(key, secret);
383
+ await fs.writeFile(entryPath, encrypted, { mode: 384 });
384
+ }
385
+ async retrieve(id) {
386
+ const storageDir = getStorageDir();
387
+ const entryPath = getEntryPath(storageDir, id);
388
+ let encoded;
389
+ try {
390
+ encoded = await fs.readFile(entryPath, "utf8");
391
+ } catch (err) {
392
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
393
+ throw new SecretNotFoundError(`Secret not found in file store: ${id}`);
394
+ }
395
+ throw err;
396
+ }
397
+ const key = await getOrCreateKey(storageDir);
398
+ try {
399
+ return decryptGcm(key, encoded);
400
+ } catch (err) {
401
+ throw new Error(
402
+ `Failed to decrypt secret: ${err instanceof Error ? err.message : String(err)}`
403
+ );
404
+ }
405
+ }
406
+ async delete(id) {
407
+ const storageDir = getStorageDir();
408
+ const entryPath = getEntryPath(storageDir, id);
409
+ try {
410
+ await fs.unlink(entryPath);
411
+ } catch (err) {
412
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
413
+ throw new SecretNotFoundError(`Secret not found in file store: ${id}`);
414
+ }
415
+ throw err;
416
+ }
417
+ }
418
+ async exists(id) {
419
+ const storageDir = getStorageDir();
420
+ const entryPath = getEntryPath(storageDir, id);
421
+ try {
422
+ await fs.access(entryPath);
423
+ return true;
424
+ } catch {
425
+ return false;
426
+ }
427
+ }
428
+ async list() {
429
+ const storageDir = getStorageDir();
430
+ let entries;
431
+ try {
432
+ entries = await fs.readdir(storageDir);
433
+ } catch {
434
+ return [];
435
+ }
436
+ return entries.filter((f) => f.endsWith(".enc")).map((f) => Buffer.from(f.slice(0, -4), "hex").toString("utf8"));
437
+ }
438
+ };
300
439
  async function execCommand(command, args, options) {
301
- const result = await execCommandFull(command, args);
440
+ const result = await execCommandFull(command, args, options);
302
441
  if (result.exitCode !== 0) {
303
442
  throw new Error(`Command failed with exit code ${String(result.exitCode)}: ${result.stderr}`);
304
443
  }
305
444
  return result.stdout.trim();
306
445
  }
307
446
  function execCommandFull(command, args, options) {
308
- return new Promise((resolve, reject) => {
447
+ return new Promise((resolve2, reject) => {
309
448
  const proc = spawn(command, args, {
310
- stdio: ["ignore", "pipe", "pipe"]
449
+ stdio: [options?.stdin !== void 0 ? "pipe" : "ignore", "pipe", "pipe"]
311
450
  });
312
451
  let stdout = "";
313
452
  let stderr = "";
@@ -317,25 +456,887 @@ function execCommandFull(command, args, options) {
317
456
  proc.stderr?.on("data", (data) => {
318
457
  stderr += data.toString();
319
458
  });
459
+ if (options?.stdin !== void 0 && proc.stdin) {
460
+ proc.stdin.write(options.stdin);
461
+ proc.stdin.end();
462
+ }
463
+ if (options?.timeoutMs !== void 0) {
464
+ setTimeout(() => {
465
+ proc.kill("SIGTERM");
466
+ reject(new Error(`Command timed out after ${String(options.timeoutMs)}ms`));
467
+ }, options.timeoutMs);
468
+ }
320
469
  proc.on("close", (code) => {
321
- resolve({ stdout, stderr, exitCode: code ?? 1 });
470
+ resolve2({ stdout, stderr, exitCode: code ?? 1 });
322
471
  });
323
472
  proc.on("error", (error) => {
324
473
  reject(error);
325
474
  });
326
475
  });
327
476
  }
328
- path5.join(".vaultkeeper", "file");
329
- path5.join(".vaultkeeper", "yubikey");
477
+
478
+ // src/backend/keychain-backend.ts
479
+ var ACCOUNT = "vaultkeeper";
480
+ var SERVICE_PREFIX = "vaultkeeper:";
481
+ var KeychainBackend = class {
482
+ type = "keychain";
483
+ displayName = "macOS Keychain";
484
+ async isAvailable() {
485
+ if (process.platform !== "darwin") {
486
+ return false;
487
+ }
488
+ try {
489
+ const result = await execCommandFull("security", ["version"]);
490
+ return result.exitCode === 0;
491
+ } catch {
492
+ return false;
493
+ }
494
+ }
495
+ async store(id, secret) {
496
+ const service = `${SERVICE_PREFIX}${id}`;
497
+ const encoded = Buffer.from(secret, "utf8").toString("base64");
498
+ await execCommandFull("security", [
499
+ "delete-generic-password",
500
+ "-a",
501
+ ACCOUNT,
502
+ "-s",
503
+ service
504
+ ]);
505
+ await execCommand("security", [
506
+ "add-generic-password",
507
+ "-a",
508
+ ACCOUNT,
509
+ "-s",
510
+ service,
511
+ "-w",
512
+ encoded
513
+ ]);
514
+ }
515
+ async retrieve(id) {
516
+ const service = `${SERVICE_PREFIX}${id}`;
517
+ const result = await execCommandFull("security", [
518
+ "find-generic-password",
519
+ "-a",
520
+ ACCOUNT,
521
+ "-s",
522
+ service,
523
+ "-w"
524
+ ]);
525
+ if (result.exitCode !== 0) {
526
+ throw new SecretNotFoundError(`Secret not found in macOS Keychain: ${id}`);
527
+ }
528
+ const encoded = result.stdout.trim();
529
+ return Buffer.from(encoded, "base64").toString("utf8");
530
+ }
531
+ async delete(id) {
532
+ const service = `${SERVICE_PREFIX}${id}`;
533
+ const result = await execCommandFull("security", [
534
+ "delete-generic-password",
535
+ "-a",
536
+ ACCOUNT,
537
+ "-s",
538
+ service
539
+ ]);
540
+ if (result.exitCode !== 0) {
541
+ throw new SecretNotFoundError(`Secret not found in macOS Keychain: ${id}`);
542
+ }
543
+ }
544
+ async exists(id) {
545
+ const service = `${SERVICE_PREFIX}${id}`;
546
+ const result = await execCommandFull("security", [
547
+ "find-generic-password",
548
+ "-a",
549
+ ACCOUNT,
550
+ "-s",
551
+ service
552
+ ]);
553
+ return result.exitCode === 0;
554
+ }
555
+ async list() {
556
+ const result = await execCommandFull("security", [
557
+ "dump-keychain"
558
+ ]);
559
+ if (result.exitCode !== 0) {
560
+ return [];
561
+ }
562
+ const ids = [];
563
+ const servicePattern = /0x00000007 <blob>="vaultkeeper:([^"]+)"/g;
564
+ let match = servicePattern.exec(result.stdout);
565
+ while (match !== null) {
566
+ const id = match[1];
567
+ if (id !== void 0) {
568
+ ids.push(id);
569
+ }
570
+ match = servicePattern.exec(result.stdout);
571
+ }
572
+ return ids;
573
+ }
574
+ };
575
+ function getStoragePath() {
576
+ return path3.join(os4.homedir(), ".vaultkeeper", "dpapi");
577
+ }
578
+ function getEntryPath2(storageDir, id) {
579
+ const safeId = Buffer.from(id, "utf8").toString("hex");
580
+ return path3.join(storageDir, `${safeId}.enc`);
581
+ }
582
+ var DpapiBackend = class {
583
+ type = "dpapi";
584
+ displayName = "Windows DPAPI";
585
+ async isAvailable() {
586
+ if (process.platform !== "win32") {
587
+ return false;
588
+ }
589
+ try {
590
+ const result = await execCommandFull("powershell", [
591
+ "-NoProfile",
592
+ "-Command",
593
+ "[System.Security.Cryptography.ProtectedData] | Out-Null; exit 0"
594
+ ]);
595
+ return result.exitCode === 0;
596
+ } catch {
597
+ return false;
598
+ }
599
+ }
600
+ async store(id, secret) {
601
+ const storageDir = getStoragePath();
602
+ await fs.mkdir(storageDir, { recursive: true });
603
+ const entryPath = getEntryPath2(storageDir, id);
604
+ const script = [
605
+ "Add-Type -AssemblyName System.Security",
606
+ `$bytes = [System.Text.Encoding]::UTF8.GetBytes(${JSON.stringify(secret)})`,
607
+ "$entropy = $null",
608
+ "$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
609
+ "$encrypted = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $entropy, $scope)",
610
+ `[System.IO.File]::WriteAllBytes(${JSON.stringify(entryPath)}, $encrypted)`
611
+ ].join("; ");
612
+ await execCommand("powershell", ["-NoProfile", "-Command", script]);
613
+ }
614
+ async retrieve(id) {
615
+ const storageDir = getStoragePath();
616
+ const entryPath = getEntryPath2(storageDir, id);
617
+ try {
618
+ await fs.access(entryPath);
619
+ } catch {
620
+ throw new SecretNotFoundError(`Secret not found in Windows DPAPI store: ${id}`);
621
+ }
622
+ const script = [
623
+ "Add-Type -AssemblyName System.Security",
624
+ `$encrypted = [System.IO.File]::ReadAllBytes(${JSON.stringify(entryPath)})`,
625
+ "$entropy = $null",
626
+ "$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
627
+ "$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encrypted, $entropy, $scope)",
628
+ "Write-Output ([System.Text.Encoding]::UTF8.GetString($bytes))"
629
+ ].join("; ");
630
+ return execCommand("powershell", ["-NoProfile", "-Command", script]);
631
+ }
632
+ async delete(id) {
633
+ const storageDir = getStoragePath();
634
+ const entryPath = getEntryPath2(storageDir, id);
635
+ try {
636
+ await fs.unlink(entryPath);
637
+ } catch (err) {
638
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
639
+ throw new SecretNotFoundError(`Secret not found in Windows DPAPI store: ${id}`);
640
+ }
641
+ throw err;
642
+ }
643
+ }
644
+ async exists(id) {
645
+ const storageDir = getStoragePath();
646
+ const entryPath = getEntryPath2(storageDir, id);
647
+ try {
648
+ await fs.access(entryPath);
649
+ return true;
650
+ } catch {
651
+ return false;
652
+ }
653
+ }
654
+ async list() {
655
+ const storageDir = getStoragePath();
656
+ let entries;
657
+ try {
658
+ entries = await fs.readdir(storageDir);
659
+ } catch {
660
+ return [];
661
+ }
662
+ return entries.filter((f) => f.endsWith(".enc")).map((f) => Buffer.from(f.slice(0, -4), "hex").toString("utf8"));
663
+ }
664
+ };
665
+
666
+ // src/backend/secret-tool-backend.ts
667
+ var ATTRIBUTE_KEY = "vaultkeeper-id";
668
+ var LABEL_PREFIX = "vaultkeeper: ";
669
+ var SecretToolBackend = class {
670
+ type = "secret-tool";
671
+ displayName = "Linux Secret Service (secret-tool)";
672
+ async isAvailable() {
673
+ if (process.platform !== "linux") {
674
+ return false;
675
+ }
676
+ try {
677
+ const result = await execCommandFull("secret-tool", ["--version"]);
678
+ return result.exitCode === 0;
679
+ } catch {
680
+ return false;
681
+ }
682
+ }
683
+ async store(id, secret) {
684
+ const label = `${LABEL_PREFIX}${id}`;
685
+ await execCommand(
686
+ "secret-tool",
687
+ ["store", "--label", label, ATTRIBUTE_KEY, id],
688
+ { stdin: secret }
689
+ );
690
+ }
691
+ async retrieve(id) {
692
+ const result = await execCommandFull("secret-tool", ["lookup", ATTRIBUTE_KEY, id]);
693
+ if (result.exitCode !== 0 || result.stdout.trim() === "") {
694
+ throw new SecretNotFoundError(`Secret not found in Secret Service: ${id}`);
695
+ }
696
+ return result.stdout.trim();
697
+ }
698
+ async delete(id) {
699
+ const result = await execCommandFull("secret-tool", ["clear", ATTRIBUTE_KEY, id]);
700
+ if (result.exitCode !== 0) {
701
+ throw new SecretNotFoundError(`Secret not found in Secret Service: ${id}`);
702
+ }
703
+ }
704
+ async exists(id) {
705
+ const result = await execCommandFull("secret-tool", ["lookup", ATTRIBUTE_KEY, id]);
706
+ return result.exitCode === 0 && result.stdout.trim() !== "";
707
+ }
708
+ async list() {
709
+ const result = await execCommandFull("secret-tool", [
710
+ "search",
711
+ ATTRIBUTE_KEY,
712
+ ""
713
+ ]);
714
+ if (result.exitCode !== 0) {
715
+ return [];
716
+ }
717
+ const ids = [];
718
+ const attrPattern = new RegExp(`attribute\\.${ATTRIBUTE_KEY} = (.+)`, "g");
719
+ let match = attrPattern.exec(result.stdout);
720
+ while (match !== null) {
721
+ const id = match[1];
722
+ if (id !== void 0) {
723
+ ids.push(id);
724
+ }
725
+ match = attrPattern.exec(result.stdout);
726
+ }
727
+ return ids;
728
+ }
729
+ };
730
+ var INTEGRATION_NAME = "vaultkeeper";
731
+ var cachedVersion;
732
+ function getIntegrationVersion() {
733
+ if (cachedVersion !== void 0) return cachedVersion;
734
+ const dir = dirname(fileURLToPath(import.meta.url));
735
+ const candidates = [
736
+ resolve(dir, "..", "..", "package.json"),
737
+ resolve(dir, "..", "package.json")
738
+ ];
739
+ for (const candidate of candidates) {
740
+ if (!existsSync(candidate)) continue;
741
+ const raw = JSON.parse(readFileSync(candidate, "utf8"));
742
+ if (raw !== null && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
743
+ cachedVersion = raw.version;
744
+ return cachedVersion;
745
+ }
746
+ }
747
+ throw new Error(
748
+ `Could not read version from vaultkeeper package.json. Tried paths: ${candidates.join(", ")}`
749
+ );
750
+ }
751
+
752
+ // src/backend/one-password-backend.ts
753
+ var SDK_INSTALL_URL = "https://developer.1password.com/docs/sdks/";
754
+ var TAG = "vaultkeeper";
755
+ var PASSWORD_FIELD_TITLE = "password";
756
+ var SESSION_TIMEOUT_MS = 3e4;
757
+ function isWorkerSuccess(res) {
758
+ return "value" in res;
759
+ }
760
+ function isWorkerResponse(value) {
761
+ if (value === null || typeof value !== "object") return false;
762
+ if ("value" in value && typeof value.value === "string") return true;
763
+ if ("error" in value && typeof value.error === "string" && "code" in value && typeof value.code === "string")
764
+ return true;
765
+ return false;
766
+ }
767
+ var OnePasswordBackend = class {
768
+ type = "1password";
769
+ displayName = "1Password";
770
+ vaultId;
771
+ account;
772
+ serviceAccountToken;
773
+ accessMode;
774
+ sessionTimeoutMs;
775
+ /** In-flight or resolved client promise — prevents duplicate createClient calls. */
776
+ clientPromise;
777
+ constructor(options) {
778
+ if (options.accessMode === "per-access" && options.serviceAccountToken !== void 0) {
779
+ throw new Error(
780
+ "per-access mode requires desktop biometric authentication and cannot be used with a service account token"
781
+ );
782
+ }
783
+ if (options.account !== void 0 && options.serviceAccountToken !== void 0) {
784
+ throw new Error(
785
+ "account and serviceAccountToken are mutually exclusive \u2014 provide one or the other, not both"
786
+ );
787
+ }
788
+ this.vaultId = options.vault;
789
+ this.sessionTimeoutMs = options.sessionTimeoutMs ?? SESSION_TIMEOUT_MS;
790
+ if (options.account !== void 0) {
791
+ this.account = options.account;
792
+ }
793
+ if (options.serviceAccountToken !== void 0) {
794
+ this.serviceAccountToken = options.serviceAccountToken;
795
+ }
796
+ this.accessMode = options.accessMode ?? "session";
797
+ }
798
+ async isAvailable() {
799
+ const sdk = await this.tryLoadSdk();
800
+ return sdk !== null;
801
+ }
802
+ // ---- Session client management ----
803
+ /**
804
+ * Dynamically import the SDK. Returns `null` if the SDK is not installed or
805
+ * the native library cannot be loaded.
806
+ */
807
+ async tryLoadSdk() {
808
+ try {
809
+ const sdk = await import('@1password/sdk');
810
+ return sdk;
811
+ } catch {
812
+ return null;
813
+ }
814
+ }
815
+ /**
816
+ * Acquire (or create) a cached SDK client.
817
+ * Wraps `createClient` with a configurable timeout (default 30 s) to handle
818
+ * the known beta SDK hang after session expiry.
819
+ */
820
+ acquireClient() {
821
+ this.clientPromise ??= this.createClientInternal().catch((err) => {
822
+ this.clientPromise = void 0;
823
+ throw err;
824
+ });
825
+ return this.clientPromise;
826
+ }
827
+ async createClientInternal() {
828
+ const sdk = await this.tryLoadSdk();
829
+ if (sdk === null) {
830
+ throw new PluginNotFoundError(
831
+ "1Password SDK (@1password/sdk) is not available. Install it to use this backend.",
832
+ "@1password/sdk",
833
+ SDK_INSTALL_URL
834
+ );
835
+ }
836
+ const auth = this.buildAuth(sdk);
837
+ let timerId;
838
+ const timeoutPromise = new Promise((_resolve, reject) => {
839
+ timerId = setTimeout(() => {
840
+ reject(new BackendLockedError("1Password session timed out waiting for authentication", true));
841
+ }, this.sessionTimeoutMs);
842
+ });
843
+ try {
844
+ const client = await Promise.race([
845
+ sdk.createClient({
846
+ auth,
847
+ integrationName: INTEGRATION_NAME,
848
+ integrationVersion: getIntegrationVersion()
849
+ }),
850
+ timeoutPromise
851
+ ]);
852
+ return client;
853
+ } catch (err) {
854
+ if (err instanceof BackendLockedError) {
855
+ throw err;
856
+ }
857
+ if (err instanceof sdk.DesktopSessionExpiredError) {
858
+ throw new BackendLockedError(
859
+ "1Password session has expired. Please unlock the app.",
860
+ true
861
+ );
862
+ }
863
+ throw new AuthorizationDeniedError(
864
+ `1Password authentication failed: ${String(err)}`
865
+ );
866
+ } finally {
867
+ if (timerId !== void 0) {
868
+ clearTimeout(timerId);
869
+ }
870
+ }
871
+ }
872
+ buildAuth(sdk) {
873
+ if (this.serviceAccountToken !== void 0) {
874
+ return this.serviceAccountToken;
875
+ }
876
+ const accountName = this.account ?? "";
877
+ return new sdk.DesktopAuth(accountName);
878
+ }
879
+ // ---- Helpers for item lookup by title ----
880
+ /**
881
+ * List all items in the vault tagged "vaultkeeper" and find one with the
882
+ * matching title (= secret ID). Returns `undefined` if not found.
883
+ */
884
+ async findItemOverview(client, id) {
885
+ const overviews = await client.items.list(this.vaultId);
886
+ for (const overview of overviews) {
887
+ if (overview.title === id && overview.tags.includes(TAG)) {
888
+ return overview;
889
+ }
890
+ }
891
+ return void 0;
892
+ }
893
+ /**
894
+ * Fetch the full item for a given secret id. Returns `undefined` if not found.
895
+ */
896
+ async findItem(client, id) {
897
+ const overview = await this.findItemOverview(client, id);
898
+ if (overview === void 0) return void 0;
899
+ return client.items.get(this.vaultId, overview.id);
900
+ }
901
+ /**
902
+ * Extract the concealed password field value from an item.
903
+ */
904
+ extractSecret(item, id) {
905
+ for (const field of item.fields) {
906
+ if (field.title === PASSWORD_FIELD_TITLE) {
907
+ return field.value;
908
+ }
909
+ }
910
+ throw new SecretNotFoundError(
911
+ `Secret found in 1Password but missing password field: ${id}`
912
+ );
913
+ }
914
+ // ---- SecretBackend / ListableBackend implementation ----
915
+ async store(id, secret) {
916
+ const { ItemCategory, ItemFieldType } = await this.requireSdk();
917
+ const client = await this.acquireClient();
918
+ const existing = await this.findItem(client, id);
919
+ if (existing !== void 0) {
920
+ const hasPasswordField = existing.fields.some((f) => f.title === PASSWORD_FIELD_TITLE);
921
+ const updatedFields = hasPasswordField ? existing.fields.map((f) => {
922
+ if (f.title === PASSWORD_FIELD_TITLE) {
923
+ return { ...f, value: secret };
924
+ }
925
+ return f;
926
+ }) : [
927
+ ...existing.fields,
928
+ {
929
+ id: "password",
930
+ title: PASSWORD_FIELD_TITLE,
931
+ fieldType: ItemFieldType.Concealed,
932
+ value: secret
933
+ }
934
+ ];
935
+ await client.items.put({ ...existing, fields: updatedFields });
936
+ } else {
937
+ await client.items.create({
938
+ category: ItemCategory.Password,
939
+ vaultId: this.vaultId,
940
+ title: id,
941
+ tags: [TAG],
942
+ fields: [
943
+ {
944
+ id: "password",
945
+ title: PASSWORD_FIELD_TITLE,
946
+ fieldType: ItemFieldType.Concealed,
947
+ value: secret
948
+ }
949
+ ]
950
+ });
951
+ }
952
+ }
953
+ async retrieve(id) {
954
+ if (this.accessMode === "per-access") {
955
+ return this.retrieveViaWorker(id);
956
+ }
957
+ return this.retrieveViaSession(id);
958
+ }
959
+ async retrieveViaSession(id) {
960
+ const client = await this.acquireClient();
961
+ const item = await this.findItem(client, id);
962
+ if (item === void 0) {
963
+ throw new SecretNotFoundError(`Secret not found in 1Password: ${id}`);
964
+ }
965
+ return this.extractSecret(item, id);
966
+ }
967
+ /**
968
+ * Spawn the per-access worker script that triggers a fresh biometric prompt
969
+ * for each retrieval, then returns the secret from its stdout.
970
+ */
971
+ retrieveViaWorker(id) {
972
+ return new Promise((resolve2, reject) => {
973
+ const workerPath = join(
974
+ dirname(fileURLToPath(import.meta.url)),
975
+ "one-password-worker.js"
976
+ );
977
+ const accountArg = this.account ?? "";
978
+ const child = spawn(
979
+ process.execPath,
980
+ [workerPath, accountArg, this.vaultId, id],
981
+ { stdio: ["ignore", "pipe", "pipe"] }
982
+ );
983
+ const stdoutChunks = [];
984
+ const stderrChunks = [];
985
+ child.stdout.on("data", (chunk) => {
986
+ stdoutChunks.push(chunk);
987
+ });
988
+ child.stderr.on("data", (chunk) => {
989
+ stderrChunks.push(chunk);
990
+ });
991
+ child.on("close", (code) => {
992
+ const raw = Buffer.concat(stdoutChunks).toString("utf8").trim();
993
+ if (raw === "") {
994
+ const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
995
+ const detail = stderr !== "" ? stderr : `exit code ${String(code)}`;
996
+ reject(new Error(`1Password per-access worker crashed for secret ${id}: ${detail}`));
997
+ return;
998
+ }
999
+ let parsed;
1000
+ try {
1001
+ parsed = JSON.parse(raw);
1002
+ } catch {
1003
+ reject(new SecretNotFoundError(`Worker returned unparseable output for secret: ${id}`));
1004
+ return;
1005
+ }
1006
+ if (!isWorkerResponse(parsed)) {
1007
+ reject(new SecretNotFoundError(`Worker returned unexpected response shape for secret: ${id}`));
1008
+ return;
1009
+ }
1010
+ if (isWorkerSuccess(parsed)) {
1011
+ resolve2(parsed.value);
1012
+ } else {
1013
+ switch (parsed.code) {
1014
+ case "NOT_FOUND":
1015
+ reject(new SecretNotFoundError(`Secret not found in 1Password: ${id}`));
1016
+ break;
1017
+ case "AUTH_DENIED":
1018
+ reject(new AuthorizationDeniedError("1Password authentication was denied"));
1019
+ break;
1020
+ case "LOCKED":
1021
+ reject(new BackendLockedError("1Password is locked. Please unlock and retry.", true));
1022
+ break;
1023
+ default:
1024
+ reject(new SecretNotFoundError(`Worker failed for secret ${id}: ${parsed.error}`));
1025
+ }
1026
+ }
1027
+ });
1028
+ child.on("error", (err) => {
1029
+ reject(new Error(
1030
+ `Failed to spawn 1Password per-access worker at ${workerPath}: ${String(err)}`
1031
+ ));
1032
+ });
1033
+ });
1034
+ }
1035
+ async delete(id) {
1036
+ const client = await this.acquireClient();
1037
+ const overview = await this.findItemOverview(client, id);
1038
+ if (overview === void 0) {
1039
+ throw new SecretNotFoundError(`Secret not found in 1Password: ${id}`);
1040
+ }
1041
+ await client.items.delete(this.vaultId, overview.id);
1042
+ }
1043
+ async exists(id) {
1044
+ const client = await this.acquireClient();
1045
+ const overview = await this.findItemOverview(client, id);
1046
+ return overview !== void 0;
1047
+ }
1048
+ async list() {
1049
+ const client = await this.acquireClient();
1050
+ const overviews = await client.items.list(this.vaultId);
1051
+ const ids = [];
1052
+ for (const overview of overviews) {
1053
+ if (overview.tags.includes(TAG)) {
1054
+ ids.push(overview.title);
1055
+ }
1056
+ }
1057
+ return ids;
1058
+ }
1059
+ // ---- Private helpers ----
1060
+ /** Load SDK and throw PluginNotFoundError if unavailable. */
1061
+ async requireSdk() {
1062
+ const sdk = await this.tryLoadSdk();
1063
+ if (sdk === null) {
1064
+ throw new PluginNotFoundError(
1065
+ "1Password SDK (@1password/sdk) is not available.",
1066
+ "@1password/sdk",
1067
+ SDK_INSTALL_URL
1068
+ );
1069
+ }
1070
+ return sdk;
1071
+ }
1072
+ };
1073
+ var YKMAN_INSTALL_URL = "https://developers.yubico.com/yubikey-manager/";
1074
+ var STORAGE_DIR_NAME2 = path3.join(".vaultkeeper", "yubikey");
1075
+ var METADATA_FILE = "metadata.json";
1076
+ var DEVICE_TIMEOUT_MS = 5e3;
1077
+ var GCM_IV_BYTES2 = 12;
1078
+ var GCM_KEY_BYTES2 = 32;
1079
+ var GCM_TAG_LENGTH_BITS = 128;
1080
+ var FORMAT_VERSION = "1";
1081
+ function getStorageDir2() {
1082
+ return path3.join(os4.homedir(), STORAGE_DIR_NAME2);
1083
+ }
1084
+ function getEntryPath3(storageDir, id) {
1085
+ const safeId = Buffer.from(id, "utf8").toString("hex");
1086
+ return path3.join(storageDir, `${safeId}.enc`);
1087
+ }
1088
+ function isStringRecord(value) {
1089
+ if (value === null || typeof value !== "object") {
1090
+ return false;
1091
+ }
1092
+ return Object.values(value).every((v) => typeof v === "string");
1093
+ }
1094
+ async function loadMetadata(storageDir) {
1095
+ const metaPath = path3.join(storageDir, METADATA_FILE);
1096
+ try {
1097
+ const raw = await fs.readFile(metaPath, "utf8");
1098
+ const parsed = JSON.parse(raw);
1099
+ if (parsed !== null && typeof parsed === "object" && "entries" in parsed && isStringRecord(parsed.entries)) {
1100
+ return { entries: parsed.entries };
1101
+ }
1102
+ return { entries: {} };
1103
+ } catch {
1104
+ return { entries: {} };
1105
+ }
1106
+ }
1107
+ async function saveMetadata(storageDir, metadata) {
1108
+ const metaPath = path3.join(storageDir, METADATA_FILE);
1109
+ await fs.writeFile(metaPath, JSON.stringify(metadata, null, 2), { mode: 384 });
1110
+ }
1111
+ var HMAC_RESPONSE_HEX_LENGTH = 40;
1112
+ var HMAC_RESPONSE_RE = /^[0-9a-fA-F]{40}$/;
1113
+ function deriveKey(hmacResponse, id) {
1114
+ const trimmed = hmacResponse.trim();
1115
+ if (!HMAC_RESPONSE_RE.test(trimmed)) {
1116
+ throw new Error(
1117
+ `Invalid YubiKey HMAC response: expected exactly ${String(HMAC_RESPONSE_HEX_LENGTH)} hex characters (20 bytes), got ${String(trimmed.length)} characters`
1118
+ );
1119
+ }
1120
+ const ikm = Buffer.from(trimmed, "hex");
1121
+ const info = Buffer.from(`vaultkeeper-yubikey:${id}`, "utf8");
1122
+ const keyMaterial = crypto.hkdfSync("sha256", ikm, Buffer.alloc(0), info, GCM_KEY_BYTES2);
1123
+ return Buffer.from(keyMaterial);
1124
+ }
1125
+ function encryptGcm2(key, plaintext) {
1126
+ const iv = crypto.randomBytes(GCM_IV_BYTES2);
1127
+ let encrypted;
1128
+ let authTag;
1129
+ try {
1130
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv, {
1131
+ authTagLength: GCM_TAG_LENGTH_BITS / 8
1132
+ });
1133
+ encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
1134
+ authTag = cipher.getAuthTag();
1135
+ return [
1136
+ FORMAT_VERSION,
1137
+ iv.toString("base64"),
1138
+ authTag.toString("base64"),
1139
+ encrypted.toString("base64")
1140
+ ].join(":");
1141
+ } finally {
1142
+ key.fill(0);
1143
+ iv.fill(0);
1144
+ encrypted?.fill(0);
1145
+ authTag?.fill(0);
1146
+ }
1147
+ }
1148
+ function decryptGcm2(key, encoded) {
1149
+ const parts = encoded.split(":");
1150
+ const versionSegment = parts[0] ?? "";
1151
+ const parsedVersion = parseInt(versionSegment, 10);
1152
+ const isNumericVersion = String(parsedVersion) === versionSegment && !Number.isNaN(parsedVersion);
1153
+ if (!isNumericVersion) {
1154
+ key.fill(0);
1155
+ throw new Error(
1156
+ "Encrypted file uses a legacy format (AES-256-CBC). Delete the secret and re-store it to migrate to AES-256-GCM."
1157
+ );
1158
+ }
1159
+ if (versionSegment !== FORMAT_VERSION) {
1160
+ key.fill(0);
1161
+ throw new Error(
1162
+ `Unsupported encrypted file version: ${versionSegment}. This vaultkeeper build only supports version ${FORMAT_VERSION}. Upgrade vaultkeeper to read this secret.`
1163
+ );
1164
+ }
1165
+ if (parts.length !== 4) {
1166
+ key.fill(0);
1167
+ throw new Error(
1168
+ `Invalid encrypted file format: expected ${FORMAT_VERSION}:iv:authTag:ciphertext`
1169
+ );
1170
+ }
1171
+ const [_version, ivB64, authTagB64, ciphertextB64] = parts;
1172
+ if (ivB64 === void 0 || authTagB64 === void 0 || ciphertextB64 === void 0) {
1173
+ key.fill(0);
1174
+ throw new Error("Invalid encrypted file format: missing part");
1175
+ }
1176
+ let decrypted;
1177
+ try {
1178
+ const iv = Buffer.from(ivB64, "base64");
1179
+ const authTag = Buffer.from(authTagB64, "base64");
1180
+ const ciphertext = Buffer.from(ciphertextB64, "base64");
1181
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv, {
1182
+ authTagLength: GCM_TAG_LENGTH_BITS / 8
1183
+ });
1184
+ decipher.setAuthTag(authTag);
1185
+ try {
1186
+ decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
1187
+ } catch (err) {
1188
+ throw new Error(
1189
+ `GCM authentication failed \u2014 ciphertext may be tampered: ${err instanceof Error ? err.message : String(err)}`
1190
+ );
1191
+ }
1192
+ const plaintext = decrypted.toString("utf8");
1193
+ return plaintext;
1194
+ } finally {
1195
+ key.fill(0);
1196
+ decrypted?.fill(0);
1197
+ }
1198
+ }
1199
+ var YubikeyBackend = class {
1200
+ type = "yubikey";
1201
+ displayName = "YubiKey";
1202
+ async isAvailable() {
1203
+ try {
1204
+ const result = await execCommandFull("ykman", ["--version"]);
1205
+ if (result.exitCode !== 0) {
1206
+ return false;
1207
+ }
1208
+ const listResult = await execCommandFull("ykman", ["list"]);
1209
+ return listResult.exitCode === 0 && listResult.stdout.trim() !== "";
1210
+ } catch {
1211
+ return false;
1212
+ }
1213
+ }
1214
+ async requireDevice() {
1215
+ const available = await this.isAvailable();
1216
+ if (!available) {
1217
+ const hasYkman = await execCommandFull("ykman", ["--version"]).then(
1218
+ (r) => r.exitCode === 0,
1219
+ () => false
1220
+ );
1221
+ if (!hasYkman) {
1222
+ throw new PluginNotFoundError("ykman is not installed", "ykman", YKMAN_INSTALL_URL);
1223
+ }
1224
+ throw new DeviceNotPresentError("No YubiKey device detected", DEVICE_TIMEOUT_MS);
1225
+ }
1226
+ }
1227
+ /**
1228
+ * Perform the YubiKey HMAC-SHA1 challenge-response for `id` and return the
1229
+ * raw hex response string. Throws on device failure.
1230
+ */
1231
+ async challengeResponse(id) {
1232
+ const challenge = Buffer.from(`vaultkeeper:${id}`, "utf8").toString("hex");
1233
+ const responseResult = await execCommandFull("ykman", ["otp", "calculate", "2", challenge]);
1234
+ if (responseResult.exitCode !== 0) {
1235
+ throw new Error(`YubiKey challenge-response failed: ${responseResult.stderr}`);
1236
+ }
1237
+ return responseResult.stdout.trim();
1238
+ }
1239
+ async store(id, secret) {
1240
+ await this.requireDevice();
1241
+ const storageDir = getStorageDir2();
1242
+ await fs.mkdir(storageDir, { recursive: true, mode: 448 });
1243
+ const hmacResponse = await this.challengeResponse(id);
1244
+ const key = deriveKey(hmacResponse, id);
1245
+ const encrypted = encryptGcm2(key, secret);
1246
+ const entryPath = getEntryPath3(storageDir, id);
1247
+ await fs.writeFile(entryPath, encrypted, { mode: 384 });
1248
+ const metadata = await loadMetadata(storageDir);
1249
+ metadata.entries[id] = entryPath;
1250
+ await saveMetadata(storageDir, metadata);
1251
+ }
1252
+ async retrieve(id) {
1253
+ await this.requireDevice();
1254
+ const storageDir = getStorageDir2();
1255
+ const entryPath = getEntryPath3(storageDir, id);
1256
+ try {
1257
+ await fs.access(entryPath);
1258
+ } catch {
1259
+ throw new SecretNotFoundError(`Secret not found in YubiKey store: ${id}`);
1260
+ }
1261
+ const encoded = await fs.readFile(entryPath, "utf8");
1262
+ const hmacResponse = await this.challengeResponse(id);
1263
+ const key = deriveKey(hmacResponse, id);
1264
+ return decryptGcm2(key, encoded);
1265
+ }
1266
+ async delete(id) {
1267
+ await this.requireDevice();
1268
+ const storageDir = getStorageDir2();
1269
+ const entryPath = getEntryPath3(storageDir, id);
1270
+ try {
1271
+ await fs.unlink(entryPath);
1272
+ } catch (err) {
1273
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1274
+ throw new SecretNotFoundError(`Secret not found in YubiKey store: ${id}`);
1275
+ }
1276
+ throw err;
1277
+ }
1278
+ const metadata = await loadMetadata(storageDir);
1279
+ delete metadata.entries[id];
1280
+ await saveMetadata(storageDir, metadata);
1281
+ }
1282
+ async exists(id) {
1283
+ const storageDir = getStorageDir2();
1284
+ const entryPath = getEntryPath3(storageDir, id);
1285
+ try {
1286
+ await fs.access(entryPath);
1287
+ return true;
1288
+ } catch {
1289
+ return false;
1290
+ }
1291
+ }
1292
+ async list() {
1293
+ const storageDir = getStorageDir2();
1294
+ const metadata = await loadMetadata(storageDir);
1295
+ return Object.keys(metadata.entries);
1296
+ }
1297
+ };
1298
+
1299
+ // src/backend/register-builtins.ts
1300
+ function registerBuiltinBackends() {
1301
+ BackendRegistry.register("file", () => new FileBackend());
1302
+ BackendRegistry.register("keychain", () => new KeychainBackend());
1303
+ BackendRegistry.register("dpapi", () => new DpapiBackend());
1304
+ BackendRegistry.register("secret-tool", () => new SecretToolBackend());
1305
+ BackendRegistry.register("1password", (config) => {
1306
+ const opts = config?.options;
1307
+ const vaultId = opts?.vault ?? "";
1308
+ const opOptions = {
1309
+ vault: vaultId,
1310
+ // 'session' is the safer default — 'per-access' re-prompts on every read.
1311
+ accessMode: opts?.accessMode === "per-access" ? "per-access" : "session"
1312
+ };
1313
+ const account = opts?.account;
1314
+ if (account !== void 0) {
1315
+ opOptions.account = account;
1316
+ }
1317
+ const serviceAccountToken = opts?.serviceAccountToken;
1318
+ if (serviceAccountToken !== void 0) {
1319
+ opOptions.serviceAccountToken = serviceAccountToken;
1320
+ }
1321
+ return new OnePasswordBackend(opOptions);
1322
+ });
1323
+ BackendRegistry.register("yubikey", () => new YubikeyBackend());
1324
+ }
1325
+ registerBuiltinBackends();
1326
+
1327
+ // src/backend/types.ts
1328
+ function isListableBackend(backend) {
1329
+ return "list" in backend && typeof backend.list === "function";
1330
+ }
330
1331
  function hashExecutable(filePath) {
331
- return new Promise((resolve, reject) => {
332
- const hash = crypto4.createHash("sha256");
1332
+ return new Promise((resolve2, reject) => {
1333
+ const hash = crypto.createHash("sha256");
333
1334
  const stream = fs4.createReadStream(filePath);
334
1335
  stream.on("data", (chunk) => {
335
1336
  hash.update(chunk);
336
1337
  });
337
1338
  stream.on("end", () => {
338
- resolve(hash.digest("hex"));
1339
+ resolve2(hash.digest("hex"));
339
1340
  });
340
1341
  stream.on("error", (err) => {
341
1342
  reject(err);
@@ -358,10 +1359,10 @@ function isTrustManifestEntry(value) {
358
1359
  return true;
359
1360
  }
360
1361
  async function loadManifest(configDir) {
361
- const manifestPath = path5.join(configDir, MANIFEST_FILENAME);
1362
+ const manifestPath = path3.join(configDir, MANIFEST_FILENAME);
362
1363
  let rawText;
363
1364
  try {
364
- rawText = await fs5.readFile(manifestPath, "utf8");
1365
+ rawText = await fs.readFile(manifestPath, "utf8");
365
1366
  } catch (err) {
366
1367
  if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
367
1368
  return /* @__PURE__ */ new Map();
@@ -381,14 +1382,14 @@ async function loadManifest(configDir) {
381
1382
  return manifest;
382
1383
  }
383
1384
  async function saveManifest(configDir, manifest) {
384
- await fs5.mkdir(configDir, { recursive: true });
1385
+ await fs.mkdir(configDir, { recursive: true });
385
1386
  const entries = {};
386
1387
  for (const [namespace, entry] of manifest) {
387
1388
  entries[namespace] = entry;
388
1389
  }
389
1390
  const raw = { version: 1, entries };
390
- const manifestPath = path5.join(configDir, MANIFEST_FILENAME);
391
- await fs5.writeFile(manifestPath, JSON.stringify(raw, null, 2), "utf8");
1391
+ const manifestPath = path3.join(configDir, MANIFEST_FILENAME);
1392
+ await fs.writeFile(manifestPath, JSON.stringify(raw, null, 2), "utf8");
392
1393
  }
393
1394
  function addTrustedHash(manifest, namespace, hash) {
394
1395
  const next = new Map(manifest);
@@ -499,14 +1500,18 @@ function validateCapabilityToken(token) {
499
1500
  return claims;
500
1501
  }
501
1502
  function getDefaultConfigDir() {
1503
+ const envOverride = process.env.VAULTKEEPER_CONFIG_DIR;
1504
+ if (envOverride !== void 0 && envOverride !== "") {
1505
+ return envOverride;
1506
+ }
502
1507
  if (process.platform === "win32") {
503
1508
  const appData = process.env.APPDATA;
504
1509
  if (appData !== void 0) {
505
- return path5.join(appData, "vaultkeeper");
1510
+ return path3.join(appData, "vaultkeeper");
506
1511
  }
507
- return path5.join(os4.homedir(), "AppData", "Roaming", "vaultkeeper");
1512
+ return path3.join(os4.homedir(), "AppData", "Roaming", "vaultkeeper");
508
1513
  }
509
- return path5.join(os4.homedir(), ".config", "vaultkeeper");
1514
+ return path3.join(os4.homedir(), ".config", "vaultkeeper");
510
1515
  }
511
1516
  function defaultConfig() {
512
1517
  return {
@@ -622,10 +1627,10 @@ function validateConfig(config) {
622
1627
  }
623
1628
  async function loadConfig(configDir) {
624
1629
  const dir = configDir ?? getDefaultConfigDir();
625
- const configPath = path5.join(dir, "config.json");
1630
+ const configPath = path3.join(dir, "config.json");
626
1631
  let raw;
627
1632
  try {
628
- raw = await fs5.readFile(configPath, "utf-8");
1633
+ raw = await fs.readFile(configPath, "utf-8");
629
1634
  } catch {
630
1635
  return defaultConfig();
631
1636
  }
@@ -644,10 +1649,10 @@ var KeyManager = class {
644
1649
  #rotating = false;
645
1650
  /** Generate a new 32-byte key with a timestamp-based id. */
646
1651
  generateKey() {
647
- const randomSuffix = crypto4.randomBytes(4).toString("hex");
1652
+ const randomSuffix = crypto.randomBytes(4).toString("hex");
648
1653
  return {
649
1654
  id: `k-${String(Date.now())}-${randomSuffix}`,
650
- key: new Uint8Array(crypto4.randomBytes(32)),
1655
+ key: new Uint8Array(crypto.randomBytes(32)),
651
1656
  createdAt: /* @__PURE__ */ new Date()
652
1657
  };
653
1658
  }
@@ -949,7 +1954,7 @@ function replaceInRecord2(record, secret) {
949
1954
  function delegatedExec(secret, request) {
950
1955
  const args = (request.args ?? []).map((arg) => replacePlaceholder2(arg, secret));
951
1956
  const env = request.env !== void 0 ? replaceInRecord2(request.env, secret) : void 0;
952
- return new Promise((resolve, reject) => {
1957
+ return new Promise((resolve2, reject) => {
953
1958
  const spawnOptions = {
954
1959
  stdio: ["ignore", "pipe", "pipe"]
955
1960
  };
@@ -969,7 +1974,7 @@ function delegatedExec(secret, request) {
969
1974
  stderr += data.toString();
970
1975
  });
971
1976
  proc.on("close", (code) => {
972
- resolve({ stdout, stderr, exitCode: code ?? 1 });
1977
+ resolve2({ stdout, stderr, exitCode: code ?? 1 });
973
1978
  });
974
1979
  proc.on("error", (error) => {
975
1980
  reject(error);
@@ -1083,10 +2088,10 @@ function resolveAlgorithmForKey(key, override) {
1083
2088
 
1084
2089
  // src/access/delegated-sign.ts
1085
2090
  function delegatedSign(secretPem, request) {
1086
- const key = crypto4.createPrivateKey(secretPem);
2091
+ const key = crypto.createPrivateKey(secretPem);
1087
2092
  const { signAlg, label } = resolveAlgorithmForKey(key, request.algorithm);
1088
2093
  const data = Buffer.isBuffer(request.data) ? request.data : Buffer.from(request.data);
1089
- const signature = crypto4.sign(signAlg, data, key);
2094
+ const signature = crypto.sign(signAlg, data, key);
1090
2095
  return {
1091
2096
  signature: signature.toString("base64"),
1092
2097
  algorithm: label
@@ -1095,7 +2100,7 @@ function delegatedSign(secretPem, request) {
1095
2100
  function delegatedVerify(request) {
1096
2101
  let key;
1097
2102
  try {
1098
- key = crypto4.createPublicKey(request.publicKey);
2103
+ key = crypto.createPublicKey(request.publicKey);
1099
2104
  } catch {
1100
2105
  return false;
1101
2106
  }
@@ -1106,7 +2111,7 @@ function delegatedVerify(request) {
1106
2111
  const sig = Buffer.from(request.signature, "base64");
1107
2112
  try {
1108
2113
  const data = Buffer.isBuffer(request.data) ? request.data : Buffer.from(request.data);
1109
- return crypto4.verify(signAlg, data, key, sig);
2114
+ return crypto.verify(signAlg, data, key, sig);
1110
2115
  } catch {
1111
2116
  return false;
1112
2117
  }
@@ -1364,7 +2369,7 @@ var VaultKeeper = class _VaultKeeper {
1364
2369
  }
1365
2370
  const now = Math.floor(Date.now() / 1e3);
1366
2371
  const claims = {
1367
- jti: crypto4.randomUUID(),
2372
+ jti: crypto.randomUUID(),
1368
2373
  exp: now + ttlMinutes * 60,
1369
2374
  iat: now,
1370
2375
  sub: secretName,
@@ -1590,7 +2595,7 @@ var VaultKeeper = class _VaultKeeper {
1590
2595
  []
1591
2596
  );
1592
2597
  }
1593
- return BackendRegistry.create(firstEnabled.type);
2598
+ return BackendRegistry.create(firstEnabled.type, firstEnabled);
1594
2599
  }
1595
2600
  #requireBackend() {
1596
2601
  if (this.#backend === void 0) {