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