@kitsy/cnos 1.2.0 → 1.4.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 (65) hide show
  1. package/README.md +3 -3
  2. package/dist/build/index.cjs +1003 -121
  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-APCTXRUN.js → chunk-APIU4GTB.js} +1012 -195
  7. package/dist/chunk-EQSKV3DP.js +105 -0
  8. package/dist/{chunk-MLQGYCO7.js → chunk-FWJC4Y2D.js} +1 -1
  9. package/dist/{chunk-RD5WMHPM.js → chunk-HMM76UYZ.js} +1 -1
  10. package/dist/{chunk-EIN55XXA.js → chunk-J4K4JUJL.js} +1 -1
  11. package/dist/{chunk-SO5XREEU.js → chunk-JSBVYK2T.js} +32 -11
  12. package/dist/chunk-LJD4SM32.js +189 -0
  13. package/dist/{chunk-SXTMTACL.js → chunk-T6Y57KTT.js} +20 -31
  14. package/dist/chunk-WCHX2QFY.js +115 -0
  15. package/dist/{chunk-ZA74BO47.js → chunk-ZTPSFXWP.js} +1 -1
  16. package/dist/configure/index.cjs +3021 -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-CcsqAel3.d.ts → envNaming-Dvm_LP2D.d.ts} +1 -1
  21. package/dist/{envNaming-BTJpH93W.d.cts → envNaming-S4B-dHUx.d.cts} +1 -1
  22. package/dist/index.cjs +1243 -186
  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 +1525 -81
  27. package/dist/internal.d.cts +171 -14
  28. package/dist/internal.d.ts +171 -14
  29. package/dist/internal.js +652 -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 +65 -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 +105 -11
  51. package/dist/plugin/process-env.d.cts +4 -3
  52. package/dist/plugin/process-env.d.ts +4 -3
  53. package/dist/plugin/process-env.js +6 -4
  54. package/dist/{plugin-DkOIT5uI.d.cts → plugin-B4xwySxw.d.cts} +15 -2
  55. package/dist/{plugin-DkOIT5uI.d.ts → plugin-B4xwySxw.d.ts} +15 -2
  56. package/dist/runtime/index.cjs +1057 -136
  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-C9clvXLo.d.ts → toPublicEnv-CvhGAfsB.d.ts} +1 -1
  61. package/dist/{toPublicEnv-DvFeV3qG.d.cts → toPublicEnv-ggmphZFs.d.cts} +1 -1
  62. package/package.json +11 -1
  63. package/dist/chunk-JUHPBAEH.js +0 -20
  64. package/dist/chunk-PQ4KSV76.js +0 -50
  65. package/dist/chunk-WHUGFPE4.js +0 -49
package/dist/internal.cjs CHANGED
@@ -31,28 +31,63 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var internal_exports = {};
32
32
  __export(internal_exports, {
33
33
  CNOS_GRAPH_ENV_VAR: () => CNOS_GRAPH_ENV_VAR,
34
+ CNOS_SECRET_PAYLOAD_ENV_VAR: () => CNOS_SECRET_PAYLOAD_ENV_VAR,
35
+ CNOS_SESSION_KEY_ENV_VAR: () => CNOS_SESSION_KEY_ENV_VAR,
36
+ CnosAuthenticationError: () => CnosAuthenticationError,
34
37
  CnosSecurityError: () => CnosSecurityError,
38
+ applyManifestMappings: () => applyManifestMappings,
39
+ clearAllVaultSessionKeys: () => clearAllVaultSessionKeys,
40
+ clearVaultSessionKey: () => clearVaultSessionKey,
41
+ compareSchemaToGraph: () => compareSchemaToGraph,
35
42
  createSecretVault: () => createSecretVault,
43
+ createSecretVaultProvider: () => createSecretVaultProvider,
44
+ deleteLocalSecret: () => deleteLocalSecret,
45
+ deriveVaultKey: () => deriveVaultKey,
36
46
  deserializeRuntimeGraph: () => deserializeRuntimeGraph,
47
+ detectLegacyVaultFormat: () => detectLegacyVaultFormat,
48
+ diffGraphs: () => diffGraphs,
37
49
  ensureProjectionAllowed: () => ensureProjectionAllowed,
38
50
  flattenObject: () => flattenObject,
51
+ formatDriftReport: () => formatDriftReport,
52
+ generateCodegenContent: () => generateCodegenContent,
53
+ getNamespaceDefinition: () => getNamespaceDefinition,
39
54
  getVaultPassphraseEnvVar: () => getVaultPassphraseEnvVar,
55
+ getVaultSessionKeyEnvVar: () => getVaultSessionKeyEnvVar,
56
+ graphRequiresSecretHydration: () => graphRequiresSecretHydration,
40
57
  isPassphraseEnvRef: () => isPassphraseEnvRef,
58
+ isSecretReference: () => isSecretReference,
59
+ listLocalSecrets: () => listLocalSecrets,
41
60
  listSecretVaults: () => listSecretVaults,
42
61
  loadManifest: () => loadManifest,
43
62
  parseYaml: () => parseYaml,
63
+ proposeMapping: () => proposeMapping,
64
+ readKeychain: () => readKeychain,
65
+ readLocalSecret: () => readLocalSecret,
44
66
  readRuntimeGraphFromEnv: () => readRuntimeGraphFromEnv,
67
+ readVaultMetadata: () => readVaultMetadata,
68
+ removeLocalVaultFiles: () => removeLocalVaultFiles,
69
+ resolveCodegenPaths: () => resolveCodegenPaths,
45
70
  resolveConfigDocumentPath: () => resolveConfigDocumentPath,
46
71
  resolveConfiguredVaultPassphrase: () => resolveConfiguredVaultPassphrase,
47
72
  resolveManifestRoot: () => resolveManifestRoot,
48
73
  resolveSecretPassphrase: () => resolveSecretPassphrase,
49
74
  resolveSecretStoreRoot: () => resolveSecretStoreRoot,
50
75
  resolveSecretVaultFile: () => resolveSecretVaultFile,
76
+ resolveVaultAccessKey: () => resolveVaultAccessKey,
77
+ resolveVaultAuth: () => resolveVaultAuth,
51
78
  resolveVaultDefinition: () => resolveVaultDefinition,
79
+ rewriteSourceFiles: () => rewriteSourceFiles,
80
+ scanEnvUsage: () => scanEnvUsage,
52
81
  serializeRuntimeGraph: () => serializeRuntimeGraph,
82
+ serializeSecretPayload: () => serializeSecretPayload,
53
83
  stringifyYaml: () => stringifyYaml,
54
84
  validateRuntime: () => validateRuntime,
55
- writeLocalSecret: () => writeLocalSecret
85
+ watchFiles: () => watchFiles,
86
+ watchSchema: () => watchSchema,
87
+ writeCodegenOutput: () => writeCodegenOutput,
88
+ writeKeychain: () => writeKeychain,
89
+ writeLocalSecret: () => writeLocalSecret,
90
+ writeVaultSessionKey: () => writeVaultSessionKey
56
91
  });
57
92
  module.exports = __toCommonJS(internal_exports);
58
93
 
@@ -75,6 +110,117 @@ var CnosSecurityError = class extends CnosError {
75
110
  super(message);
76
111
  }
77
112
  };
113
+ var CnosAuthenticationError = class extends CnosError {
114
+ constructor(message) {
115
+ super(message);
116
+ }
117
+ };
118
+
119
+ // ../core/src/keychain/linux.ts
120
+ var import_node_child_process = require("child_process");
121
+ var import_node_util = require("util");
122
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
123
+ async function readLinuxKeychain(entry) {
124
+ try {
125
+ const { stdout } = await execFileAsync("secret-tool", ["lookup", "service", "cnos", "account", entry]);
126
+ const value = stdout.trim();
127
+ return value.length > 0 ? value : void 0;
128
+ } catch {
129
+ return void 0;
130
+ }
131
+ }
132
+ async function writeLinuxKeychain(entry, value) {
133
+ await new Promise((resolve, reject) => {
134
+ const child = (0, import_node_child_process.spawn)("secret-tool", ["store", "--label", `CNOS ${entry}`, "service", "cnos", "account", entry], {
135
+ stdio: ["pipe", "ignore", "pipe"]
136
+ });
137
+ let stderr = "";
138
+ child.stdin?.end(value);
139
+ child.stderr?.on("data", (chunk) => {
140
+ stderr += chunk.toString();
141
+ });
142
+ child.on("error", reject);
143
+ child.on("close", (code) => {
144
+ if (code === 0) {
145
+ resolve();
146
+ return;
147
+ }
148
+ reject(new Error(stderr || `secret-tool exited with code ${code ?? 1}`));
149
+ });
150
+ });
151
+ }
152
+
153
+ // ../core/src/keychain/macos.ts
154
+ var import_node_child_process2 = require("child_process");
155
+ var import_node_util2 = require("util");
156
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
157
+ async function readMacosKeychain(entry) {
158
+ try {
159
+ const { stdout } = await execFileAsync2("security", ["find-generic-password", "-a", "cnos", "-s", entry, "-w"]);
160
+ const value = stdout.trim();
161
+ return value.length > 0 ? value : void 0;
162
+ } catch {
163
+ return void 0;
164
+ }
165
+ }
166
+ async function writeMacosKeychain(entry, value) {
167
+ await execFileAsync2("security", ["add-generic-password", "-a", "cnos", "-s", entry, "-w", value, "-U"]);
168
+ }
169
+
170
+ // ../core/src/keychain/windows.ts
171
+ var import_node_child_process3 = require("child_process");
172
+ var import_node_util3 = require("util");
173
+ var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
174
+ function wrap(script) {
175
+ return ["-NoProfile", "-Command", script];
176
+ }
177
+ async function readWindowsKeychain(entry) {
178
+ try {
179
+ const { stdout } = await execFileAsync3(
180
+ "powershell",
181
+ 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`)
182
+ );
183
+ const value = stdout.trim();
184
+ return value.length > 0 ? value : void 0;
185
+ } catch {
186
+ return void 0;
187
+ }
188
+ }
189
+ async function writeWindowsKeychain(entry, value) {
190
+ await execFileAsync3(
191
+ "powershell",
192
+ 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)`)
193
+ );
194
+ }
195
+
196
+ // ../core/src/keychain/index.ts
197
+ async function readKeychain(entry) {
198
+ if (process.platform === "win32") {
199
+ return readWindowsKeychain(entry);
200
+ }
201
+ if (process.platform === "darwin") {
202
+ return readMacosKeychain(entry);
203
+ }
204
+ if (process.platform === "linux") {
205
+ return readLinuxKeychain(entry);
206
+ }
207
+ return void 0;
208
+ }
209
+ async function writeKeychain(entry, value) {
210
+ if (process.platform === "win32") {
211
+ await writeWindowsKeychain(entry, value);
212
+ return;
213
+ }
214
+ if (process.platform === "darwin") {
215
+ await writeMacosKeychain(entry, value);
216
+ return;
217
+ }
218
+ if (process.platform === "linux") {
219
+ await writeLinuxKeychain(entry, value);
220
+ return;
221
+ }
222
+ throw new CnosAuthenticationError(`OS keychain is not supported on platform "${process.platform}".`);
223
+ }
78
224
 
