@kitsy/cnos 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +3 -3
  2. package/dist/build/index.cjs +902 -113
  3. package/dist/build/index.d.cts +1 -1
  4. package/dist/build/index.d.ts +1 -1
  5. package/dist/build/index.js +22 -10
  6. package/dist/{chunk-WHUGFPE4.js → chunk-CDXJISGB.js} +1 -1
  7. package/dist/{chunk-APCTXRUN.js → chunk-DRKDNY4I.js} +998 -191
  8. package/dist/chunk-E7SE6N26.js +189 -0
  9. package/dist/{chunk-SO5XREEU.js → chunk-EDCLLCNL.js} +32 -11
  10. package/dist/{chunk-SXTMTACL.js → chunk-FC3IV6A7.js} +1 -31
  11. package/dist/{chunk-MLQGYCO7.js → chunk-JDII6O72.js} +1 -1
  12. package/dist/chunk-K6QYI2T4.js +105 -0
  13. package/dist/{chunk-EIN55XXA.js → chunk-OOKFRWTN.js} +1 -1
  14. package/dist/{chunk-ZA74BO47.js → chunk-OWUZQ4OH.js} +1 -1
  15. package/dist/{chunk-RD5WMHPM.js → chunk-QTKXPY3N.js} +1 -1
  16. package/dist/configure/index.cjs +2928 -0
  17. package/dist/configure/index.d.cts +12 -0
  18. package/dist/configure/index.d.ts +12 -0
  19. package/dist/configure/index.js +24 -0
  20. package/dist/{envNaming-BTJpH93W.d.cts → envNaming-D6k66myh.d.cts} +1 -1
  21. package/dist/{envNaming-CcsqAel3.d.ts → envNaming-Dy3WYiGK.d.ts} +1 -1
  22. package/dist/index.cjs +1142 -178
  23. package/dist/index.d.cts +2 -13
  24. package/dist/index.d.ts +2 -13
  25. package/dist/index.js +13 -25
  26. package/dist/internal.cjs +1512 -80
  27. package/dist/internal.d.cts +170 -14
  28. package/dist/internal.d.ts +170 -14
  29. package/dist/internal.js +645 -5
  30. package/dist/plugin/basic-schema.cjs +29 -2
  31. package/dist/plugin/basic-schema.d.cts +1 -1
  32. package/dist/plugin/basic-schema.d.ts +1 -1
  33. package/dist/plugin/basic-schema.js +2 -2
  34. package/dist/plugin/cli-args.cjs +29 -2
  35. package/dist/plugin/cli-args.d.cts +1 -1
  36. package/dist/plugin/cli-args.d.ts +1 -1
  37. package/dist/plugin/cli-args.js +2 -2
  38. package/dist/plugin/dotenv.cjs +36 -9
  39. package/dist/plugin/dotenv.d.cts +2 -2
  40. package/dist/plugin/dotenv.d.ts +2 -2
  41. package/dist/plugin/dotenv.js +2 -2
  42. package/dist/plugin/env-export.cjs +31 -2
  43. package/dist/plugin/env-export.d.cts +2 -2
  44. package/dist/plugin/env-export.d.ts +2 -2
  45. package/dist/plugin/env-export.js +2 -2
  46. package/dist/plugin/filesystem.cjs +46 -91
  47. package/dist/plugin/filesystem.d.cts +1 -1
  48. package/dist/plugin/filesystem.d.ts +1 -1
  49. package/dist/plugin/filesystem.js +2 -2
  50. package/dist/plugin/process-env.cjs +31 -4
  51. package/dist/plugin/process-env.d.cts +2 -2
  52. package/dist/plugin/process-env.d.ts +2 -2
  53. package/dist/plugin/process-env.js +2 -2
  54. package/dist/{plugin-DkOIT5uI.d.cts → plugin-CyNkf7Dm.d.cts} +14 -2
  55. package/dist/{plugin-DkOIT5uI.d.ts → plugin-CyNkf7Dm.d.ts} +14 -2
  56. package/dist/runtime/index.cjs +956 -128
  57. package/dist/runtime/index.d.cts +1 -1
  58. package/dist/runtime/index.d.ts +1 -1
  59. package/dist/runtime/index.js +11 -186
  60. package/dist/{toPublicEnv-DvFeV3qG.d.cts → toPublicEnv-Cz72m6y0.d.cts} +1 -1
  61. package/dist/{toPublicEnv-C9clvXLo.d.ts → toPublicEnv-D2PZkaN-.d.ts} +1 -1
  62. package/package.json +11 -1
  63. package/dist/chunk-JUHPBAEH.js +0 -20
  64. package/dist/chunk-PQ4KSV76.js +0 -50
@@ -17,6 +17,11 @@ var CnosSecurityError = class extends CnosError {
17
17
  super(message);
18
18
  }
19
19
  };