79
225
  // ../core/src/manifest/loadManifest.ts
80
226
  var import_promises2 = require("fs/promises");
@@ -123,7 +269,7 @@ function expandHomePath(targetPath) {
123
269
  return targetPath;
124
270
  }
125
271
  function resolveNamespaceDirectory(workspaceRoot, namespace, profile) {
126
- const rootFolder = namespace === "value" ? "values" : "secrets";
272
+ const rootFolder = namespace === "value" ? "values" : namespace === "secret" ? "secrets" : namespace;
127
273
  if (profile && profile !== "base") {
128
274
  return import_node_path.default.resolve(workspaceRoot, "profiles", profile, rootFolder);
129
275
  }
@@ -176,6 +322,11 @@ var DEFAULT_NAMESPACES = {
176
322
  shareable: false,
177
323
  readonly: true
178
324
  },
325
+ process: {
326
+ kind: "system",
327
+ shareable: false,
328
+ readonly: true
329
+ },
179
330
  public: {
180
331
  kind: "projection",
181
332
  source: "promote",
@@ -230,22 +381,82 @@ function normalizeNamespaces(namespaces) {
230
381
  function normalizeVaults(vaults) {
231
382
  return Object.fromEntries(
232
383
  Object.entries(vaults ?? {}).map(([name, definition]) => {
384
+ const legacyPassphrase = definition.passphrase;
385
+ if (legacyPassphrase !== void 0) {
386
+ throw new CnosManifestError(
387
+ `Vault "${name}" uses legacy passphrase configuration. Use vaults.${name}.auth instead.`
388
+ );
389
+ }
233
390
  const provider = definition.provider?.trim();
234
391
  if (!provider) {
235
392
  throw new CnosManifestError(`Vault "${name}" requires a provider`);
236
393
  }
394
+ const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
395
+ const normalizedMapping = Object.fromEntries(
396
+ Object.entries(definition.mapping ?? {}).filter(
397
+ (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
398
+ ).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
399
+ );
237
400
  return [
238
401
  name,
239
402
  {
240
403
  provider,
241
- ...definition.passphrase?.trim() ? {
242
- passphrase: definition.passphrase.trim()
404
+ auth: normalizedAuth,
405
+ ...Object.keys(normalizedMapping).length > 0 ? {
406
+ mapping: normalizedMapping
243
407
  } : {}
244
408
  }
245
409
  ];
246
410
  })
247
411
  );
248
412
  }
413
+ function normalizeAuthSources(value) {
414
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
415
+ return void 0;
416
+ }
417
+ const sources = Array.isArray(value.from) ? value.from : void 0;
418
+ const normalized = (sources ?? []).map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
419
+ return normalized.length > 0 ? normalized : void 0;
420
+ }
421
+ function normalizeVaultAuth(vaultName, provider, auth) {
422
+ if (provider === "local") {
423
+ const passphraseSources = normalizeAuthSources(auth?.passphrase);
424
+ const defaultToken = vaultName.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
425
+ const defaultSources = [
426
+ ...defaultToken ? [`env:CNOS_SECRET_PASSPHRASE_${defaultToken}`] : [],
427
+ "env:CNOS_SECRET_PASSPHRASE",
428
+ `keychain:cnos/${vaultName}`,
429
+ "prompt"
430
+ ];
431
+ return {
432
+ method: auth?.method ?? "passphrase",
433
+ passphrase: {
434
+ from: passphraseSources ?? defaultSources
435
+ },
436
+ ...auth?.config ? { config: auth.config } : {}
437
+ };
438
+ }
439
+ if (provider === "github-secrets") {
440
+ return {
441
+ method: auth?.method ?? "environment",
442
+ ...auth?.config ? { config: auth.config } : {}
443
+ };
444
+ }
445
+ return {
446
+ ...auth?.method ? { method: auth.method } : {},
447
+ ...normalizeAuthSources(auth?.passphrase) ? {
448
+ passphrase: {
449
+ from: normalizeAuthSources(auth?.passphrase) ?? []
450
+ }
451
+ } : {},
452
+ ...normalizeAuthSources(auth?.token) ? {
453
+ token: {
454
+ from: normalizeAuthSources(auth?.token) ?? []
455
+ }
456
+ } : {},
457
+ ...auth?.config ? { config: auth.config } : {}
458
+ };
459
+ }
249
460
  function normalizeManifest(manifest) {
250
461
  const version = manifest.version ?? 1;
251
462
  if (version !== 1) {
@@ -428,120 +639,682 @@ function validateProjectionIssue(manifest, key, target) {
428
639
  var import_promises5 = require("fs/promises");
429
640
  var import_node_path5 = __toESM(require("path"), 1);
430
641
 
642
+ // ../core/src/secrets/auditLog.ts
643
+ var import_promises8 = require("fs/promises");
644
+ var import_node_path8 = __toESM(require("path"), 1);
645
+
431
646
  // ../core/src/utils/secretStore.ts
432
647
  var import_node_crypto = require("crypto");
648
+ var import_promises7 = require("fs/promises");
649
+ var import_node_path7 = __toESM(require("path"), 1);
650
+
651
+ // ../core/src/secrets/sessionStore.ts
433
652
  var import_promises6 = require("fs/promises");
434
653
  var import_node_path6 = __toESM(require("path"), 1);
435
- function resolveSecretStoreRoot(processEnv = process.env) {
436
- return import_node_path6.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
654
+ function buildSessionRoot(processEnv = process.env) {
655
+ return import_node_path6.default.join(import_node_path6.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets")), "sessions");
437
656
  }
438
- function resolveSecretVaultFile(storeRoot, vault = "default") {
439
- return import_node_path6.default.join(storeRoot, "vaults", `${vault}.json`);
657
+ function buildSessionPath(vault, processEnv) {
658
+ return import_node_path6.default.join(buildSessionRoot(processEnv), `${vault}.json`);
440
659
  }
441
- function resolveSecretStoreFile(storeRoot, ref, vault = "default") {
442
- return import_node_path6.default.join(storeRoot, "vaults", vault, "store", ...ref.split("/")).concat(".json");
660
+ async function writeVaultSessionKey(vault, derivedKey, processEnv) {
661
+ const filePath = buildSessionPath(vault, processEnv);
662
+ await (0, import_promises6.mkdir)(import_node_path6.default.dirname(filePath), { recursive: true });
663
+ const document = {
664
+ version: 1,
665
+ vault,
666
+ derivedKey: derivedKey.toString("hex"),
667
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
668
+ };
669
+ await (0, import_promises6.writeFile)(filePath, JSON.stringify(document, null, 2), "utf8");
670
+ return filePath;
443
671
  }
444
- function deriveKey(passphrase, salt) {
445
- return (0, import_node_crypto.scryptSync)(passphrase, salt, 32);
672
+ async function readVaultSessionKey(vault, processEnv) {
673
+ try {
674
+ const source = await (0, import_promises6.readFile)(buildSessionPath(vault, processEnv), "utf8");
675
+ const document = JSON.parse(source);
676
+ if (document.version !== 1 || typeof document.derivedKey !== "string") {
677
+ return void 0;
678
+ }
679
+ const key = Buffer.from(document.derivedKey, "hex");
680
+ return key.length > 0 ? key : void 0;
681
+ } catch {
682
+ return void 0;
683
+ }
446
684
  }
447
- function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
448
- const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
449
- return processEnv[`CNOS_SECRET_PASSPHRASE_${vaultToken}`] ?? processEnv.CNOS_SECRET_PASSPHRASE;
685
+ async function clearVaultSessionKey(vault, processEnv) {
686
+ await (0, import_promises6.rm)(buildSessionPath(vault, processEnv), { force: true });
687
+ }
688
+ async function clearAllVaultSessionKeys(processEnv) {
689
+ const root = buildSessionRoot(processEnv);
690
+ try {
691
+ const entries = await (0, import_promises6.readdir)(root);
692
+ await Promise.all(entries.map((entry) => (0, import_promises6.rm)(import_node_path6.default.join(root, entry), { force: true })));
693
+ } catch {
694
+ }
695
+ }
696
+
697
+ // ../core/src/utils/secretStore.ts
698
+ var KEY_LENGTH = 32;
699
+ var SALT_LENGTH = 32;
700
+ var IV_LENGTH = 12;
701
+ var AUTH_TAG_LENGTH = 16;
702
+ var PBKDF2_ITERATIONS = 6e5;
703
+ var KEYSTORE_VERSION = 1;
704
+ var METADATA_VERSION = 1;
705
+ var META_FILENAME = "meta.yml";
706
+ var KEYSTORE_FILENAME = "keystore.enc";
707
+ function isObject(value) {
708
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
709
+ }
710
+ function isSecretReference(value) {
711
+ 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));
712
+ }
713
+ function resolveSecretStoreRoot(processEnv = process.env) {
714
+ return import_node_path7.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
715
+ }
716
+ function normalizeVaultToken(vault = "default") {
717
+ return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
450
718
  }
451
719
  function getVaultPassphraseEnvVar(vault = "default") {
452
- const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
720
+ const vaultToken = normalizeVaultToken(vault);
453
721
  return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
454
722
  }
455
723
  function isPassphraseEnvRef(value) {
456
724
  return typeof value === "string" && value.startsWith("env:") && value.length > 4;
457
725
  }
458
- function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
459
- if (definition?.provider !== "local") {
726
+ function getVaultSessionKeyEnvVar(vault = "default") {
727
+ const vaultToken = normalizeVaultToken(vault);
728
+ return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
729
+ }
730
+ function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
731
+ return processEnv[getVaultPassphraseEnvVar(vault)] ?? processEnv.CNOS_SECRET_PASSPHRASE;
732
+ }
733
+ function resolveVaultSessionKey(vault = "default", processEnv = process.env) {
734
+ const encoded = processEnv[getVaultSessionKeyEnvVar(vault)];
735
+ if (!encoded) {
736
+ return readVaultSessionKey(vault, processEnv);
737
+ }
738
+ try {
739
+ const key = Buffer.from(encoded, "hex");
740
+ return key.length === KEY_LENGTH ? key : void 0;
741
+ } catch {
460
742
  return void 0;
461
743
  }
462
- const passphraseRef = definition.passphrase;
463
- if (typeof passphraseRef === "string" && passphraseRef.startsWith("env:") && passphraseRef.length > 4) {
464
- return processEnv[passphraseRef.slice(4)];
744
+ }
745
+ function deriveVaultKey(passphrase, salt, iterations = PBKDF2_ITERATIONS) {
746
+ return (0, import_node_crypto.pbkdf2Sync)(passphrase, salt, iterations, KEY_LENGTH, "sha512");
747
+ }
748
+ function buildMetaPath(storeRoot, vault = "default") {
749
+ return import_node_path7.default.join(storeRoot, "vaults", vault, META_FILENAME);
750
+ }
751
+ function resolveSecretVaultFile(storeRoot, vault = "default") {
752
+ return buildMetaPath(storeRoot, vault);
753
+ }
754
+ function buildKeystorePath(storeRoot, vault = "default") {
755
+ return import_node_path7.default.join(storeRoot, "vaults", vault, KEYSTORE_FILENAME);
756
+ }
757
+ function buildLegacyVaultFile(storeRoot, vault = "default") {
758
+ return import_node_path7.default.join(storeRoot, "vaults", `${vault}.json`);
759
+ }
760
+ function buildLegacyVaultStoreRoot(storeRoot, vault = "default") {
761
+ return import_node_path7.default.join(storeRoot, "vaults", vault, "store");
762
+ }
763
+ function assertVaultMetadata(value, filePath) {
764
+ if (!isObject(value)) {
765
+ throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
465
766
  }
466
- if (passphraseRef) {
467
- return passphraseRef;
767
+ 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") {
768
+ throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
468
769
  }
469
- return resolveSecretPassphrase(vault, processEnv);
770
+ return value;
470
771
  }
471
- function resolveVaultDefinition(vaults, vault = "default") {
472
- const definition = vaults?.[vault];
473
- const provider = definition?.provider ?? "local";
474
- return {
475
- name: vault,
476
- provider,
477
- ...definition?.passphrase ? {
478
- passphrase: definition.passphrase
479
- } : {},
480
- requiresPassphrase: provider === "local"
481
- };
772
+ async function exists2(targetPath) {
773
+ try {
774
+ await (0, import_promises7.stat)(targetPath);
775
+ return true;
776
+ } catch {
777
+ return false;
778
+ }
779
+ }
780
+ async function detectLegacyVaultFormat(storeRoot, vault = "default") {
781
+ const legacyFile = buildLegacyVaultFile(storeRoot, vault);
782
+ const legacyStore = buildLegacyVaultStoreRoot(storeRoot, vault);
783
+ if (await exists2(legacyFile)) {
784
+ return legacyFile;
785
+ }
786
+ if (await exists2(legacyStore)) {
787
+ return legacyStore;
788
+ }
789
+ return void 0;
482
790
  }
483
- function encryptDocument(value, passphrase) {
484
- const salt = (0, import_node_crypto.randomBytes)(16);
485
- const iv = (0, import_node_crypto.randomBytes)(12);
486
- const key = deriveKey(passphrase, salt);
791
+ async function assertNoLegacyVaultFormat(storeRoot, vault = "default") {
792
+ const legacyPath = await detectLegacyVaultFormat(storeRoot, vault);
793
+ if (!legacyPath) {
794
+ return;
795
+ }
796
+ throw new CnosSecurityError(
797
+ `Legacy CNOS local vault format detected for vault "${vault}" at ${legacyPath}. CNOS 1.4 requires the new keystore format. Remove and recreate the vault.`
798
+ );
799
+ }
800
+ function encryptPayload(payload, key) {
801
+ const iv = (0, import_node_crypto.randomBytes)(IV_LENGTH);
487
802
  const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", key, iv);
488
- const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
803
+ const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
804
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
489
805
  const tag = cipher.getAuthTag();
806
+ return Buffer.concat([
807
+ Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
808
+ iv,
809
+ tag,
810
+ ciphertext
811
+ ]);
812
+ }
813
+ function decryptPayload(buffer, key) {
814
+ if (buffer.length < 4 + IV_LENGTH + AUTH_TAG_LENGTH) {
815
+ throw new CnosSecurityError("Invalid CNOS local vault keystore");
816
+ }
817
+ const version = buffer.readUInt32LE(0);
818
+ if (version !== KEYSTORE_VERSION) {
819
+ throw new CnosSecurityError(`Unsupported CNOS local vault keystore version: ${version}`);
820
+ }
821
+ const ivOffset = 4;
822
+ const tagOffset = ivOffset + IV_LENGTH;
823
+ const cipherOffset = tagOffset + AUTH_TAG_LENGTH;
824
+ const iv = buffer.subarray(ivOffset, tagOffset);
825
+ const tag = buffer.subarray(tagOffset, cipherOffset);
826
+ const ciphertext = buffer.subarray(cipherOffset);
827
+ const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, iv);
828
+ decipher.setAuthTag(tag);
829
+ try {
830
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
831
+ const payload = JSON.parse(plaintext);
832
+ if (!payload || !isObject(payload.secrets) || !isObject(payload.metadata)) {
833
+ throw new Error("invalid");
834
+ }
835
+ return {
836
+ secrets: Object.fromEntries(
837
+ Object.entries(payload.secrets).filter((entry) => typeof entry[1] === "string")
838
+ ),
839
+ metadata: Object.fromEntries(
840
+ Object.entries(payload.metadata).filter(
841
+ (entry) => isObject(entry[1]) && typeof entry[1].createdAt === "string" && typeof entry[1].updatedAt === "string"
842
+ )
843
+ )
844
+ };
845
+ } catch {
846
+ throw new CnosAuthenticationError("Failed to decrypt CNOS local vault. Check vault authentication.");
847
+ }
848
+ }
849
+ function buildInitialPayload() {
490
850
  return {
491
- version: 1,
492
- algorithm: "aes-256-gcm",
493
- salt: salt.toString("base64"),
494
- iv: iv.toString("base64"),
495
- tag: tag.toString("base64"),
496
- ciphertext: ciphertext.toString("base64")
851
+ secrets: {},
852
+ metadata: {}
497
853
  };
498
854
  }
499
- async function createSecretVault(storeRoot, vault, passphrase) {
500
- const normalizedVault = vault.trim() || "default";
501
- const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
502
- await (0, import_promises6.mkdir)(import_node_path6.default.dirname(filePath), { recursive: true });
503
- const document = {
504
- version: 1,
505
- name: normalizedVault,
506
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
507
- verifier: encryptDocument(`cnos-vault:${normalizedVault}`, passphrase)
508
- };
509
- await (0, import_promises6.writeFile)(filePath, JSON.stringify(document, null, 2), "utf8");
510
- return filePath;
855
+ async function writeVaultFiles(storeRoot, vault, meta, payload, key) {
856
+ const metaPath = buildMetaPath(storeRoot, vault);
857
+ const keystorePath = buildKeystorePath(storeRoot, vault);
858
+ await (0, import_promises7.mkdir)(import_node_path7.default.dirname(metaPath), { recursive: true });
859
+ await (0, import_promises7.writeFile)(metaPath, stringifyYaml(meta), "utf8");
860
+ await (0, import_promises7.writeFile)(keystorePath, encryptPayload(payload, key));
511
861
  }
512
- async function ensureSecretVault(storeRoot, vault, passphrase) {
513
- const normalizedVault = vault.trim() || "default";
514
- const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
862
+ async function readVaultMetadata(storeRoot, vault = "default") {
863
+ await assertNoLegacyVaultFormat(storeRoot, vault);
864
+ const metaPath = buildMetaPath(storeRoot, vault);
515
865
  try {
516
- await (0, import_promises6.readFile)(filePath, "utf8");
517
- return filePath;
866
+ const source = await (0, import_promises7.readFile)(metaPath, "utf8");
867
+ return assertVaultMetadata(parseYaml(source), metaPath);
518
868
  } catch (error) {
519
- if (error.code !== "ENOENT") {
520
- throw error;
869
+ if (error.code === "ENOENT") {
870
+ return void 0;
521
871
  }
872
+ throw error;
522
873
  }
523
- return createSecretVault(storeRoot, normalizedVault, passphrase);
524
874
  }
525
875
  async function listSecretVaults(storeRoot) {
526
- const vaultRoot = import_node_path6.default.join(storeRoot, "vaults");
876
+ const vaultRoot = import_node_path7.default.join(storeRoot, "vaults");
527
877
  try {
528
- const entries = await (0, import_promises6.readdir)(vaultRoot, { withFileTypes: true });
529
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.replace(/\.json$/, "")).sort((left, right) => left.localeCompare(right));
878
+ const entries = await (0, import_promises7.readdir)(vaultRoot, { withFileTypes: true });
879
+ const vaults = await Promise.all(
880
+ entries.filter((entry) => entry.isDirectory()).map(async (entry) => await exists2(import_node_path7.default.join(vaultRoot, entry.name, META_FILENAME)) ? entry.name : void 0)
881
+ );
882
+ return vaults.filter((value) => Boolean(value)).sort((left, right) => left.localeCompare(right));
530
883
  } catch {
531
884
  return [];
532
885
  }
533
886
  }
534
- async function writeLocalSecret(storeRoot, ref, value, passphrase, vault = "default") {
535
- await ensureSecretVault(storeRoot, vault, passphrase);
536
- const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
537
- await (0, import_promises6.mkdir)(import_node_path6.default.dirname(filePath), { recursive: true });
538
- await (0, import_promises6.writeFile)(filePath, JSON.stringify(encryptDocument(value, passphrase), null, 2), "utf8");
539
- return filePath;
887
+ async function createSecretVault(storeRoot, vault, passphrase) {
888
+ const normalizedVault = vault.trim() || "default";
889
+ await assertNoLegacyVaultFormat(storeRoot, normalizedVault);
890
+ const salt = (0, import_node_crypto.randomBytes)(SALT_LENGTH);
891
+ const key = deriveVaultKey(passphrase, salt, PBKDF2_ITERATIONS);
892
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
893
+ const meta = {
894
+ version: METADATA_VERSION,
895
+ algorithm: "aes-256-gcm",
896
+ kdf: "pbkdf2-sha512",
897
+ iterations: PBKDF2_ITERATIONS,
898
+ salt: salt.toString("base64"),
899
+ createdAt,
900
+ secretCount: 0
901
+ };
902
+ await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
903
+ return buildMetaPath(storeRoot, normalizedVault);
904
+ }
905
+ async function ensureSecretVault(storeRoot, vault, passphrase) {
906
+ const normalizedVault = vault.trim() || "default";
907
+ const meta = await readVaultMetadata(storeRoot, normalizedVault);
908
+ if (meta) {
909
+ return buildMetaPath(storeRoot, normalizedVault);
910
+ }
911
+ return createSecretVault(storeRoot, normalizedVault, passphrase);
912
+ }
913
+ function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
914
+ if (definition?.provider !== "local") {
915
+ return void 0;
916
+ }
917
+ const configuredSources = definition.auth?.passphrase?.from ?? [];
918
+ for (const source of configuredSources) {
919
+ if (source.startsWith("env:")) {
920
+ const value = processEnv[source.slice(4)];
921
+ if (value) {
922
+ return value;
923
+ }
924
+ }
925
+ }
926
+ return resolveSecretPassphrase(vault, processEnv);
927
+ }
928
+ async function resolveVaultAccessKey(storeRoot, definition, vault = "default", processEnv = process.env) {
929
+ if (definition?.provider !== "local") {
930
+ return definition?.provider === "github-secrets" ? {
931
+ method: definition.auth?.method ?? "environment",
932
+ ...definition?.auth?.config ? { config: definition.auth.config } : {}
933
+ } : void 0;
934
+ }
935
+ const sessionKey = await resolveVaultSessionKey(vault, processEnv);
936
+ if (sessionKey) {
937
+ return {
938
+ derivedKey: sessionKey,
939
+ method: "keychain",
940
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
941
+ };
942
+ }
943
+ const passphrase = resolveConfiguredVaultPassphrase(definition, vault, processEnv);
944
+ if (passphrase) {
945
+ return {
946
+ passphrase,
947
+ method: "passphrase",
948
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
949
+ };
950
+ }
951
+ const metadata = await readVaultMetadata(storeRoot, vault);
952
+ if (!metadata) {
953
+ return void 0;
954
+ }
955
+ throw new CnosAuthenticationError(
956
+ `Cannot authenticate to vault "${vault}". Set ${getVaultPassphraseEnvVar(vault)} or run cnos vault auth ${vault}.`
957
+ );
958
+ }
959
+ async function loadVaultPayload(storeRoot, vault, auth) {
960
+ const meta = await readVaultMetadata(storeRoot, vault);
961
+ if (!meta) {
962
+ throw new CnosManifestError(`Missing CNOS vault metadata for "${vault}"`);
963
+ }
964
+ const salt = Buffer.from(meta.salt, "base64");
965
+ const key = auth.derivedKey ?? (auth.passphrase ? deriveVaultKey(auth.passphrase, salt, meta.iterations) : void 0);
966
+ if (!key) {
967
+ throw new CnosAuthenticationError(`Vault "${vault}" requires authentication before access.`);
968
+ }
969
+ const buffer = await (0, import_promises7.readFile)(buildKeystorePath(storeRoot, vault));
970
+ return {
971
+ meta,
972
+ payload: decryptPayload(buffer, key),
973
+ key
974
+ };
975
+ }
976
+ async function writeLocalSecret(storeRoot, ref, value, authOrPassphrase, vault = "default") {
977
+ const auth = typeof authOrPassphrase === "string" ? {
978
+ passphrase: authOrPassphrase,
979
+ method: "passphrase"
980
+ } : authOrPassphrase;
981
+ if (auth.passphrase) {
982
+ await ensureSecretVault(storeRoot, vault, auth.passphrase);
983
+ } else {
984
+ const meta2 = await readVaultMetadata(storeRoot, vault);
985
+ if (!meta2) {
986
+ throw new CnosAuthenticationError(`Vault "${vault}" requires passphrase-based authentication for initial creation.`);
987
+ }
988
+ }
989
+ const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
990
+ const now = (/* @__PURE__ */ new Date()).toISOString();
991
+ const existing = payload.metadata[ref];
992
+ payload.secrets[ref] = value;
993
+ payload.metadata[ref] = {
994
+ createdAt: existing?.createdAt ?? now,
995
+ updatedAt: now
996
+ };
997
+ const nextMeta = {
998
+ ...meta,
999
+ secretCount: Object.keys(payload.secrets).length
1000
+ };
1001
+ await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
1002
+ return buildKeystorePath(storeRoot, vault);
1003
+ }
1004
+ async function deleteLocalSecret(storeRoot, ref, auth, vault = "default") {
1005
+ const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
1006
+ if (!(ref in payload.secrets)) {
1007
+ return false;
1008
+ }
1009
+ delete payload.secrets[ref];
1010
+ delete payload.metadata[ref];
1011
+ const nextMeta = {
1012
+ ...meta,
1013
+ secretCount: Object.keys(payload.secrets).length
1014
+ };
1015
+ await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
1016
+ return true;
1017
+ }
1018
+ async function readLocalSecret(storeRoot, ref, auth, vault = "default") {
1019
+ const { payload } = await loadVaultPayload(storeRoot, vault, auth);
1020
+ const value = payload.secrets[ref];
1021
+ if (value === void 0) {
1022
+ throw new CnosManifestError(`Missing local secret ref "${ref}" in vault "${vault}"`);
1023
+ }
1024
+ return value;
1025
+ }
1026
+ async function listLocalSecrets(storeRoot, auth, vault = "default") {
1027
+ const { payload } = await loadVaultPayload(storeRoot, vault, auth);
1028
+ return Object.keys(payload.secrets).sort((left, right) => left.localeCompare(right));
1029
+ }
1030
+ function resolveVaultDefinition(vaults, vault = "default") {
1031
+ const definition = vaults?.[vault];
1032
+ const provider = definition?.provider ?? "local";
1033
+ return {
1034
+ name: vault,
1035
+ provider,
1036
+ ...definition?.auth ? { auth: definition.auth } : {},
1037
+ ...definition?.mapping ? { mapping: definition.mapping } : {},
1038
+ requiresAuthentication: provider === "local"
1039
+ };
1040
+ }
1041
+ async function removeLocalVaultFiles(storeRoot, vault = "default") {
1042
+ await (0, import_promises7.rm)(import_node_path7.default.join(storeRoot, "vaults", vault), { recursive: true, force: true });
1043
+ }
1044
+
1045
+ // ../core/src/secrets/auditLog.ts
1046
+ async function appendAuditEvent(event, processEnv = process.env) {
1047
+ const auditFile = processEnv.CNOS_AUDIT_FILE ?? import_node_path8.default.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
1048
+ await (0, import_promises8.mkdir)(import_node_path8.default.dirname(auditFile), { recursive: true });
1049
+ await (0, import_promises8.appendFile)(
1050
+ auditFile,
1051
+ `${JSON.stringify({
1052
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1053
+ ...event
1054
+ })}
1055
+ `,
1056
+ "utf8"
1057
+ );
1058
+ }
1059
+
1060
+ // ../core/src/secrets/providers/github.ts
1061
+ var GithubSecretsVaultProvider = class {
1062
+ constructor(vaultId, definition, processEnv = process.env) {
1063
+ this.vaultId = vaultId;
1064
+ this.definition = definition;
1065
+ this.processEnv = processEnv;
1066
+ }
1067
+ vaultId;
1068
+ definition;
1069
+ processEnv;
1070
+ authenticated = false;
1071
+ async authenticate(_authConfig) {
1072
+ void _authConfig;
1073
+ this.authenticated = true;
1074
+ }
1075
+ isAuthenticated() {
1076
+ return this.authenticated;
1077
+ }
1078
+ resolveEnvVar(ref) {
1079
+ if (this.processEnv[ref] !== void 0) {
1080
+ return ref;
1081
+ }
1082
+ return Object.entries(this.definition.mapping ?? {}).find(([, logicalRef]) => logicalRef === ref)?.[0];
1083
+ }
1084
+ async batchGet(refs) {
1085
+ this.authenticated = true;
1086
+ const resolved = /* @__PURE__ */ new Map();
1087
+ for (const ref of Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right))) {
1088
+ const envVar = this.resolveEnvVar(ref);
1089
+ const value = envVar ? this.processEnv[envVar] : void 0;
1090
+ if (value !== void 0) {
1091
+ resolved.set(ref, value);
1092
+ }
1093
+ }
1094
+ return resolved;
1095
+ }
1096
+ async get(ref) {
1097
+ const envVar = this.resolveEnvVar(ref);
1098
+ this.authenticated = true;
1099
+ return envVar ? this.processEnv[envVar] : void 0;
1100
+ }
1101
+ async set(ref, value) {
1102
+ void ref;
1103
+ void value;
1104
+ throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be written by CNOS.`);
1105
+ }
1106
+ async delete(ref) {
1107
+ void ref;
1108
+ throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be mutated by CNOS.`);
1109
+ }
1110
+ async list() {
1111
+ return Object.values(this.definition.mapping ?? {}).sort((left, right) => left.localeCompare(right));
1112
+ }
1113
+ };
1114
+
1115
+ // ../core/src/secrets/providers/local.ts
1116
+ var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
1117
+ constructor(vaultId, definition, processEnv = process.env, storeRoot = resolveSecretStoreRoot(processEnv)) {
1118
+ this.vaultId = vaultId;
1119
+ this.processEnv = processEnv;
1120
+ this.storeRoot = storeRoot;
1121
+ this.definition = definition;
1122
+ }
1123
+ vaultId;
1124
+ processEnv;
1125
+ storeRoot;
1126
+ authConfig;
1127
+ definition;
1128
+ static fromVaults(vaults, vaultId, processEnv) {
1129
+ const definition = resolveVaultDefinition(vaults, vaultId);
1130
+ return new _LocalSecretVaultProvider(vaultId, definition, processEnv);
1131
+ }
1132
+ async authenticate(authConfig) {
1133
+ this.authConfig = authConfig;
1134
+ await this.list();
1135
+ }
1136
+ isAuthenticated() {
1137
+ return Boolean(this.authConfig);
1138
+ }
1139
+ async requireAuth() {
1140
+ if (this.authConfig) {
1141
+ return this.authConfig;
1142
+ }
1143
+ const resolved = await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
1144
+ if (!resolved) {
1145
+ throw new CnosAuthenticationError(
1146
+ `Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
1147
+ );
1148
+ }
1149
+ this.authConfig = resolved;
1150
+ return resolved;
1151
+ }
1152
+ async batchGet(refs) {
1153
+ const auth = await this.requireAuth();
1154
+ const entries = await Promise.all(
1155
+ Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right)).map(async (ref) => [ref, await readLocalSecret(this.storeRoot, ref, auth, this.vaultId)])
1156
+ );
1157
+ return new Map(entries);
1158
+ }
1159
+ async get(ref) {
1160
+ const auth = await this.requireAuth();
1161
+ try {
1162
+ return await readLocalSecret(this.storeRoot, ref, auth, this.vaultId);
1163
+ } catch {
1164
+ return void 0;
1165
+ }
1166
+ }
1167
+ async set(ref, value) {
1168
+ const auth = await this.requireAuth();
1169
+ await writeLocalSecret(this.storeRoot, ref, value, auth, this.vaultId);
1170
+ await appendAuditEvent(
1171
+ {
1172
+ action: "write",
1173
+ vault: this.vaultId,
1174
+ ref,
1175
+ caller: "cli"
1176
+ },
1177
+ this.processEnv
1178
+ );
1179
+ }
1180
+ async delete(ref) {
1181
+ const auth = await this.requireAuth();
1182
+ await deleteLocalSecret(this.storeRoot, ref, auth, this.vaultId);
1183
+ await appendAuditEvent(
1184
+ {
1185
+ action: "delete",
1186
+ vault: this.vaultId,
1187
+ ref,
1188
+ caller: "cli"
1189
+ },
1190
+ this.processEnv
1191
+ );
1192
+ }
1193
+ async list() {
1194
+ const auth = this.authConfig ?? await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
1195
+ if (!auth) {
1196
+ throw new CnosAuthenticationError(
1197
+ `Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
1198
+ );
1199
+ }
1200
+ this.authConfig = auth;
1201
+ return listLocalSecrets(this.storeRoot, auth, this.vaultId);
1202
+ }
1203
+ };
1204
+
1205
+ // ../core/src/secrets/providers/registry.ts
1206
+ function createSecretVaultProvider(vaultId, definition, processEnv) {
1207
+ if (definition.provider === "local") {
1208
+ return new LocalSecretVaultProvider(vaultId, definition, processEnv);
1209
+ }
1210
+ if (definition.provider === "github-secrets") {
1211
+ return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
1212
+ }
1213
+ throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
1214
+ }
1215
+
1216
+ // ../core/src/secrets/prompt.ts
1217
+ var import_node_readline = __toESM(require("readline"), 1);
1218
+ var import_node_stream = require("stream");
1219
+ async function promptHidden(message) {
1220
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1221
+ return void 0;
1222
+ }
1223
+ const mutableStdout = new WritableMask();
1224
+ const rl = import_node_readline.default.createInterface({
1225
+ input: process.stdin,
1226
+ output: mutableStdout,
1227
+ terminal: true
1228
+ });
1229
+ try {
1230
+ mutableStdout.muted = true;
1231
+ const value = await new Promise((resolve) => {
1232
+ rl.question(message, resolve);
1233
+ });
1234
+ process.stdout.write("\n");
1235
+ return value;
1236
+ } finally {
1237
+ rl.close();
1238
+ }
1239
+ }
1240
+ var WritableMask = class extends import_node_stream.Writable {
1241
+ muted = false;
1242
+ _write(chunk, _encoding, callback) {
1243
+ if (!this.muted) {
1244
+ process.stdout.write(chunk);
1245
+ }
1246
+ callback();
1247
+ }
1248
+ };
1249
+
1250
+ // ../core/src/secrets/resolveAuth.ts
1251
+ function toAuthError(vaultId, sources) {
1252
+ return new CnosAuthenticationError(
1253
+ `Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
1254
+ );
1255
+ }
1256
+ async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
1257
+ const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
1258
+ if (sessionKey) {
1259
+ return {
1260
+ derivedKey: sessionKey,
1261
+ method: "keychain",
1262
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1263
+ };
1264
+ }
1265
+ if (definition.provider === "github-secrets") {
1266
+ return {
1267
+ method: definition.auth?.method ?? "environment",
1268
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1269
+ };
1270
+ }
1271
+ const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
1272
+ for (const source of sources) {
1273
+ if (source.startsWith("env:")) {
1274
+ const value = processEnv[source.slice(4)];
1275
+ if (value) {
1276
+ return {
1277
+ passphrase: value,
1278
+ method: "passphrase",
1279
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1280
+ };
1281
+ }
1282
+ }
1283
+ if (source.startsWith("keychain:")) {
1284
+ const value = await readKeychain(source.slice("keychain:".length));
1285
+ if (value) {
1286
+ return {
1287
+ derivedKey: Buffer.from(value, "hex"),
1288
+ method: "keychain",
1289
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1290
+ };
1291
+ }
1292
+ }
1293
+ if (source === "prompt") {
1294
+ const value = await promptHidden(`Enter passphrase for vault "${vaultId}": `);
1295
+ if (value) {
1296
+ return {
1297
+ passphrase: value,
1298
+ method: "passphrase",
1299
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1300
+ };
1301
+ }
1302
+ }
1303
+ }
1304
+ const fallback = resolveSecretPassphrase(vaultId, processEnv);
1305
+ if (fallback) {
1306
+ return {
1307
+ passphrase: fallback,
1308
+ method: "passphrase",
1309
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1310
+ };
1311
+ }
1312
+ throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
540
1313
  }
541
1314
 
542
1315
  // ../core/src/runtime/dump.ts
543
- var import_promises7 = require("fs/promises");
544
- var import_node_path7 = __toESM(require("path"), 1);
1316
+ var import_promises9 = require("fs/promises");
1317
+ var import_node_path9 = __toESM(require("path"), 1);
545
1318
 
546
1319
  // ../core/src/utils/envNaming.ts
547
1320
  function normalizeMappingConfig(config = {}) {
@@ -553,8 +1326,8 @@ function normalizeMappingConfig(config = {}) {
553
1326
  function toScreamingSnakeSegment(segment) {
554
1327
  return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
555
1328
  }
556
- function toScreamingSnake(path8) {
557
- return path8.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
1329
+ function toScreamingSnake(path13) {
1330
+ return path13.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
558
1331
  }
559
1332
  function logicalKeyToEnvVar(key, config = {}) {
560
1333
  const normalized = normalizeMappingConfig(config);
@@ -687,7 +1460,10 @@ async function validateRuntime(runtime) {
687
1460
  }
688
1461
 
689
1462
  // src/runtime/bootstrap.ts
1463
+ var import_node_crypto2 = require("crypto");
690
1464
  var CNOS_GRAPH_ENV_VAR = "__CNOS_GRAPH__";
1465
+ var CNOS_SECRET_PAYLOAD_ENV_VAR = "__CNOS_SECRET_PAYLOAD__";
1466
+ var CNOS_SESSION_KEY_ENV_VAR = "__CNOS_SESSION_KEY__";
691
1467
  function serializeRuntimeGraph(graph) {
692
1468
  const payload = {
693
1469
  entries: Array.from(graph.entries.values()),
@@ -722,36 +1498,704 @@ function deserializeRuntimeGraph(source) {
722
1498
  workspace: payload.workspace
723
1499
  };
724
1500
  }
1501
+ function decryptSecretPayload(serialized, sessionKey) {
1502
+ const payload = JSON.parse(serialized);
1503
+ if (!payload || typeof payload.iv !== "string" || typeof payload.tag !== "string" || typeof payload.ciphertext !== "string") {
1504
+ throw new Error("Invalid CNOS secret payload");
1505
+ }
1506
+ const key = Buffer.from(sessionKey, "hex");
1507
+ const iv = Buffer.from(payload.iv, "base64");
1508
+ const tag = Buffer.from(payload.tag, "base64");
1509
+ const ciphertext = Buffer.from(payload.ciphertext, "base64");
1510
+ const decipher = (0, import_node_crypto2.createDecipheriv)("aes-256-gcm", key, iv);
1511
+ decipher.setAuthTag(tag);
1512
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
1513
+ return JSON.parse(plaintext);
1514
+ }
1515
+ function serializeSecretPayload(values) {
1516
+ const key = (0, import_node_crypto2.randomBytes)(32);
1517
+ const iv = (0, import_node_crypto2.randomBytes)(12);
1518
+ const cipher = (0, import_node_crypto2.createCipheriv)("aes-256-gcm", key, iv);
1519
+ const ciphertext = Buffer.concat([cipher.update(JSON.stringify(values), "utf8"), cipher.final()]);
1520
+ const tag = cipher.getAuthTag();
1521
+ return {
1522
+ payload: JSON.stringify({
1523
+ iv: iv.toString("base64"),
1524
+ tag: tag.toString("base64"),
1525
+ ciphertext: ciphertext.toString("base64")
1526
+ }),
1527
+ sessionKey: key.toString("hex")
1528
+ };
1529
+ }
725
1530
  function readRuntimeGraphFromEnv(processEnv = process.env) {
726
1531
  const serialized = processEnv[CNOS_GRAPH_ENV_VAR];
727
1532
  if (!serialized) {
728
1533
  return void 0;
729
1534
  }
730
- return deserializeRuntimeGraph(serialized);
1535
+ const graph = deserializeRuntimeGraph(serialized);
1536
+ const secretPayload = processEnv[CNOS_SECRET_PAYLOAD_ENV_VAR];
1537
+ const sessionKey = processEnv[CNOS_SESSION_KEY_ENV_VAR];
1538
+ if (secretPayload && sessionKey) {
1539
+ const decrypted = decryptSecretPayload(secretPayload, sessionKey);
1540
+ for (const [key, value] of Object.entries(decrypted)) {
1541
+ const entry = graph.entries.get(key);
1542
+ if (entry) {
1543
+ entry.value = value;
1544
+ }
1545
+ }
1546
+ }
1547
+ return graph;
1548
+ }
1549
+ function graphRequiresSecretHydration(graph) {
1550
+ return Array.from(graph.entries.values()).some((entry) => entry.namespace === "secret" && isSecretReference(entry.value));
1551
+ }
1552
+
1553
+ // src/codegen/generateTypes.ts
1554
+ function toPascalCase(value) {
1555
+ return value.split(/[^A-Za-z0-9]+/).filter((segment) => segment.length > 0).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
1556
+ }
1557
+ function mapSchemaType(rule) {
1558
+ switch (rule?.type) {
1559
+ case "number":
1560
+ return "number";
1561
+ case "string":
1562
+ return "string";
1563
+ case "boolean":
1564
+ return "boolean";
1565
+ case "object":
1566
+ return "Record<string, unknown>";
1567
+ case "array":
1568
+ return "unknown[]";
1569
+ default:
1570
+ return "unknown";
1571
+ }
1572
+ }
1573
+ function isOptional(rule) {
1574
+ return !(rule?.required ?? false) && rule?.default === void 0;
1575
+ }
1576
+ function buildNamespaceInterfaces(schema) {
1577
+ const namespaceGroups = /* @__PURE__ */ new Map();
1578
+ for (const [logicalKey, rule] of Object.entries(schema).sort(([left], [right]) => left.localeCompare(right))) {
1579
+ const separatorIndex = logicalKey.indexOf(".");
1580
+ if (separatorIndex <= 0 || separatorIndex >= logicalKey.length - 1) {
1581
+ continue;
1582
+ }
1583
+ const namespace = logicalKey.slice(0, separatorIndex);
1584
+ const path13 = logicalKey.slice(separatorIndex + 1);
1585
+ const existing = namespaceGroups.get(namespace) ?? [];
1586
+ existing.push({
1587
+ key: path13,
1588
+ rule
1589
+ });
1590
+ namespaceGroups.set(namespace, existing);
1591
+ }
1592
+ const orderedNamespaces = Array.from(namespaceGroups.keys()).sort((left, right) => {
1593
+ if (left === "value") {
1594
+ return -1;
1595
+ }
1596
+ if (right === "value") {
1597
+ return 1;
1598
+ }
1599
+ if (left === "secret") {
1600
+ return -1;
1601
+ }
1602
+ if (right === "secret") {
1603
+ return 1;
1604
+ }
1605
+ return left.localeCompare(right);
1606
+ });
1607
+ if (orderedNamespaces.length === 0) {
1608
+ return [
1609
+ "export interface CnosValueConfig {}",
1610
+ "export interface CnosSecretConfig {}",
1611
+ "",
1612
+ "export interface CnosConfig {",
1613
+ " value: CnosValueConfig;",
1614
+ " secret: CnosSecretConfig;",
1615
+ "}"
1616
+ ];
1617
+ }
1618
+ const blocks = [];
1619
+ for (const namespace of orderedNamespaces) {
1620
+ const entries = namespaceGroups.get(namespace) ?? [];
1621
+ const interfaceName = namespace === "value" ? "CnosValueConfig" : namespace === "secret" ? "CnosSecretConfig" : `Cnos${toPascalCase(namespace)}Config`;
1622
+ blocks.push(`export interface ${interfaceName} {`);
1623
+ for (const entry of entries) {
1624
+ const optional = isOptional(entry.rule) ? "?" : "";
1625
+ blocks.push(` "${entry.key}"${optional}: ${mapSchemaType(entry.rule)};`);
1626
+ }
1627
+ blocks.push("}");
1628
+ blocks.push("");
1629
+ }
1630
+ const configEntries = orderedNamespaces.map((namespace) => {
1631
+ const interfaceName = namespace === "value" ? "CnosValueConfig" : namespace === "secret" ? "CnosSecretConfig" : `Cnos${toPascalCase(namespace)}Config`;
1632
+ return ` ${namespace}: ${interfaceName};`;
1633
+ });
1634
+ blocks.push("export interface CnosConfig {");
1635
+ blocks.push(...configEntries);
1636
+ blocks.push("}");
1637
+ if (!orderedNamespaces.includes("value")) {
1638
+ blocks.push("");
1639
+ blocks.push("export interface CnosValueConfig {}");
1640
+ }
1641
+ if (!orderedNamespaces.includes("secret")) {
1642
+ blocks.push("");
1643
+ blocks.push("export interface CnosSecretConfig {}");
1644
+ }
1645
+ return blocks;
1646
+ }
1647
+ function generateCodegenContent(manifest, sourcePath, typeModuleImport = "./cnos") {
1648
+ const schema = manifest.schema ?? {};
1649
+ const hasSchema = Object.keys(schema).length > 0;
1650
+ const interfaceBlocks = buildNamespaceInterfaces(schema);
1651
+ const typesLines = [
1652
+ "// Auto-generated by cnos codegen. Do not edit.",
1653
+ `// Source: ${sourcePath}`,
1654
+ "",
1655
+ ...interfaceBlocks,
1656
+ "",
1657
+ 'import type { CnosRuntime } from "@kitsy/cnos";',
1658
+ "",
1659
+ "export interface TypedCnosRuntime extends CnosRuntime {",
1660
+ " value<K extends keyof CnosValueConfig>(path: K): CnosValueConfig[K] | undefined;",
1661
+ " secret<K extends keyof CnosSecretConfig>(path: K): CnosSecretConfig[K] | undefined;",
1662
+ " require(key: `value.${keyof CnosValueConfig}`): CnosValueConfig[keyof CnosValueConfig];",
1663
+ " require(key: `secret.${keyof CnosSecretConfig}`): CnosSecretConfig[keyof CnosSecretConfig];",
1664
+ "}",
1665
+ ""
1666
+ ];
1667
+ if (!hasSchema) {
1668
+ typesLines.push("// Hint: add a schema section to .cnos/cnos.yml for typed key generation.");
1669
+ typesLines.push("");
1670
+ }
1671
+ const runtimeLines = [
1672
+ "// Auto-generated by cnos codegen. Do not edit.",
1673
+ `// Source: ${sourcePath}`,
1674
+ "",
1675
+ 'import { createCnos as _createCnos, type CnosCreateOptions } from "@kitsy/cnos/configure";',
1676
+ `import type { TypedCnosRuntime } from "${typeModuleImport}";`,
1677
+ "",
1678
+ "export async function createCnos(options: CnosCreateOptions = {}): Promise<TypedCnosRuntime> {",
1679
+ " return (await _createCnos(options)) as TypedCnosRuntime;",
1680
+ "}",
1681
+ ""
1682
+ ];
1683
+ return {
1684
+ typesContent: typesLines.join("\n"),
1685
+ runtimeContent: runtimeLines.join("\n"),
1686
+ schemaEntryCount: Object.keys(schema).length,
1687
+ hasSchema
1688
+ };
1689
+ }
1690
+
1691
+ // src/codegen/writeOutput.ts
1692
+ var import_promises10 = require("fs/promises");
1693
+ var import_node_path10 = __toESM(require("path"), 1);
1694
+ function stripTsExtension(filePath) {
1695
+ return filePath.replace(/(\.d)?\.[cm]?tsx?$/i, "").replace(/\.[cm]?jsx?$/i, "");
1696
+ }
1697
+ function resolveCodegenPaths(repoRoot, out) {
1698
+ const typesPath = out ? import_node_path10.default.resolve(repoRoot, out) : import_node_path10.default.join(repoRoot, ".cnos", "types", "cnos.d.ts");
1699
+ const runtimePath = import_node_path10.default.join(import_node_path10.default.dirname(typesPath), "runtime.ts");
1700
+ const typeImportPath = `./${import_node_path10.default.basename(stripTsExtension(typesPath))}`;
1701
+ return {
1702
+ typesPath,
1703
+ runtimePath,
1704
+ typeImportPath
1705
+ };
1706
+ }
1707
+ async function writeCodegenOutput(options = {}) {
1708
+ const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
1709
+ const paths = resolveCodegenPaths(loadedManifest.repoRoot, options.out);
1710
+ const generated = generateCodegenContent(loadedManifest.manifest, loadedManifest.manifestPath, paths.typeImportPath);
1711
+ await (0, import_promises10.mkdir)(import_node_path10.default.dirname(paths.typesPath), { recursive: true });
1712
+ await (0, import_promises10.mkdir)(import_node_path10.default.dirname(paths.runtimePath), { recursive: true });
1713
+ await (0, import_promises10.writeFile)(paths.typesPath, generated.typesContent, "utf8");
1714
+ await (0, import_promises10.writeFile)(paths.runtimePath, generated.runtimeContent, "utf8");
1715
+ return {
1716
+ manifestPath: loadedManifest.manifestPath,
1717
+ typesPath: paths.typesPath,
1718
+ runtimePath: paths.runtimePath,
1719
+ schemaEntryCount: generated.schemaEntryCount,
1720
+ hasSchema: generated.hasSchema
1721
+ };
1722
+ }
1723
+
1724
+ // src/codegen/watchSchema.ts
1725
+ var import_node_fs = require("fs");
1726
+ async function watchSchema(options = {}) {
1727
+ const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
1728
+ let timeout;
1729
+ const debounceMs = options.debounceMs ?? 300;
1730
+ const runWrite = async () => {
1731
+ try {
1732
+ const result = await writeCodegenOutput(options);
1733
+ await options.onWrite?.(result);
1734
+ } catch (error) {
1735
+ await options.onError?.(error);
1736
+ }
1737
+ };
1738
+ await runWrite();
1739
+ const watcher = (0, import_node_fs.watch)(loadedManifest.manifestPath, () => {
1740
+ if (timeout) {
1741
+ clearTimeout(timeout);
1742
+ }
1743
+ timeout = setTimeout(() => {
1744
+ void runWrite();
1745
+ }, debounceMs);
1746
+ });
1747
+ watcher.on("close", () => {
1748
+ if (timeout) {
1749
+ clearTimeout(timeout);
1750
+ }
1751
+ });
1752
+ return watcher;
1753
+ }
1754
+
1755
+ // src/drift/compareSchemaToGraph.ts
1756
+ function describeValueType(value) {
1757
+ if (Array.isArray(value)) {
1758
+ return "array";
1759
+ }
1760
+ if (value === null) {
1761
+ return "null";
1762
+ }
1763
+ return typeof value;
1764
+ }
1765
+ function matchesType(value, type) {
1766
+ if (!type) {
1767
+ return true;
1768
+ }
1769
+ switch (type) {
1770
+ case "array":
1771
+ return Array.isArray(value);
1772
+ case "object":
1773
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1774
+ default:
1775
+ return typeof value === type;
1776
+ }
1777
+ }
1778
+ function isSchemaDefault(entry) {
1779
+ return entry.winner.metadata?.schemaDefault === true;
1780
+ }
1781
+ function shouldTrackKey(key) {
1782
+ return key.startsWith("value.") || key.startsWith("secret.");
1783
+ }
1784
+ function isTransientRuntimeSource(entry) {
1785
+ return entry.winner.sourceId === "process-env" || entry.winner.sourceId === "cli-args";
1786
+ }
1787
+ function compareSchemaToGraph(runtime) {
1788
+ const schema = runtime.manifest.schema;
1789
+ const missing = [];
1790
+ const mismatches = [];
1791
+ const defaultsApplied = [];
1792
+ for (const [key, rule] of Object.entries(schema).sort(([left], [right]) => left.localeCompare(right))) {
1793
+ const entry = runtime.graph.entries.get(key);
1794
+ if (!entry) {
1795
+ if (rule.required && rule.default === void 0) {
1796
+ missing.push({
1797
+ key,
1798
+ ...rule.type ? {
1799
+ expectedType: rule.type
1800
+ } : {}
1801
+ });
1802
+ }
1803
+ continue;
1804
+ }
1805
+ if (isSchemaDefault(entry)) {
1806
+ defaultsApplied.push({
1807
+ key,
1808
+ value: entry.value
1809
+ });
1810
+ }
1811
+ const actualValue = entry.winner.value;
1812
+ if (!matchesType(actualValue, rule.type)) {
1813
+ mismatches.push({
1814
+ key,
1815
+ ...rule.type ? {
1816
+ expectedType: rule.type
1817
+ } : {},
1818
+ actualType: describeValueType(actualValue),
1819
+ value: actualValue,
1820
+ ...entry.winner.origin?.file ? {
1821
+ sourceFile: entry.winner.origin.file
1822
+ } : {}
1823
+ });
1824
+ }
1825
+ }
1826
+ const undeclared = Array.from(runtime.graph.entries.values()).filter(
1827
+ (entry) => shouldTrackKey(entry.key) && !schema[entry.key] && !isSchemaDefault(entry) && !isTransientRuntimeSource(entry)
1828
+ ).map((entry) => {
1829
+ const issue = {
1830
+ key: entry.key,
1831
+ value: entry.winner.value,
1832
+ actualType: describeValueType(entry.winner.value)
1833
+ };
1834
+ if (entry.winner.origin?.file) {
1835
+ issue.sourceFile = entry.winner.origin.file;
1836
+ }
1837
+ return issue;
1838
+ }).sort((left, right) => left.key.localeCompare(right.key));
1839
+ return {
1840
+ profile: runtime.graph.profile,
1841
+ workspace: runtime.graph.workspace.workspaceId,
1842
+ missing,
1843
+ undeclared,
1844
+ mismatches,
1845
+ defaultsApplied
1846
+ };
1847
+ }
1848
+
1849
+ // src/drift/formatDriftReport.ts
1850
+ function formatIssueList(title, marker, issues, formatter) {
1851
+ if (issues.length === 0) {
1852
+ return [];
1853
+ }
1854
+ return [
1855
+ `${title}:`,
1856
+ ...issues.map((issue) => ` ${marker} ${formatter(issue)}`),
1857
+ ""
1858
+ ];
1859
+ }
1860
+ function formatDriftReport(report) {
1861
+ const lines = [
1862
+ `Schema vs resolved config (${report.workspace} / ${report.profile}):`,
1863
+ ""
1864
+ ];
1865
+ lines.push(
1866
+ ...formatIssueList("Missing (required, not defined)", "x", report.missing, (issue) => issue.key)
1867
+ );
1868
+ lines.push(
1869
+ ...formatIssueList(
1870
+ "Undeclared (defined, not in schema)",
1871
+ "?",
1872
+ report.undeclared,
1873
+ (issue) => issue.sourceFile ? `${issue.key} (found in ${issue.sourceFile})` : issue.key
1874
+ )
1875
+ );
1876
+ lines.push(
1877
+ ...formatIssueList("Type mismatches", "x", report.mismatches, (issue) => {
1878
+ const actual = issue.value === void 0 ? issue.actualType : `${issue.actualType} ${JSON.stringify(issue.value)}`;
1879
+ return `${issue.key} (schema: ${issue.expectedType}, actual: ${actual})`;
1880
+ })
1881
+ );
1882
+ lines.push(
1883
+ ...formatIssueList(
1884
+ "Defaults applied",
1885
+ "i",
1886
+ report.defaultsApplied,
1887
+ (issue) => `${issue.key} (using default: ${JSON.stringify(issue.value)})`
1888
+ )
1889
+ );
1890
+ if (lines[lines.length - 1] === "") {
1891
+ lines.pop();
1892
+ }
1893
+ if (lines.length === 2) {
1894
+ lines.push("No drift detected.");
1895
+ }
1896
+ return lines.join("\n");
1897
+ }
1898
+
1899
+ // src/migrate/applyManifest.ts
1900
+ var import_promises11 = require("fs/promises");
1901
+ function sortRecord(record) {
1902
+ return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
1903
+ }
1904
+ async function applyManifestMappings(proposals, root) {
1905
+ const loadedManifest = await loadManifest(root ? { root } : {});
1906
+ const rawManifest = {
1907
+ ...loadedManifest.rawManifest
1908
+ };
1909
+ const explicit = {
1910
+ ...rawManifest.envMapping?.explicit ?? {}
1911
+ };
1912
+ const promoted = new Set(rawManifest.public?.promote ?? []);
1913
+ let appliedMappings = 0;
1914
+ let appliedPromotions = 0;
1915
+ for (const proposal of proposals) {
1916
+ if (explicit[proposal.envVar] !== proposal.logicalKey) {
1917
+ explicit[proposal.envVar] = proposal.logicalKey;
1918
+ appliedMappings += 1;
1919
+ }
1920
+ if (proposal.public && !promoted.has(proposal.logicalKey)) {
1921
+ promoted.add(proposal.logicalKey);
1922
+ appliedPromotions += 1;
1923
+ }
1924
+ }
1925
+ rawManifest.envMapping = {
1926
+ ...rawManifest.envMapping ?? {},
1927
+ explicit: sortRecord(explicit)
1928
+ };
1929
+ if (promoted.size > 0) {
1930
+ rawManifest.public = {
1931
+ ...rawManifest.public ?? {},
1932
+ promote: Array.from(promoted).sort((left, right) => left.localeCompare(right))
1933
+ };
1934
+ }
1935
+ await (0, import_promises11.writeFile)(loadedManifest.manifestPath, stringifyYaml(rawManifest), "utf8");
1936
+ return {
1937
+ manifestPath: loadedManifest.manifestPath,
1938
+ appliedMappings,
1939
+ appliedPromotions
1940
+ };
1941
+ }
1942
+
1943
+ // src/migrate/proposeMapping.ts
1944
+ var SECRET_TOKENS = ["PASSWORD", "SECRET", "KEY", "TOKEN"];
1945
+ function normalizeSegments(value) {
1946
+ return value.split("_").map((segment) => segment.trim().toLowerCase()).filter((segment) => segment.length > 0);
1947
+ }
1948
+ function isSecretEnvVar(value) {
1949
+ return SECRET_TOKENS.some((token) => value.includes(`_${token}`) || value.endsWith(token));
1950
+ }
1951
+ function proposeMapping(envVar) {
1952
+ const framework = envVar.startsWith("VITE_") ? "vite" : envVar.startsWith("NEXT_PUBLIC_") ? "next" : void 0;
1953
+ const strippedEnvVar = framework === "vite" ? envVar.slice("VITE_".length) : framework === "next" ? envVar.slice("NEXT_PUBLIC_".length) : envVar;
1954
+ const namespace = isSecretEnvVar(strippedEnvVar) && !framework ? "secret" : "value";
1955
+ const segments = normalizeSegments(strippedEnvVar);
1956
+ const logicalPath = segments.join(".");
1957
+ return {
1958
+ envVar,
1959
+ namespace,
1960
+ logicalPath,
1961
+ logicalKey: `${namespace}.${logicalPath}`,
1962
+ public: Boolean(framework),
1963
+ ...framework ? {
1964
+ framework
1965
+ } : {}
1966
+ };
1967
+ }
1968
+
1969
+ // src/migrate/rewriteSource.ts
1970
+ var import_promises12 = require("fs/promises");
1971
+ function importStatementFor(kind) {
1972
+ return kind === "import-meta-env" ? "import cnos from '@kitsy/cnos/browser';" : "import cnos from '@kitsy/cnos';";
1973
+ }
1974
+ function replacementFor(proposal) {
1975
+ if (proposal.public) {
1976
+ return `cnos.read(${JSON.stringify(`public.${proposal.logicalPath}`)})`;
1977
+ }
1978
+ return proposal.namespace === "secret" ? `cnos.secret(${JSON.stringify(proposal.logicalPath)})` : `cnos.value(${JSON.stringify(proposal.logicalPath)})`;
1979
+ }
1980
+ async function rewriteSourceFiles(usages, proposals) {
1981
+ const fileGroups = /* @__PURE__ */ new Map();
1982
+ for (const usage of usages) {
1983
+ const existing = fileGroups.get(usage.filePath) ?? [];
1984
+ existing.push(usage);
1985
+ fileGroups.set(usage.filePath, existing);
1986
+ }
1987
+ const rewrittenFiles = [];
1988
+ const backupFiles = [];
1989
+ const skippedUsages = [];
1990
+ for (const [filePath, fileUsages] of fileGroups.entries()) {
1991
+ const original = await (0, import_promises12.readFile)(filePath, "utf8");
1992
+ let nextSource = original;
1993
+ let changed = false;
1994
+ const importKinds = /* @__PURE__ */ new Set();
1995
+ for (const usage of fileUsages) {
1996
+ const proposal = proposals.get(usage.envVar);
1997
+ if (!proposal) {
1998
+ skippedUsages.push(`${filePath}:${usage.source}`);
1999
+ continue;
2000
+ }
2001
+ if (usage.kind === "import-meta-env" && proposal.namespace === "secret") {
2002
+ skippedUsages.push(`${filePath}:${usage.source}`);
2003
+ continue;
2004
+ }
2005
+ const replacement = replacementFor(proposal);
2006
+ if (!nextSource.includes(usage.source)) {
2007
+ skippedUsages.push(`${filePath}:${usage.source}`);
2008
+ continue;
2009
+ }
2010
+ nextSource = nextSource.split(usage.source).join(replacement);
2011
+ changed = true;
2012
+ importKinds.add(usage.kind);
2013
+ }
2014
+ if (!changed) {
2015
+ continue;
2016
+ }
2017
+ const backupPath = `${filePath}.bak`;
2018
+ await (0, import_promises12.copyFile)(filePath, backupPath);
2019
+ backupFiles.push(backupPath);
2020
+ for (const kind of Array.from(importKinds)) {
2021
+ const importStatement = importStatementFor(kind);
2022
+ if (!nextSource.includes(importStatement)) {
2023
+ nextSource = `${importStatement}
2024
+ ${nextSource}`;
2025
+ }
2026
+ }
2027
+ await (0, import_promises12.writeFile)(filePath, nextSource, "utf8");
2028
+ rewrittenFiles.push(filePath);
2029
+ }
2030
+ return {
2031
+ rewrittenFiles: rewrittenFiles.sort((left, right) => left.localeCompare(right)),
2032
+ backupFiles: backupFiles.sort((left, right) => left.localeCompare(right)),
2033
+ skippedUsages: skippedUsages.sort((left, right) => left.localeCompare(right))
2034
+ };
2035
+ }
2036
+
2037
+ // src/migrate/scanEnvUsage.ts
2038
+ var import_promises13 = require("fs/promises");
2039
+ var import_node_path11 = __toESM(require("path"), 1);
2040
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"]);
2041
+ var PROCESS_ENV_DOT = /process\.env\.([A-Z][A-Z0-9_]*)/g;
2042
+ var PROCESS_ENV_BRACKET = /process\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g;
2043
+ var IMPORT_META_ENV_DOT = /import\.meta\.env\.([A-Z][A-Z0-9_]*)/g;
2044
+ var IMPORT_META_ENV_BRACKET = /import\.meta\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g;
2045
+ async function collectFiles(root) {
2046
+ const entries = await (0, import_promises13.readdir)(root, { withFileTypes: true });
2047
+ const files = [];
2048
+ for (const entry of entries) {
2049
+ const filePath = import_node_path11.default.join(root, entry.name);
2050
+ if (entry.isDirectory()) {
2051
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") {
2052
+ continue;
2053
+ }
2054
+ files.push(...await collectFiles(filePath));
2055
+ continue;
2056
+ }
2057
+ if (SOURCE_EXTENSIONS.has(import_node_path11.default.extname(entry.name))) {
2058
+ files.push(filePath);
2059
+ }
2060
+ }
2061
+ return files;
2062
+ }
2063
+ function collectMatches(filePath, source, pattern, kind) {
2064
+ const matches = [];
2065
+ for (const match of source.matchAll(pattern)) {
2066
+ const envVar = match[1];
2067
+ if (!envVar) {
2068
+ continue;
2069
+ }
2070
+ matches.push({
2071
+ filePath,
2072
+ envVar,
2073
+ source: match[0],
2074
+ kind
2075
+ });
2076
+ }
2077
+ return matches;
2078
+ }
2079
+ async function scanEnvUsage(scanRoot) {
2080
+ const files = await collectFiles(scanRoot);
2081
+ const usages = [];
2082
+ for (const filePath of files) {
2083
+ const source = await (0, import_promises13.readFile)(filePath, "utf8");
2084
+ usages.push(...collectMatches(filePath, source, PROCESS_ENV_DOT, "process-env"));
2085
+ usages.push(...collectMatches(filePath, source, PROCESS_ENV_BRACKET, "process-env"));
2086
+ usages.push(...collectMatches(filePath, source, IMPORT_META_ENV_DOT, "import-meta-env"));
2087
+ usages.push(...collectMatches(filePath, source, IMPORT_META_ENV_BRACKET, "import-meta-env"));
2088
+ }
2089
+ return usages.sort((left, right) => {
2090
+ const byFile = left.filePath.localeCompare(right.filePath);
2091
+ if (byFile !== 0) {
2092
+ return byFile;
2093
+ }
2094
+ return left.envVar.localeCompare(right.envVar);
2095
+ });
2096
+ }
2097
+
2098
+ // src/watch/diffGraphs.ts
2099
+ function stableStringify(value) {
2100
+ if (Array.isArray(value)) {
2101
+ return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
2102
+ }
2103
+ if (value && typeof value === "object") {
2104
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(",")}}`;
2105
+ }
2106
+ return JSON.stringify(value);
2107
+ }
2108
+ function diffGraphs(previous, next) {
2109
+ const keys = /* @__PURE__ */ new Set([
2110
+ ...Array.from(previous.entries.keys()),
2111
+ ...Array.from(next.entries.keys())
2112
+ ]);
2113
+ return Array.from(keys).filter((key) => !key.startsWith("meta.")).filter((key) => {
2114
+ const previousEntry = previous.entries.get(key);
2115
+ const nextEntry = next.entries.get(key);
2116
+ if (!previousEntry || !nextEntry) {
2117
+ return previousEntry?.namespace !== "meta" && nextEntry?.namespace !== "meta";
2118
+ }
2119
+ return stableStringify(previousEntry.value) !== stableStringify(nextEntry.value);
2120
+ }).sort((left, right) => left.localeCompare(right));
2121
+ }
2122
+
2123
+ // src/watch/watchFiles.ts
2124
+ var import_node_path12 = __toESM(require("path"), 1);
2125
+ async function watchFiles(runtime, root) {
2126
+ const manifest = await loadManifest(root ? { root } : {});
2127
+ const roots = Array.from(
2128
+ new Set(runtime.graph.workspace.workspaceRoots.map((workspaceRoot) => workspaceRoot.path))
2129
+ ).sort((left, right) => left.localeCompare(right));
2130
+ const files = Array.from(
2131
+ new Set(
2132
+ Array.from(runtime.graph.entries.values()).map((entry) => entry.winner.origin?.file).filter((file) => Boolean(file)).map((file) => import_node_path12.default.resolve(manifest.repoRoot, file))
2133
+ )
2134
+ ).sort((left, right) => left.localeCompare(right));
2135
+ return {
2136
+ manifestPath: manifest.manifestPath,
2137
+ roots,
2138
+ files
2139
+ };
731
2140
  }
732
2141
  // Annotate the CommonJS export names for ESM import in node:
733
2142
  0 && (module.exports = {
734
2143
  CNOS_GRAPH_ENV_VAR,
2144
+ CNOS_SECRET_PAYLOAD_ENV_VAR,
2145
+ CNOS_SESSION_KEY_ENV_VAR,
2146
+ CnosAuthenticationError,
735
2147
  CnosSecurityError,
2148
+ applyManifestMappings,
2149
+ clearAllVaultSessionKeys,
2150
+ clearVaultSessionKey,
2151
+ compareSchemaToGraph,
736
2152
  createSecretVault,
2153
+ createSecretVaultProvider,
2154
+ deleteLocalSecret,
2155
+ deriveVaultKey,
737
2156
  deserializeRuntimeGraph,
2157
+ detectLegacyVaultFormat,
2158
+ diffGraphs,
738
2159
  ensureProjectionAllowed,
739
2160
  flattenObject,
2161
+ formatDriftReport,
2162
+ generateCodegenContent,
2163
+ getNamespaceDefinition,
740
2164
  getVaultPassphraseEnvVar,
2165
+ getVaultSessionKeyEnvVar,
2166
+ graphRequiresSecretHydration,
741
2167
  isPassphraseEnvRef,
2168
+ isSecretReference,
2169
+ listLocalSecrets,
742
2170
  listSecretVaults,
743
2171
  loadManifest,
744
2172
  parseYaml,
2173
+ proposeMapping,
2174
+ readKeychain,
2175
+ readLocalSecret,
745
2176
  readRuntimeGraphFromEnv,
2177
+ readVaultMetadata,
2178
+ removeLocalVaultFiles,
2179
+ resolveCodegenPaths,
746
2180
  resolveConfigDocumentPath,
747
2181
  resolveConfiguredVaultPassphrase,
748
2182
  resolveManifestRoot,
749
2183
  resolveSecretPassphrase,
750
2184
  resolveSecretStoreRoot,
751
2185
  resolveSecretVaultFile,
2186
+ resolveVaultAccessKey,
2187
+ resolveVaultAuth,
752
2188
  resolveVaultDefinition,
2189
+ rewriteSourceFiles,
2190
+ scanEnvUsage,
753
2191
  serializeRuntimeGraph,
2192
+ serializeSecretPayload,
754
2193
  stringifyYaml,
755
2194
  validateRuntime,
756
- writeLocalSecret
2195
+ watchFiles,
2196
+ watchSchema,
2197
+ writeCodegenOutput,
2198
+ writeKeychain,
2199
+ writeLocalSecret,
2200
+ writeVaultSessionKey
757
2201
  });