20
+ var CnosAuthenticationError = class extends CnosError {
21
+ constructor(message) {
22
+ super(message);
23
+ }
24
+ };
20
25
  var CnosKeyNotFoundError = class extends CnosError {
21
26
  constructor(key) {
22
27
  super(`Missing required CNOS config key: ${key}`);
@@ -58,6 +63,112 @@ function inspectValue(graph, key) {
58
63
  };
59
64
  }
60
65
 
66
+ // ../core/src/keychain/linux.ts
67
+ import { execFile, spawn } from "child_process";
68
+ import { promisify } from "util";
69
+ var execFileAsync = promisify(execFile);
70
+ async function readLinuxKeychain(entry) {
71
+ try {
72
+ const { stdout } = await execFileAsync("secret-tool", ["lookup", "service", "cnos", "account", entry]);
73
+ const value = stdout.trim();
74
+ return value.length > 0 ? value : void 0;
75
+ } catch {
76
+ return void 0;
77
+ }
78
+ }
79
+ async function writeLinuxKeychain(entry, value) {
80
+ await new Promise((resolve, reject) => {
81
+ const child = spawn("secret-tool", ["store", "--label", `CNOS ${entry}`, "service", "cnos", "account", entry], {
82
+ stdio: ["pipe", "ignore", "pipe"]
83
+ });
84
+ let stderr = "";
85
+ child.stdin?.end(value);
86
+ child.stderr?.on("data", (chunk) => {
87
+ stderr += chunk.toString();
88
+ });
89
+ child.on("error", reject);
90
+ child.on("close", (code) => {
91
+ if (code === 0) {
92
+ resolve();
93
+ return;
94
+ }
95
+ reject(new Error(stderr || `secret-tool exited with code ${code ?? 1}`));
96
+ });
97
+ });
98
+ }
99
+
100
+ // ../core/src/keychain/macos.ts
101
+ import { execFile as execFile2 } from "child_process";
102
+ import { promisify as promisify2 } from "util";
103
+ var execFileAsync2 = promisify2(execFile2);
104
+ async function readMacosKeychain(entry) {
105
+ try {
106
+ const { stdout } = await execFileAsync2("security", ["find-generic-password", "-a", "cnos", "-s", entry, "-w"]);
107
+ const value = stdout.trim();
108
+ return value.length > 0 ? value : void 0;
109
+ } catch {
110
+ return void 0;
111
+ }
112
+ }
113
+ async function writeMacosKeychain(entry, value) {
114
+ await execFileAsync2("security", ["add-generic-password", "-a", "cnos", "-s", entry, "-w", value, "-U"]);
115
+ }
116
+
117
+ // ../core/src/keychain/windows.ts
118
+ import { execFile as execFile3 } from "child_process";
119
+ import { promisify as promisify3 } from "util";
120
+ var execFileAsync3 = promisify3(execFile3);
121
+ function wrap(script) {
122
+ return ["-NoProfile", "-Command", script];
123
+ }
124
+ async function readWindowsKeychain(entry) {
125
+ try {
126
+ const { stdout } = await execFileAsync3(
127
+ "powershell",
128
+ wrap(`Add-Type -AssemblyName System.Runtime.WindowsRuntime; [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime] > $null; $vault = New-Object Windows.Security.Credentials.PasswordVault; $credential = $vault.Retrieve('cnos','${entry}'); $credential.RetrievePassword(); Write-Output $credential.Password`)
129
+ );
130
+ const value = stdout.trim();
131
+ return value.length > 0 ? value : void 0;
132
+ } catch {
133
+ return void 0;
134
+ }
135
+ }
136
+ async function writeWindowsKeychain(entry, value) {
137
+ await execFileAsync3(
138
+ "powershell",
139
+ wrap(`Add-Type -AssemblyName System.Runtime.WindowsRuntime; [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime] > $null; $vault = New-Object Windows.Security.Credentials.PasswordVault; try { $existing = $vault.Retrieve('cnos','${entry}'); $vault.Remove($existing) } catch {}; $credential = New-Object Windows.Security.Credentials.PasswordCredential('cnos','${entry}','${value}'); $vault.Add($credential)`)
140
+ );
141
+ }
142
+
143
+ // ../core/src/keychain/index.ts
144
+ async function readKeychain(entry) {
145
+ if (process.platform === "win32") {
146
+ return readWindowsKeychain(entry);
147
+ }
148
+ if (process.platform === "darwin") {
149
+ return readMacosKeychain(entry);
150
+ }
151
+ if (process.platform === "linux") {
152
+ return readLinuxKeychain(entry);
153
+ }
154
+ return void 0;
155
+ }
156
+ async function writeKeychain(entry, value) {
157
+ if (process.platform === "win32") {
158
+ await writeWindowsKeychain(entry, value);
159
+ return;
160
+ }
161
+ if (process.platform === "darwin") {
162
+ await writeMacosKeychain(entry, value);
163
+ return;
164
+ }
165
+ if (process.platform === "linux") {
166
+ await writeLinuxKeychain(entry, value);
167
+ return;
168
+ }
169
+ throw new CnosAuthenticationError(`OS keychain is not supported on platform "${process.platform}".`);
170
+ }
171
+
61
172
  // ../core/src/utils/path.ts
62
173
  import { access } from "fs/promises";
63
174
  import os from "os";
@@ -246,22 +357,82 @@ function normalizeNamespaces(namespaces) {
246
357
  function normalizeVaults(vaults) {
247
358
  return Object.fromEntries(
248
359
  Object.entries(vaults ?? {}).map(([name, definition]) => {
360
+ const legacyPassphrase = definition.passphrase;
361
+ if (legacyPassphrase !== void 0) {
362
+ throw new CnosManifestError(
363
+ `Vault "${name}" uses legacy passphrase configuration. Use vaults.${name}.auth instead.`
364
+ );
365
+ }
249
366
  const provider = definition.provider?.trim();
250
367
  if (!provider) {
251
368
  throw new CnosManifestError(`Vault "${name}" requires a provider`);
252
369
  }
370
+ const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
371
+ const normalizedMapping = Object.fromEntries(
372
+ Object.entries(definition.mapping ?? {}).filter(
373
+ (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
374
+ ).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
375
+ );
253
376
  return [
254
377
  name,
255
378
  {
256
379
  provider,
257
- ...definition.passphrase?.trim() ? {
258
- passphrase: definition.passphrase.trim()
380
+ auth: normalizedAuth,
381
+ ...Object.keys(normalizedMapping).length > 0 ? {
382
+ mapping: normalizedMapping
259
383
  } : {}
260
384
  }
261
385
  ];
262
386
  })
263
387
  );
264
388
  }
389
+ function normalizeAuthSources(value) {
390
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
391
+ return void 0;
392
+ }
393
+ const sources = Array.isArray(value.from) ? value.from : void 0;
394
+ const normalized = (sources ?? []).map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
395
+ return normalized.length > 0 ? normalized : void 0;
396
+ }
397
+ function normalizeVaultAuth(vaultName, provider, auth) {
398
+ if (provider === "local") {
399
+ const passphraseSources = normalizeAuthSources(auth?.passphrase);
400
+ const defaultToken = vaultName.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
401
+ const defaultSources = [
402
+ ...defaultToken ? [`env:CNOS_SECRET_PASSPHRASE_${defaultToken}`] : [],
403
+ "env:CNOS_SECRET_PASSPHRASE",
404
+ `keychain:cnos/${vaultName}`,
405
+ "prompt"
406
+ ];
407
+ return {
408
+ method: auth?.method ?? "passphrase",
409
+ passphrase: {
410
+ from: passphraseSources ?? defaultSources
411
+ },
412
+ ...auth?.config ? { config: auth.config } : {}
413
+ };
414
+ }
415
+ if (provider === "github-secrets") {
416
+ return {
417
+ method: auth?.method ?? "environment",
418
+ ...auth?.config ? { config: auth.config } : {}
419
+ };
420
+ }
421
+ return {
422
+ ...auth?.method ? { method: auth.method } : {},
423
+ ...normalizeAuthSources(auth?.passphrase) ? {
424
+ passphrase: {
425
+ from: normalizeAuthSources(auth?.passphrase) ?? []
426
+ }
427
+ } : {},
428
+ ...normalizeAuthSources(auth?.token) ? {
429
+ token: {
430
+ from: normalizeAuthSources(auth?.token) ?? []
431
+ }
432
+ } : {},
433
+ ...auth?.config ? { config: auth.config } : {}
434
+ };
435
+ }
265
436
  function normalizeManifest(manifest) {
266
437
  const version = manifest.version ?? 1;
267
438
  if (version !== 1) {
@@ -432,60 +603,65 @@ function validateProjectionIssue(manifest, key, target) {
432
603
  }
433
604
  }
434
605
 
435
- // ../core/src/runtime/projection.ts
436
- function setNestedValue(target, pathSegments, value) {
437
- const [head, ...tail] = pathSegments;
438
- if (!head) {
439
- return;
440
- }
441
- if (tail.length === 0) {
442
- target[head] = value;
443
- return;
444
- }
445
- const current = target[head];
446
- const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
447
- target[head] = nextTarget;
448
- setNestedValue(nextTarget, tail, value);
606
+ // ../core/src/secrets/sessionStore.ts
607
+ import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "fs/promises";
608
+ import path3 from "path";
609
+ function buildSessionRoot(processEnv = process.env) {
610
+ return path3.join(path3.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets")), "sessions");
449
611
  }
450
- function toNamespaceObject(graph, namespace) {
451
- const output = {};
452
- const resolvedEntries = Array.from(graph.entries.values()).sort(
453
- (left, right) => left.key.localeCompare(right.key)
454
- );
455
- for (const entry of resolvedEntries) {
456
- if (namespace && entry.namespace !== namespace) {
457
- continue;
612
+ function buildSessionPath(vault, processEnv) {
613
+ return path3.join(buildSessionRoot(processEnv), `${vault}.json`);
614
+ }
615
+ async function writeVaultSessionKey(vault, derivedKey, processEnv) {
616
+ const filePath = buildSessionPath(vault, processEnv);
617
+ await mkdir(path3.dirname(filePath), { recursive: true });
618
+ const document = {
619
+ version: 1,
620
+ vault,
621
+ derivedKey: derivedKey.toString("hex"),
622
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
623
+ };
624
+ await writeFile(filePath, JSON.stringify(document, null, 2), "utf8");
625
+ return filePath;
626
+ }
627
+ async function readVaultSessionKey(vault, processEnv) {
628
+ try {
629
+ const source = await readFile2(buildSessionPath(vault, processEnv), "utf8");
630
+ const document = JSON.parse(source);
631
+ if (document.version !== 1 || typeof document.derivedKey !== "string") {
632
+ return void 0;
458
633
  }
459
- const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
460
- setNestedValue(output, valuePath.split("."), entry.value);
634
+ const key = Buffer.from(document.derivedKey, "hex");
635
+ return key.length > 0 ? key : void 0;
636
+ } catch {
637
+ return void 0;
461
638
  }
462
- return output;
463
639
  }
464
-
465
- // ../core/src/runtime/read.ts
466
- function readValue(graph, key) {
467
- return graph.entries.get(key)?.value;
468
- }
469
-
470
- // ../core/src/runtime/readOr.ts
471
- function readOrValue(graph, key, fallback) {
472
- const value = readValue(graph, key);
473
- return value === void 0 ? fallback : value;
640
+ async function clearVaultSessionKey(vault, processEnv) {
641
+ await rm(buildSessionPath(vault, processEnv), { force: true });
474
642
  }
475
-
476
- // ../core/src/runtime/require.ts
477
- function requireValue(graph, key) {
478
- const value = readValue(graph, key);
479
- if (value === void 0) {
480
- throw new CnosKeyNotFoundError(key);
643
+ async function clearAllVaultSessionKeys(processEnv) {
644
+ const root = buildSessionRoot(processEnv);
645
+ try {
646
+ const entries = await readdir(root);
647
+ await Promise.all(entries.map((entry) => rm(path3.join(root, entry), { force: true })));
648
+ } catch {
481
649
  }
482
- return value;
483
650
  }
484
651
 
485
652
  // ../core/src/utils/secretStore.ts
486
- import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
487
- import { mkdir, readdir, readFile as readFile2, writeFile } from "fs/promises";
488
- import path3 from "path";
653
+ import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
654
+ import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2, stat, writeFile as writeFile2 } from "fs/promises";
655
+ import path4 from "path";
656
+ var KEY_LENGTH = 32;
657
+ var SALT_LENGTH = 32;
658
+ var IV_LENGTH = 12;
659
+ var AUTH_TAG_LENGTH = 16;
660
+ var PBKDF2_ITERATIONS = 6e5;
661
+ var KEYSTORE_VERSION = 1;
662
+ var METADATA_VERSION = 1;
663
+ var META_FILENAME = "meta.yml";
664
+ var KEYSTORE_FILENAME = "keystore.enc";
489
665
  function isObject(value) {
490
666
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
491
667
  }
@@ -493,135 +669,657 @@ function isSecretReference(value) {
493
669
  return isObject(value) && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.ref === "string" && value.ref.trim().length > 0 && (value.vault === void 0 && true || typeof value.vault === "string" && value.vault.trim().length > 0) && Object.keys(value).every((key) => ["provider", "ref", "vault"].includes(key));
494
670
  }
495
671
  function resolveSecretStoreRoot(processEnv = process.env) {
496
- return path3.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
497
- }
498
- function resolveSecretVaultFile(storeRoot, vault = "default") {
499
- return path3.join(storeRoot, "vaults", `${vault}.json`);
500
- }
501
- function resolveSecretStoreFile(storeRoot, ref, vault = "default") {
502
- return path3.join(storeRoot, "vaults", vault, "store", ...ref.split("/")).concat(".json");
672
+ return path4.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
503
673
  }
504
- function deriveKey(passphrase, salt) {
505
- return scryptSync(passphrase, salt, 32);
506
- }
507
- function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
508
- const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
509
- return processEnv[`CNOS_SECRET_PASSPHRASE_${vaultToken}`] ?? processEnv.CNOS_SECRET_PASSPHRASE;
674
+ function normalizeVaultToken(vault = "default") {
675
+ return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
510
676
  }
511
677
  function getVaultPassphraseEnvVar(vault = "default") {
512
- const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
678
+ const vaultToken = normalizeVaultToken(vault);
513
679
  return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
514
680
  }
515
681
  function isPassphraseEnvRef(value) {
516
682
  return typeof value === "string" && value.startsWith("env:") && value.length > 4;
517
683
  }
518
- function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
519
- if (definition?.provider !== "local") {
684
+ function getVaultSessionKeyEnvVar(vault = "default") {
685
+ const vaultToken = normalizeVaultToken(vault);
686
+ return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
687
+ }
688
+ function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
689
+ return processEnv[getVaultPassphraseEnvVar(vault)] ?? processEnv.CNOS_SECRET_PASSPHRASE;
690
+ }
691
+ function resolveVaultSessionKey(vault = "default", processEnv = process.env) {
692
+ const encoded = processEnv[getVaultSessionKeyEnvVar(vault)];
693
+ if (!encoded) {
694
+ return readVaultSessionKey(vault, processEnv);
695
+ }
696
+ try {
697
+ const key = Buffer.from(encoded, "hex");
698
+ return key.length === KEY_LENGTH ? key : void 0;
699
+ } catch {
520
700
  return void 0;
521
701
  }
522
- const passphraseRef = definition.passphrase;
523
- if (typeof passphraseRef === "string" && passphraseRef.startsWith("env:") && passphraseRef.length > 4) {
524
- return processEnv[passphraseRef.slice(4)];
702
+ }
703
+ function deriveVaultKey(passphrase, salt, iterations = PBKDF2_ITERATIONS) {
704
+ return pbkdf2Sync(passphrase, salt, iterations, KEY_LENGTH, "sha512");
705
+ }
706
+ function buildMetaPath(storeRoot, vault = "default") {
707
+ return path4.join(storeRoot, "vaults", vault, META_FILENAME);
708
+ }
709
+ function resolveSecretVaultFile(storeRoot, vault = "default") {
710
+ return buildMetaPath(storeRoot, vault);
711
+ }
712
+ function buildKeystorePath(storeRoot, vault = "default") {
713
+ return path4.join(storeRoot, "vaults", vault, KEYSTORE_FILENAME);
714
+ }
715
+ function buildLegacyVaultFile(storeRoot, vault = "default") {
716
+ return path4.join(storeRoot, "vaults", `${vault}.json`);
717
+ }
718
+ function buildLegacyVaultStoreRoot(storeRoot, vault = "default") {
719
+ return path4.join(storeRoot, "vaults", vault, "store");
720
+ }
721
+ function assertVaultMetadata(value, filePath) {
722
+ if (!isObject(value)) {
723
+ throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
525
724
  }
526
- if (passphraseRef) {
527
- return passphraseRef;
725
+ if (value.version !== METADATA_VERSION || value.algorithm !== "aes-256-gcm" || value.kdf !== "pbkdf2-sha512" || typeof value.iterations !== "number" || typeof value.salt !== "string" || typeof value.createdAt !== "string" || typeof value.secretCount !== "number") {
726
+ throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
528
727
  }
529
- return resolveSecretPassphrase(vault, processEnv);
728
+ return value;
530
729
  }
531
- function resolveVaultDefinition(vaults, vault = "default") {
532
- const definition = vaults?.[vault];
533
- const provider = definition?.provider ?? "local";
534
- return {
535
- name: vault,
536
- provider,
537
- ...definition?.passphrase ? {
538
- passphrase: definition.passphrase
539
- } : {},
540
- requiresPassphrase: provider === "local"
541
- };
730
+ async function exists2(targetPath) {
731
+ try {
732
+ await stat(targetPath);
733
+ return true;
734
+ } catch {
735
+ return false;
736
+ }
737
+ }
738
+ async function detectLegacyVaultFormat(storeRoot, vault = "default") {
739
+ const legacyFile = buildLegacyVaultFile(storeRoot, vault);
740
+ const legacyStore = buildLegacyVaultStoreRoot(storeRoot, vault);
741
+ if (await exists2(legacyFile)) {
742
+ return legacyFile;
743
+ }
744
+ if (await exists2(legacyStore)) {
745
+ return legacyStore;
746
+ }
747
+ return void 0;
748
+ }
749
+ async function assertNoLegacyVaultFormat(storeRoot, vault = "default") {
750
+ const legacyPath = await detectLegacyVaultFormat(storeRoot, vault);
751
+ if (!legacyPath) {
752
+ return;
753
+ }
754
+ throw new CnosSecurityError(
755
+ `Legacy CNOS local vault format detected for vault "${vault}" at ${legacyPath}. CNOS 1.4 requires the new keystore format. Remove and recreate the vault.`
756
+ );
542
757
  }
543
- function encryptDocument(value, passphrase) {
544
- const salt = randomBytes(16);
545
- const iv = randomBytes(12);
546
- const key = deriveKey(passphrase, salt);
758
+ function encryptPayload(payload, key) {
759
+ const iv = randomBytes(IV_LENGTH);
547
760
  const cipher = createCipheriv("aes-256-gcm", key, iv);
548
- const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
761
+ const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
762
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
549
763
  const tag = cipher.getAuthTag();
550
- return {
551
- version: 1,
552
- algorithm: "aes-256-gcm",
553
- salt: salt.toString("base64"),
554
- iv: iv.toString("base64"),
555
- tag: tag.toString("base64"),
556
- ciphertext: ciphertext.toString("base64")
557
- };
764
+ return Buffer.concat([
765
+ Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
766
+ iv,
767
+ tag,
768
+ ciphertext
769
+ ]);
558
770
  }
559
- function decryptDocument(document, passphrase) {
560
- const salt = Buffer.from(document.salt, "base64");
561
- const iv = Buffer.from(document.iv, "base64");
562
- const tag = Buffer.from(document.tag, "base64");
563
- const ciphertext = Buffer.from(document.ciphertext, "base64");
564
- const key = deriveKey(passphrase, salt);
771
+ function decryptPayload(buffer, key) {
772
+ if (buffer.length < 4 + IV_LENGTH + AUTH_TAG_LENGTH) {
773
+ throw new CnosSecurityError("Invalid CNOS local vault keystore");
774
+ }
775
+ const version = buffer.readUInt32LE(0);
776
+ if (version !== KEYSTORE_VERSION) {
777
+ throw new CnosSecurityError(`Unsupported CNOS local vault keystore version: ${version}`);
778
+ }
779
+ const ivOffset = 4;
780
+ const tagOffset = ivOffset + IV_LENGTH;
781
+ const cipherOffset = tagOffset + AUTH_TAG_LENGTH;
782
+ const iv = buffer.subarray(ivOffset, tagOffset);
783
+ const tag = buffer.subarray(tagOffset, cipherOffset);
784
+ const ciphertext = buffer.subarray(cipherOffset);
565
785
  const decipher = createDecipheriv("aes-256-gcm", key, iv);
566
786
  decipher.setAuthTag(tag);
567
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
568
- return plaintext.toString("utf8");
787
+ try {
788
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
789
+ const payload = JSON.parse(plaintext);
790
+ if (!payload || !isObject(payload.secrets) || !isObject(payload.metadata)) {
791
+ throw new Error("invalid");
792
+ }
793
+ return {
794
+ secrets: Object.fromEntries(
795
+ Object.entries(payload.secrets).filter((entry) => typeof entry[1] === "string")
796
+ ),
797
+ metadata: Object.fromEntries(
798
+ Object.entries(payload.metadata).filter(
799
+ (entry) => isObject(entry[1]) && typeof entry[1].createdAt === "string" && typeof entry[1].updatedAt === "string"
800
+ )
801
+ )
802
+ };
803
+ } catch {
804
+ throw new CnosAuthenticationError("Failed to decrypt CNOS local vault. Check vault authentication.");
805
+ }
569
806
  }
570
- async function createSecretVault(storeRoot, vault, passphrase) {
571
- const normalizedVault = vault.trim() || "default";
572
- const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
573
- await mkdir(path3.dirname(filePath), { recursive: true });
574
- const document = {
575
- version: 1,
576
- name: normalizedVault,
577
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
578
- verifier: encryptDocument(`cnos-vault:${normalizedVault}`, passphrase)
807
+ function buildInitialPayload() {
808
+ return {
809
+ secrets: {},
810
+ metadata: {}
579
811
  };
580
- await writeFile(filePath, JSON.stringify(document, null, 2), "utf8");
581
- return filePath;
582
812
  }
583
- async function ensureSecretVault(storeRoot, vault, passphrase) {
584
- const normalizedVault = vault.trim() || "default";
585
- const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
813
+ async function writeVaultFiles(storeRoot, vault, meta, payload, key) {
814
+ const metaPath = buildMetaPath(storeRoot, vault);
815
+ const keystorePath = buildKeystorePath(storeRoot, vault);
816
+ await mkdir2(path4.dirname(metaPath), { recursive: true });
817
+ await writeFile2(metaPath, stringifyYaml(meta), "utf8");
818
+ await writeFile2(keystorePath, encryptPayload(payload, key));
819
+ }
820
+ async function readVaultMetadata(storeRoot, vault = "default") {
821
+ await assertNoLegacyVaultFormat(storeRoot, vault);
822
+ const metaPath = buildMetaPath(storeRoot, vault);
586
823
  try {
587
- await readFile2(filePath, "utf8");
588
- return filePath;
824
+ const source = await readFile3(metaPath, "utf8");
825
+ return assertVaultMetadata(parseYaml(source), metaPath);
589
826
  } catch (error) {
590
- if (error.code !== "ENOENT") {
591
- throw error;
827
+ if (error.code === "ENOENT") {
828
+ return void 0;
592
829
  }
830
+ throw error;
593
831
  }
594
- return createSecretVault(storeRoot, normalizedVault, passphrase);
595
832
  }
596
833
  async function listSecretVaults(storeRoot) {
597
- const vaultRoot = path3.join(storeRoot, "vaults");
834
+ const vaultRoot = path4.join(storeRoot, "vaults");
598
835
  try {
599
- const entries = await readdir(vaultRoot, { withFileTypes: true });
600
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.replace(/\.json$/, "")).sort((left, right) => left.localeCompare(right));
836
+ const entries = await readdir2(vaultRoot, { withFileTypes: true });
837
+ const vaults = await Promise.all(
838
+ entries.filter((entry) => entry.isDirectory()).map(async (entry) => await exists2(path4.join(vaultRoot, entry.name, META_FILENAME)) ? entry.name : void 0)
839
+ );
840
+ return vaults.filter((value) => Boolean(value)).sort((left, right) => left.localeCompare(right));
601
841
  } catch {
602
842
  return [];
603
843
  }
604
844
  }
605
- async function writeLocalSecret(storeRoot, ref, value, passphrase, vault = "default") {
606
- await ensureSecretVault(storeRoot, vault, passphrase);
607
- const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
608
- await mkdir(path3.dirname(filePath), { recursive: true });
609
- await writeFile(filePath, JSON.stringify(encryptDocument(value, passphrase), null, 2), "utf8");
610
- return filePath;
845
+ async function createSecretVault(storeRoot, vault, passphrase) {
846
+ const normalizedVault = vault.trim() || "default";
847
+ await assertNoLegacyVaultFormat(storeRoot, normalizedVault);
848
+ const salt = randomBytes(SALT_LENGTH);
849
+ const key = deriveVaultKey(passphrase, salt, PBKDF2_ITERATIONS);
850
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
851
+ const meta = {
852
+ version: METADATA_VERSION,
853
+ algorithm: "aes-256-gcm",
854
+ kdf: "pbkdf2-sha512",
855
+ iterations: PBKDF2_ITERATIONS,
856
+ salt: salt.toString("base64"),
857
+ createdAt,
858
+ secretCount: 0
859
+ };
860
+ await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
861
+ return buildMetaPath(storeRoot, normalizedVault);
611
862
  }
612
- async function readLocalSecret(storeRoot, ref, passphrase, vault = "default") {
613
- if (!passphrase) {
614
- throw new CnosManifestError(
615
- `Missing CNOS secret passphrase for local secret ref "${ref}". Set CNOS_SECRET_PASSPHRASE or pass processEnv explicitly.`
863
+ async function ensureSecretVault(storeRoot, vault, passphrase) {
864
+ const normalizedVault = vault.trim() || "default";
865
+ const meta = await readVaultMetadata(storeRoot, normalizedVault);
866
+ if (meta) {
867
+ return buildMetaPath(storeRoot, normalizedVault);
868
+ }
869
+ return createSecretVault(storeRoot, normalizedVault, passphrase);
870
+ }
871
+ function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
872
+ if (definition?.provider !== "local") {
873
+ return void 0;
874
+ }
875
+ const configuredSources = definition.auth?.passphrase?.from ?? [];
876
+ for (const source of configuredSources) {
877
+ if (source.startsWith("env:")) {
878
+ const value = processEnv[source.slice(4)];
879
+ if (value) {
880
+ return value;
881
+ }
882
+ }
883
+ }
884
+ return resolveSecretPassphrase(vault, processEnv);
885
+ }
886
+ async function resolveVaultAccessKey(storeRoot, definition, vault = "default", processEnv = process.env) {
887
+ if (definition?.provider !== "local") {
888
+ return definition?.provider === "github-secrets" ? {
889
+ method: definition.auth?.method ?? "environment",
890
+ ...definition?.auth?.config ? { config: definition.auth.config } : {}
891
+ } : void 0;
892
+ }
893
+ const sessionKey = await resolveVaultSessionKey(vault, processEnv);
894
+ if (sessionKey) {
895
+ return {
896
+ derivedKey: sessionKey,
897
+ method: "keychain",
898
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
899
+ };
900
+ }
901
+ const passphrase = resolveConfiguredVaultPassphrase(definition, vault, processEnv);
902
+ if (passphrase) {
903
+ return {
904
+ passphrase,
905
+ method: "passphrase",
906
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
907
+ };
908
+ }
909
+ const metadata = await readVaultMetadata(storeRoot, vault);
910
+ if (!metadata) {
911
+ return void 0;
912
+ }
913
+ throw new CnosAuthenticationError(
914
+ `Cannot authenticate to vault "${vault}". Set ${getVaultPassphraseEnvVar(vault)} or run cnos vault auth ${vault}.`
915
+ );
916
+ }
917
+ async function loadVaultPayload(storeRoot, vault, auth) {
918
+ const meta = await readVaultMetadata(storeRoot, vault);
919
+ if (!meta) {
920
+ throw new CnosManifestError(`Missing CNOS vault metadata for "${vault}"`);
921
+ }
922
+ const salt = Buffer.from(meta.salt, "base64");
923
+ const key = auth.derivedKey ?? (auth.passphrase ? deriveVaultKey(auth.passphrase, salt, meta.iterations) : void 0);
924
+ if (!key) {
925
+ throw new CnosAuthenticationError(`Vault "${vault}" requires authentication before access.`);
926
+ }
927
+ const buffer = await readFile3(buildKeystorePath(storeRoot, vault));
928
+ return {
929
+ meta,
930
+ payload: decryptPayload(buffer, key),
931
+ key
932
+ };
933
+ }
934
+ async function writeLocalSecret(storeRoot, ref, value, authOrPassphrase, vault = "default") {
935
+ const auth = typeof authOrPassphrase === "string" ? {
936
+ passphrase: authOrPassphrase,
937
+ method: "passphrase"
938
+ } : authOrPassphrase;
939
+ if (auth.passphrase) {
940
+ await ensureSecretVault(storeRoot, vault, auth.passphrase);
941
+ } else {
942
+ const meta2 = await readVaultMetadata(storeRoot, vault);
943
+ if (!meta2) {
944
+ throw new CnosAuthenticationError(`Vault "${vault}" requires passphrase-based authentication for initial creation.`);
945
+ }
946
+ }
947
+ const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
948
+ const now = (/* @__PURE__ */ new Date()).toISOString();
949
+ const existing = payload.metadata[ref];
950
+ payload.secrets[ref] = value;
951
+ payload.metadata[ref] = {
952
+ createdAt: existing?.createdAt ?? now,
953
+ updatedAt: now
954
+ };
955
+ const nextMeta = {
956
+ ...meta,
957
+ secretCount: Object.keys(payload.secrets).length
958
+ };
959
+ await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
960
+ return buildKeystorePath(storeRoot, vault);
961
+ }
962
+ async function deleteLocalSecret(storeRoot, ref, auth, vault = "default") {
963
+ const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
964
+ if (!(ref in payload.secrets)) {
965
+ return false;
966
+ }
967
+ delete payload.secrets[ref];
968
+ delete payload.metadata[ref];
969
+ const nextMeta = {
970
+ ...meta,
971
+ secretCount: Object.keys(payload.secrets).length
972
+ };
973
+ await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
974
+ return true;
975
+ }
976
+ async function readLocalSecret(storeRoot, ref, auth, vault = "default") {
977
+ const { payload } = await loadVaultPayload(storeRoot, vault, auth);
978
+ const value = payload.secrets[ref];
979
+ if (value === void 0) {
980
+ throw new CnosManifestError(`Missing local secret ref "${ref}" in vault "${vault}"`);
981
+ }
982
+ return value;
983
+ }
984
+ async function listLocalSecrets(storeRoot, auth, vault = "default") {
985
+ const { payload } = await loadVaultPayload(storeRoot, vault, auth);
986
+ return Object.keys(payload.secrets).sort((left, right) => left.localeCompare(right));
987
+ }
988
+ function resolveVaultDefinition(vaults, vault = "default") {
989
+ const definition = vaults?.[vault];
990
+ const provider = definition?.provider ?? "local";
991
+ return {
992
+ name: vault,
993
+ provider,
994
+ ...definition?.auth ? { auth: definition.auth } : {},
995
+ ...definition?.mapping ? { mapping: definition.mapping } : {},
996
+ requiresAuthentication: provider === "local"
997
+ };
998
+ }
999
+ async function removeLocalVaultFiles(storeRoot, vault = "default") {
1000
+ await rm2(path4.join(storeRoot, "vaults", vault), { recursive: true, force: true });
1001
+ }
1002
+
1003
+ // ../core/src/secrets/providers/github.ts
1004
+ var GithubSecretsVaultProvider = class {
1005
+ constructor(vaultId, definition, processEnv = process.env) {
1006
+ this.vaultId = vaultId;
1007
+ this.definition = definition;
1008
+ this.processEnv = processEnv;
1009
+ }
1010
+ vaultId;
1011
+ definition;
1012
+ processEnv;
1013
+ authenticated = false;
1014
+ async authenticate(_authConfig) {
1015
+ void _authConfig;
1016
+ this.authenticated = true;
1017
+ }
1018
+ isAuthenticated() {
1019
+ return this.authenticated;
1020
+ }
1021
+ resolveEnvVar(ref) {
1022
+ if (this.processEnv[ref] !== void 0) {
1023
+ return ref;
1024
+ }
1025
+ return Object.entries(this.definition.mapping ?? {}).find(([, logicalRef]) => logicalRef === ref)?.[0];
1026
+ }
1027
+ async batchGet(refs) {
1028
+ this.authenticated = true;
1029
+ const resolved = /* @__PURE__ */ new Map();
1030
+ for (const ref of Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right))) {
1031
+ const envVar = this.resolveEnvVar(ref);
1032
+ const value = envVar ? this.processEnv[envVar] : void 0;
1033
+ if (value !== void 0) {
1034
+ resolved.set(ref, value);
1035
+ }
1036
+ }
1037
+ return resolved;
1038
+ }
1039
+ async get(ref) {
1040
+ const envVar = this.resolveEnvVar(ref);
1041
+ this.authenticated = true;
1042
+ return envVar ? this.processEnv[envVar] : void 0;
1043
+ }
1044
+ async set(ref, value) {
1045
+ void ref;
1046
+ void value;
1047
+ throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be written by CNOS.`);
1048
+ }
1049
+ async delete(ref) {
1050
+ void ref;
1051
+ throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be mutated by CNOS.`);
1052
+ }
1053
+ async list() {
1054
+ return Object.values(this.definition.mapping ?? {}).sort((left, right) => left.localeCompare(right));
1055
+ }
1056
+ };
1057
+
1058
+ // ../core/src/secrets/auditLog.ts
1059
+ import { appendFile, mkdir as mkdir3 } from "fs/promises";
1060
+ import path5 from "path";
1061
+ async function appendAuditEvent(event, processEnv = process.env) {
1062
+ const auditFile = processEnv.CNOS_AUDIT_FILE ?? path5.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
1063
+ await mkdir3(path5.dirname(auditFile), { recursive: true });
1064
+ await appendFile(
1065
+ auditFile,
1066
+ `${JSON.stringify({
1067
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1068
+ ...event
1069
+ })}
1070
+ `,
1071
+ "utf8"
1072
+ );
1073
+ }
1074
+
1075
+ // ../core/src/secrets/providers/local.ts
1076
+ var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
1077
+ constructor(vaultId, definition, processEnv = process.env, storeRoot = resolveSecretStoreRoot(processEnv)) {
1078
+ this.vaultId = vaultId;
1079
+ this.processEnv = processEnv;
1080
+ this.storeRoot = storeRoot;
1081
+ this.definition = definition;
1082
+ }
1083
+ vaultId;
1084
+ processEnv;
1085
+ storeRoot;
1086
+ authConfig;
1087
+ definition;
1088
+ static fromVaults(vaults, vaultId, processEnv) {
1089
+ const definition = resolveVaultDefinition(vaults, vaultId);
1090
+ return new _LocalSecretVaultProvider(vaultId, definition, processEnv);
1091
+ }
1092
+ async authenticate(authConfig) {
1093
+ this.authConfig = authConfig;
1094
+ await this.list();
1095
+ }
1096
+ isAuthenticated() {
1097
+ return Boolean(this.authConfig);
1098
+ }
1099
+ async requireAuth() {
1100
+ if (this.authConfig) {
1101
+ return this.authConfig;
1102
+ }
1103
+ const resolved = await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
1104
+ if (!resolved) {
1105
+ throw new CnosAuthenticationError(
1106
+ `Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
1107
+ );
1108
+ }
1109
+ this.authConfig = resolved;
1110
+ return resolved;
1111
+ }
1112
+ async batchGet(refs) {
1113
+ const auth = await this.requireAuth();
1114
+ const entries = await Promise.all(
1115
+ Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right)).map(async (ref) => [ref, await readLocalSecret(this.storeRoot, ref, auth, this.vaultId)])
1116
+ );
1117
+ return new Map(entries);
1118
+ }
1119
+ async get(ref) {
1120
+ const auth = await this.requireAuth();
1121
+ try {
1122
+ return await readLocalSecret(this.storeRoot, ref, auth, this.vaultId);
1123
+ } catch {
1124
+ return void 0;
1125
+ }
1126
+ }
1127
+ async set(ref, value) {
1128
+ const auth = await this.requireAuth();
1129
+ await writeLocalSecret(this.storeRoot, ref, value, auth, this.vaultId);
1130
+ await appendAuditEvent(
1131
+ {
1132
+ action: "write",
1133
+ vault: this.vaultId,
1134
+ ref,
1135
+ caller: "cli"
1136
+ },
1137
+ this.processEnv
1138
+ );
1139
+ }
1140
+ async delete(ref) {
1141
+ const auth = await this.requireAuth();
1142
+ await deleteLocalSecret(this.storeRoot, ref, auth, this.vaultId);
1143
+ await appendAuditEvent(
1144
+ {
1145
+ action: "delete",
1146
+ vault: this.vaultId,
1147
+ ref,
1148
+ caller: "cli"
1149
+ },
1150
+ this.processEnv
616
1151
  );
617
1152
  }
618
- const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
619
- const source = await readFile2(filePath, "utf8");
620
- const document = JSON.parse(source);
621
- if (document.version !== 1 || document.algorithm !== "aes-256-gcm" || typeof document.salt !== "string" || typeof document.iv !== "string" || typeof document.tag !== "string" || typeof document.ciphertext !== "string") {
622
- throw new CnosManifestError("Invalid local secret document", filePath);
1153
+ async list() {
1154
+ const auth = this.authConfig ?? await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
1155
+ if (!auth) {
1156
+ throw new CnosAuthenticationError(
1157
+ `Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
1158
+ );
1159
+ }
1160
+ this.authConfig = auth;
1161
+ return listLocalSecrets(this.storeRoot, auth, this.vaultId);
1162
+ }
1163
+ };
1164
+
1165
+ // ../core/src/secrets/providers/registry.ts
1166
+ function createSecretVaultProvider(vaultId, definition, processEnv) {
1167
+ if (definition.provider === "local") {
1168
+ return new LocalSecretVaultProvider(vaultId, definition, processEnv);
1169
+ }
1170
+ if (definition.provider === "github-secrets") {
1171
+ return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
1172
+ }
1173
+ throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
1174
+ }
1175
+
1176
+ // ../core/src/secrets/prompt.ts
1177
+ import readline from "readline";
1178
+ import { Writable } from "stream";
1179
+ async function promptHidden(message) {
1180
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1181
+ return void 0;
1182
+ }
1183
+ const mutableStdout = new WritableMask();
1184
+ const rl = readline.createInterface({
1185
+ input: process.stdin,
1186
+ output: mutableStdout,
1187
+ terminal: true
1188
+ });
1189
+ try {
1190
+ mutableStdout.muted = true;
1191
+ const value = await new Promise((resolve) => {
1192
+ rl.question(message, resolve);
1193
+ });
1194
+ process.stdout.write("\n");
1195
+ return value;
1196
+ } finally {
1197
+ rl.close();
1198
+ }
1199
+ }
1200
+ var WritableMask = class extends Writable {
1201
+ muted = false;
1202
+ _write(chunk, _encoding, callback) {
1203
+ if (!this.muted) {
1204
+ process.stdout.write(chunk);
1205
+ }
1206
+ callback();
1207
+ }
1208
+ };
1209
+
1210
+ // ../core/src/secrets/resolveAuth.ts
1211
+ function toAuthError(vaultId, sources) {
1212
+ return new CnosAuthenticationError(
1213
+ `Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
1214
+ );
1215
+ }
1216
+ async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
1217
+ const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
1218
+ if (sessionKey) {
1219
+ return {
1220
+ derivedKey: sessionKey,
1221
+ method: "keychain",
1222
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1223
+ };
1224
+ }
1225
+ if (definition.provider === "github-secrets") {
1226
+ return {
1227
+ method: definition.auth?.method ?? "environment",
1228
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1229
+ };
1230
+ }
1231
+ const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
1232
+ for (const source of sources) {
1233
+ if (source.startsWith("env:")) {
1234
+ const value = processEnv[source.slice(4)];
1235
+ if (value) {
1236
+ return {
1237
+ passphrase: value,
1238
+ method: "passphrase",
1239
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1240
+ };
1241
+ }
1242
+ }
1243
+ if (source.startsWith("keychain:")) {
1244
+ const value = await readKeychain(source.slice("keychain:".length));
1245
+ if (value) {
1246
+ return {
1247
+ derivedKey: Buffer.from(value, "hex"),
1248
+ method: "keychain",
1249
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1250
+ };
1251
+ }
1252
+ }
1253
+ if (source === "prompt") {
1254
+ const value = await promptHidden(`Enter passphrase for vault "${vaultId}": `);
1255
+ if (value) {
1256
+ return {
1257
+ passphrase: value,
1258
+ method: "passphrase",
1259
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1260
+ };
1261
+ }
1262
+ }
1263
+ }
1264
+ const fallback = resolveSecretPassphrase(vaultId, processEnv);
1265
+ if (fallback) {
1266
+ return {
1267
+ passphrase: fallback,
1268
+ method: "passphrase",
1269
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1270
+ };
623
1271
  }
624
- return decryptDocument(document, passphrase);
1272
+ throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
1273
+ }
1274
+
1275
+ // ../core/src/runtime/projection.ts
1276
+ function setNestedValue(target, pathSegments, value) {
1277
+ const [head, ...tail] = pathSegments;
1278
+ if (!head) {
1279
+ return;
1280
+ }
1281
+ if (tail.length === 0) {
1282
+ target[head] = value;
1283
+ return;
1284
+ }
1285
+ const current = target[head];
1286
+ const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
1287
+ target[head] = nextTarget;
1288
+ setNestedValue(nextTarget, tail, value);
1289
+ }
1290
+ function toNamespaceObject(graph, namespace) {
1291
+ const output = {};
1292
+ const resolvedEntries = Array.from(graph.entries.values()).sort(
1293
+ (left, right) => left.key.localeCompare(right.key)
1294
+ );
1295
+ for (const entry of resolvedEntries) {
1296
+ if (namespace && entry.namespace !== namespace) {
1297
+ continue;
1298
+ }
1299
+ const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
1300
+ setNestedValue(output, valuePath.split("."), entry.value);
1301
+ }
1302
+ return output;
1303
+ }
1304
+
1305
+ // ../core/src/runtime/read.ts
1306
+ function readValue(graph, key) {
1307
+ return graph.entries.get(key)?.value;
1308
+ }
1309
+
1310
+ // ../core/src/runtime/readOr.ts
1311
+ function readOrValue(graph, key, fallback) {
1312
+ const value = readValue(graph, key);
1313
+ return value === void 0 ? fallback : value;
1314
+ }
1315
+
1316
+ // ../core/src/runtime/require.ts
1317
+ function requireValue(graph, key) {
1318
+ const value = readValue(graph, key);
1319
+ if (value === void 0) {
1320
+ throw new CnosKeyNotFoundError(key);
1321
+ }
1322
+ return value;
625
1323
  }
626
1324
 
627
1325
  // ../core/src/runtime/toEnv.ts
@@ -705,23 +1403,23 @@ function toPublicEnv(graph, manifest, options = {}) {
705
1403
  }
706
1404
 
707
1405
  // ../core/src/runtime/dump.ts
708
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
709
- import path4 from "path";
1406
+ import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
1407
+ import path6 from "path";
710
1408
  function buildDumpFiles(graph, options = {}) {
711
- const basePath = options.flatten ? "" : path4.posix.join("workspaces", graph.workspace.workspaceId);
1409
+ const basePath = options.flatten ? "" : path6.posix.join("workspaces", graph.workspace.workspaceId);
712
1410
  const values = toNamespaceObject(graph, "value");
713
1411
  const secrets = toNamespaceObject(graph, "secret");
714
1412
  const files = [];
715
1413
  if (Object.keys(values).length > 0) {
716
1414
  files.push({
717
- path: path4.posix.join(basePath, "values", graph.profile, "app.yml"),
1415
+ path: path6.posix.join(basePath, "values", graph.profile, "app.yml"),
718
1416
  namespace: "value",
719
1417
  content: stringifyYaml(values)
720
1418
  });
721
1419
  }
722
1420
  if (Object.keys(secrets).length > 0) {
723
1421
  files.push({
724
- path: path4.posix.join(basePath, "secrets", graph.profile, "app.yml"),
1422
+ path: path6.posix.join(basePath, "secrets", graph.profile, "app.yml"),
725
1423
  namespace: "secret",
726
1424
  content: stringifyYaml(secrets)
727
1425
  });
@@ -737,12 +1435,12 @@ function planDump(graph, options = {}) {
737
1435
  };
738
1436
  }
739
1437
  async function writeDump(graph, options) {
740
- const root = path4.resolve(options.to);
1438
+ const root = path6.resolve(options.to);
741
1439
  const plan = planDump(graph, options);
742
1440
  for (const file of plan.files) {
743
- const destination = path4.join(root, file.path);
744
- await mkdir2(path4.dirname(destination), { recursive: true });
745
- await writeFile2(destination, file.content, "utf8");
1441
+ const destination = path6.join(root, file.path);
1442
+ await mkdir4(path6.dirname(destination), { recursive: true });
1443
+ await writeFile3(destination, file.content, "utf8");
746
1444
  }
747
1445
  return {
748
1446
  ...plan,
@@ -773,11 +1471,11 @@ function normalizeMappingConfig(config = {}) {
773
1471
  function toScreamingSnakeSegment(segment) {
774
1472
  return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
775
1473
  }
776
- function toScreamingSnake(path8) {
777
- return path8.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
1474
+ function toScreamingSnake(path10) {
1475
+ return path10.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
778
1476
  }
779
- function fromScreamingSnake(path8) {
780
- return path8.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
1477
+ function fromScreamingSnake(path10) {
1478
+ return path10.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
781
1479
  }
782
1480
  function logicalKeyToEnvVar(key, config = {}) {
783
1481
  const normalized = normalizeMappingConfig(config);
@@ -929,12 +1627,12 @@ function createProvenanceInspector() {
929
1627
  }
930
1628
 
931
1629
  // ../core/src/manifest/loadWorkspaceFile.ts
932
- import { readFile as readFile3 } from "fs/promises";
933
- import path5 from "path";
1630
+ import { readFile as readFile4 } from "fs/promises";
1631
+ import path7 from "path";
934
1632
  async function loadWorkspaceFile(repoRoot) {
935
- const workspaceFilePath = path5.join(repoRoot, ".cnos-workspace.yml");
1633
+ const workspaceFilePath = path7.join(repoRoot, ".cnos-workspace.yml");
936
1634
  try {
937
- const source = await readFile3(workspaceFilePath, "utf8");
1635
+ const source = await readFile4(workspaceFilePath, "utf8");
938
1636
  const parsed = parseYaml(source);
939
1637
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
940
1638
  throw new CnosManifestError(".cnos-workspace.yml must be a YAML object", workspaceFilePath);
@@ -957,8 +1655,8 @@ async function loadWorkspaceFile(repoRoot) {
957
1655
  }
958
1656
 
959
1657
  // ../core/src/profiles/expandProfileChain.ts
960
- import { access as access2, readFile as readFile4 } from "fs/promises";
961
- import path6 from "path";
1658
+ import { access as access2, readFile as readFile5 } from "fs/promises";
1659
+ import path8 from "path";
962
1660
  async function fileExists(targetPath) {
963
1661
  try {
964
1662
  await access2(targetPath);
@@ -995,11 +1693,11 @@ async function loadProfileDefinition(profileName, options) {
995
1693
  return normalizeProfileDefinition(profileName, void 0);
996
1694
  }
997
1695
  for (const workspaceRoot of [...workspaceRoots].reverse()) {
998
- const profilePath = path6.join(workspaceRoot.path, "profiles", `${profileName}.yml`);
1696
+ const profilePath = path8.join(workspaceRoot.path, "profiles", `${profileName}.yml`);
999
1697
  if (!await fileExists(profilePath)) {
1000
1698
  continue;
1001
1699
  }
1002
- const document = await readFile4(profilePath, "utf8");
1700
+ const document = await readFile5(profilePath, "utf8");
1003
1701
  const parsed = parseYaml(document);
1004
1702
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1005
1703
  throw new CnosManifestError("Profile definition must be a YAML object", profilePath);
@@ -1007,7 +1705,7 @@ async function loadProfileDefinition(profileName, options) {
1007
1705
  const definition = normalizeProfileDefinition(
1008
1706
  profileName,
1009
1707
  parsed,
1010
- options.manifestRoot ? toPortablePath(path6.relative(path6.dirname(options.manifestRoot), profilePath)) : toPortablePath(profilePath)
1708
+ options.manifestRoot ? toPortablePath(path8.relative(path8.dirname(options.manifestRoot), profilePath)) : toPortablePath(profilePath)
1011
1709
  );
1012
1710
  if (definition.name !== profileName) {
1013
1711
  throw new CnosManifestError(
@@ -1254,7 +1952,7 @@ function createProfileAwareResolver() {
1254
1952
 
1255
1953
  // ../core/src/workspaces/resolveWorkspaceContext.ts
1256
1954
  import { access as access3 } from "fs/promises";
1257
- import path7 from "path";
1955
+ import path9 from "path";
1258
1956
 
1259
1957
  // ../core/src/workspaces/expandWorkspaceChain.ts
1260
1958
  function expandWorkspaceChain(workspaceId, items) {
@@ -1291,7 +1989,7 @@ function expandWorkspaceChain(workspaceId, items) {
1291
1989
  }
1292
1990
 
1293
1991
  // ../core/src/workspaces/resolveWorkspaceContext.ts
1294
- async function exists2(targetPath) {
1992
+ async function exists3(targetPath) {
1295
1993
  try {
1296
1994
  await access3(targetPath);
1297
1995
  return true;
@@ -1300,14 +1998,14 @@ async function exists2(targetPath) {
1300
1998
  }
1301
1999
  }
1302
2000
  async function resolveLocalWorkspaceRoot(manifestRoot, workspaceId) {
1303
- const workspaceRoot = path7.join(manifestRoot, "workspaces", workspaceId);
1304
- if (await exists2(workspaceRoot)) {
2001
+ const workspaceRoot = path9.join(manifestRoot, "workspaces", workspaceId);
2002
+ if (await exists3(workspaceRoot)) {
1305
2003
  return workspaceRoot;
1306
2004
  }
1307
2005
  const legacyMarkers = ["values", "secrets", "env", "profiles"].map(
1308
- (segment) => path7.join(manifestRoot, segment)
2006
+ (segment) => path9.join(manifestRoot, segment)
1309
2007
  );
1310
- if ((await Promise.all(legacyMarkers.map((marker) => exists2(marker)))).some(Boolean)) {
2008
+ if ((await Promise.all(legacyMarkers.map((marker) => exists3(marker)))).some(Boolean)) {
1311
2009
  return manifestRoot;
1312
2010
  }
1313
2011
  return workspaceRoot;
@@ -1347,26 +2045,26 @@ function resolveGlobalRoot(manifest, workspaceFile, options) {
1347
2045
  }
1348
2046
  if (options.globalRoot) {
1349
2047
  return {
1350
- value: path7.resolve(expandHomePath(options.globalRoot)),
2048
+ value: path9.resolve(expandHomePath(options.globalRoot)),
1351
2049
  source: "cli"
1352
2050
  };
1353
2051
  }
1354
2052
  if (workspaceFile?.globalRoot) {
1355
2053
  return {
1356
- value: path7.resolve(expandHomePath(workspaceFile.globalRoot)),
2054
+ value: path9.resolve(expandHomePath(workspaceFile.globalRoot)),
1357
2055
  source: "workspace-file"
1358
2056
  };
1359
2057
  }
1360
2058
  if (manifest.workspaces.global.root) {
1361
2059
  return {
1362
- value: path7.resolve(expandHomePath(manifest.workspaces.global.root)),
2060
+ value: path9.resolve(expandHomePath(manifest.workspaces.global.root)),
1363
2061
  source: "manifest"
1364
2062
  };
1365
2063
  }
1366
2064
  const cnosHome = options.processEnv?.CNOS_HOME;
1367
2065
  if (cnosHome) {
1368
2066
  return {
1369
- value: path7.resolve(expandHomePath(cnosHome)),
2067
+ value: path9.resolve(expandHomePath(cnosHome)),
1370
2068
  source: "CNOS_HOME"
1371
2069
  };
1372
2070
  }
@@ -1383,7 +2081,7 @@ async function resolveWorkspaceContext(manifest, options) {
1383
2081
  workspaceRoots.push({
1384
2082
  scope: "global",
1385
2083
  workspaceId: chainWorkspaceId,
1386
- path: path7.join(globalRoot.value, "workspaces", globalWorkspaceId)
2084
+ path: path9.join(globalRoot.value, "workspaces", globalWorkspaceId)
1387
2085
  });
1388
2086
  }
1389
2087
  }
@@ -1573,29 +2271,120 @@ async function runPipeline(options) {
1573
2271
  return collectedEntries.flat();
1574
2272
  }
1575
2273
 
2274
+ // ../core/src/secrets/secretCache.ts
2275
+ var SecretCache = class {
2276
+ cache = /* @__PURE__ */ new Map();
2277
+ authenticated = /* @__PURE__ */ new Set();
2278
+ load(vaultId, secrets) {
2279
+ this.authenticated.add(vaultId);
2280
+ for (const [ref, value] of secrets) {
2281
+ this.cache.set(`${vaultId}:${ref}`, value);
2282
+ }
2283
+ }
2284
+ isVaultAuthenticated(vaultId) {
2285
+ return this.authenticated.has(vaultId);
2286
+ }
2287
+ get(vaultId, ref) {
2288
+ return this.cache.get(`${vaultId}:${ref}`);
2289
+ }
2290
+ clear(vaultId) {
2291
+ if (!vaultId) {
2292
+ this.cache.clear();
2293
+ this.authenticated.clear();
2294
+ return;
2295
+ }
2296
+ this.authenticated.delete(vaultId);
2297
+ for (const key of Array.from(this.cache.keys())) {
2298
+ if (key.startsWith(`${vaultId}:`)) {
2299
+ this.cache.delete(key);
2300
+ }
2301
+ }
2302
+ }
2303
+ };
2304
+
2305
+ // ../core/src/secrets/batchResolve.ts
2306
+ function collectSecretDescriptors(graph) {
2307
+ return Array.from(graph.entries.values()).filter((entry) => entry.namespace === "secret" && isSecretReference(entry.value)).map((entry) => ({
2308
+ logicalKey: entry.key,
2309
+ ref: entry.value
2310
+ }));
2311
+ }
2312
+ async function batchResolveSecrets(graph, manifest, processEnv = process.env) {
2313
+ const cache = new SecretCache();
2314
+ const descriptors = collectSecretDescriptors(graph);
2315
+ const grouped = descriptors.reduce((accumulator, descriptor) => {
2316
+ const vaultId = descriptor.ref.vault ?? "default";
2317
+ const bucket = accumulator.get(vaultId) ?? [];
2318
+ bucket.push(descriptor);
2319
+ accumulator.set(vaultId, bucket);
2320
+ return accumulator;
2321
+ }, /* @__PURE__ */ new Map());
2322
+ for (const [vaultId, refs] of grouped) {
2323
+ const definition = manifest.vaults[vaultId] ?? { provider: "local", auth: { passphrase: { from: [] } } };
2324
+ const provider = createSecretVaultProvider(vaultId, definition, processEnv);
2325
+ const auth = await resolveVaultAuth(vaultId, definition, processEnv);
2326
+ await provider.authenticate(auth);
2327
+ const resolved = await provider.batchGet(refs.map((entry) => entry.ref.ref));
2328
+ cache.load(vaultId, resolved);
2329
+ await appendAuditEvent(
2330
+ {
2331
+ action: "batch_read",
2332
+ vault: vaultId,
2333
+ refs: Array.from(resolved.keys()).sort((left, right) => left.localeCompare(right)),
2334
+ caller: "runtime",
2335
+ workspace: graph.workspace.workspaceId,
2336
+ profile: graph.profile
2337
+ },
2338
+ processEnv
2339
+ );
2340
+ }
2341
+ return cache;
2342
+ }
2343
+ function resolveSecretEntryValue(key, value, cache) {
2344
+ if (!key.startsWith("secret.") || !isSecretReference(value)) {
2345
+ return value;
2346
+ }
2347
+ const vaultId = value.vault ?? "default";
2348
+ return cache.get(vaultId, value.ref) ?? value;
2349
+ }
2350
+
1576
2351
  // ../core/src/orchestrator/runtime.ts
1577
- function createRuntime(manifest, graph, plugins = []) {
2352
+ function createRuntime(manifest, graph, plugins = [], secretCache) {
2353
+ function readLogicalKey(key) {
2354
+ const entry = graph.entries.get(key);
2355
+ if (!entry) {
2356
+ return void 0;
2357
+ }
2358
+ if (!secretCache) {
2359
+ return entry.value;
2360
+ }
2361
+ return resolveSecretEntryValue(key, entry.value, secretCache);
2362
+ }
1578
2363
  return {
1579
2364
  manifest,
1580
2365
  plugins,
1581
2366
  graph,
1582
2367
  read(key) {
1583
- return readValue(graph, key);
2368
+ return readLogicalKey(key);
1584
2369
  },
1585
2370
  require(key) {
1586
- return requireValue(graph, key);
2371
+ const value = readLogicalKey(key);
2372
+ if (value === void 0) {
2373
+ return requireValue(graph, key);
2374
+ }
2375
+ return value;
1587
2376
  },
1588
2377
  readOr(key, fallback) {
1589
2378
  return readOrValue(graph, key, fallback);
1590
2379
  },
1591
- value(path8) {
1592
- return readValue(graph, toLogicalKey("value", path8));
2380
+ value(path10) {
2381
+ return readLogicalKey(toLogicalKey("value", path10));
1593
2382
  },
1594
- secret(path8) {
1595
- return readValue(graph, toLogicalKey("secret", path8));
2383
+ secret(path10) {
2384
+ return readLogicalKey(toLogicalKey("secret", path10));
1596
2385
  },
1597
- meta(path8) {
1598
- return readValue(graph, toLogicalKey("meta", path8));
2386
+ meta(path10) {
2387
+ return readLogicalKey(toLogicalKey("meta", path10));
1599
2388
  },
1600
2389
  inspect(key) {
1601
2390
  return inspectValue(graph, key);
@@ -1753,21 +2542,26 @@ async function createCnos(options = {}) {
1753
2542
  });
1754
2543
  const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
1755
2544
  const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
2545
+ const secretCache = options.secretResolution === "lazy" ? void 0 : await batchResolveSecrets(promotedGraph, loadedManifest.manifest, options.processEnv);
1756
2546
  return createRuntime(
1757
2547
  loadedManifest.manifest,
1758
2548
  appendMetaEntries({
1759
2549
  ...promotedGraph,
1760
2550
  profileSource: activeProfile.source
1761
2551
  }, options.cnosVersion),
1762
- plugins
2552
+ plugins,
2553
+ secretCache
1763
2554
  );
1764
2555
  }
1765
2556
 
1766
2557
  export {
1767
2558
  CnosManifestError,
1768
2559
  CnosSecurityError,
2560
+ CnosAuthenticationError,
1769
2561
  inspectValue,
1770
2562
  createProvenanceInspector,
2563
+ readKeychain,
2564
+ writeKeychain,
1771
2565
  resolveManifestRoot,
1772
2566
  resolveWorkspaceScopedPath,
1773
2567
  resolveConfigDocumentPath,
@@ -1779,22 +2573,35 @@ export {
1779
2573
  loadManifest,
1780
2574
  ensureProjectionAllowed,
1781
2575
  applySchemaRules,
1782
- toNamespaceObject,
1783
- readValue,
1784
- readOrValue,
1785
- requireValue,
2576
+ writeVaultSessionKey,
2577
+ clearVaultSessionKey,
2578
+ clearAllVaultSessionKeys,
1786
2579
  isSecretReference,
1787
2580
  resolveSecretStoreRoot,
1788
- resolveSecretVaultFile,
1789
- resolveSecretPassphrase,
1790
2581
  getVaultPassphraseEnvVar,
1791
2582
  isPassphraseEnvRef,
1792
- resolveConfiguredVaultPassphrase,
1793
- resolveVaultDefinition,
1794
- createSecretVault,
2583
+ getVaultSessionKeyEnvVar,
2584
+ resolveSecretPassphrase,
2585
+ deriveVaultKey,
2586
+ resolveSecretVaultFile,
2587
+ detectLegacyVaultFormat,
2588
+ readVaultMetadata,
1795
2589
  listSecretVaults,
2590
+ createSecretVault,
2591
+ resolveConfiguredVaultPassphrase,
2592
+ resolveVaultAccessKey,
1796
2593
  writeLocalSecret,
2594
+ deleteLocalSecret,
1797
2595
  readLocalSecret,
2596
+ listLocalSecrets,
2597
+ resolveVaultDefinition,
2598
+ removeLocalVaultFiles,
2599
+ createSecretVaultProvider,
2600
+ resolveVaultAuth,
2601
+ toNamespaceObject,
2602
+ readValue,
2603
+ readOrValue,
2604
+ requireValue,
1798
2605
  toEnv,
1799
2606
  toPublicEnv,
1800
2607
  createCnos,