@kitsy/cnos 1.9.2 → 1.11.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 (61) hide show
  1. package/dist/build/index.cjs +523 -80
  2. package/dist/build/index.d.cts +1 -1
  3. package/dist/build/index.d.ts +1 -1
  4. package/dist/build/index.js +13 -15
  5. package/dist/{chunk-6QQPHDUI.js → chunk-2DMCB3PK.js} +1 -1
  6. package/dist/{chunk-LURQ4LAK.js → chunk-5JGNRADB.js} +1 -1
  7. package/dist/{chunk-2JBA2LXU.js → chunk-DPC2BV3S.js} +35 -6
  8. package/dist/{chunk-7JZO6XN3.js → chunk-KJ57PF47.js} +1 -1
  9. package/dist/{chunk-CPGRRZLP.js → chunk-NFGPS7VJ.js} +8 -8
  10. package/dist/{chunk-A2WG3ZKW.js → chunk-NU25VFA2.js} +1 -1
  11. package/dist/{chunk-L7JVECPE.js → chunk-RNTTPI5S.js} +1 -1
  12. package/dist/{chunk-NVFACB64.js → chunk-T3E57MSQ.js} +1 -1
  13. package/dist/{chunk-7KVM5PUW.js → chunk-WPB4HB2K.js} +478 -61
  14. package/dist/{chunk-QK7BMU47.js → chunk-XGK6DXQL.js} +157 -37
  15. package/dist/configure/index.cjs +521 -76
  16. package/dist/configure/index.d.cts +3 -3
  17. package/dist/configure/index.d.ts +3 -3
  18. package/dist/configure/index.js +8 -8
  19. package/dist/{core-zDTUSVx9.d.cts → core-BW8SLnRx.d.cts} +46 -7
  20. package/dist/{core-zDTUSVx9.d.ts → core-BW8SLnRx.d.ts} +46 -7
  21. package/dist/{envNaming-EFzezmB3.d.cts → envNaming-1rk7BR0e.d.cts} +1 -1
  22. package/dist/{envNaming-BkorOKW_.d.ts → envNaming-CjL28IeH.d.ts} +1 -1
  23. package/dist/index.cjs +672 -108
  24. package/dist/index.d.cts +2 -2
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.js +10 -10
  27. package/dist/internal.cjs +378 -54
  28. package/dist/internal.d.cts +32 -4
  29. package/dist/internal.d.ts +32 -4
  30. package/dist/internal.js +141 -23
  31. package/dist/plugin/basic-schema.cjs +13 -3
  32. package/dist/plugin/basic-schema.d.cts +1 -1
  33. package/dist/plugin/basic-schema.d.ts +1 -1
  34. package/dist/plugin/basic-schema.js +2 -2
  35. package/dist/plugin/cli-args.cjs +4 -1
  36. package/dist/plugin/cli-args.d.cts +1 -1
  37. package/dist/plugin/cli-args.d.ts +1 -1
  38. package/dist/plugin/cli-args.js +2 -2
  39. package/dist/plugin/dotenv.cjs +40 -8
  40. package/dist/plugin/dotenv.d.cts +2 -2
  41. package/dist/plugin/dotenv.d.ts +2 -2
  42. package/dist/plugin/dotenv.js +2 -2
  43. package/dist/plugin/env-export.cjs +5 -2
  44. package/dist/plugin/env-export.d.cts +2 -2
  45. package/dist/plugin/env-export.d.ts +2 -2
  46. package/dist/plugin/env-export.js +2 -2
  47. package/dist/plugin/filesystem.cjs +13 -10
  48. package/dist/plugin/filesystem.d.cts +1 -1
  49. package/dist/plugin/filesystem.d.ts +1 -1
  50. package/dist/plugin/filesystem.js +2 -2
  51. package/dist/plugin/process-env.cjs +4 -1
  52. package/dist/plugin/process-env.d.cts +2 -2
  53. package/dist/plugin/process-env.d.ts +2 -2
  54. package/dist/plugin/process-env.js +2 -2
  55. package/dist/runtime/index.cjs +672 -108
  56. package/dist/runtime/index.d.cts +13 -6
  57. package/dist/runtime/index.d.ts +13 -6
  58. package/dist/runtime/index.js +10 -10
  59. package/dist/{toPublicEnv-Ds1DRwCX.d.cts → toPublicEnv-CZzpvhGg.d.cts} +1 -1
  60. package/dist/{toPublicEnv-CT265rzS.d.ts → toPublicEnv-CmydGcxg.d.ts} +1 -1
  61. package/package.json +1 -1
@@ -1355,6 +1355,134 @@ function resolveRemoteRootCachePaths(uri, processEnv = process.env) {
1355
1355
  import { readFile as readFile3 } from "fs/promises";
1356
1356
  import path6 from "path";
1357
1357
 
1358
+ // ../core/src/spec/normalizeSpecRule.ts
1359
+ var ALLOWED_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "object", "array"]);
1360
+ var SECRET_FORBIDDEN_FIELDS = ["default", "examples", "enum"];
1361
+ function hasOwn(target, key) {
1362
+ return Object.prototype.hasOwnProperty.call(target, key);
1363
+ }
1364
+ function normalizeOptionalString(value, fieldName, logicalKey) {
1365
+ if (value === void 0) {
1366
+ return void 0;
1367
+ }
1368
+ if (typeof value !== "string") {
1369
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "${fieldName}" must be a string.`);
1370
+ }
1371
+ const nextValue = value.trim();
1372
+ return nextValue.length > 0 ? nextValue : void 0;
1373
+ }
1374
+ function normalizeStringArray(value, fieldName, logicalKey) {
1375
+ if (value === void 0) {
1376
+ return void 0;
1377
+ }
1378
+ if (!Array.isArray(value)) {
1379
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "${fieldName}" must be an array.`);
1380
+ }
1381
+ const nextValue = value.map((entry) => {
1382
+ if (typeof entry !== "string") {
1383
+ throw new CnosManifestError(
1384
+ `Invalid schema rule for ${logicalKey}: "${fieldName}" entries must be strings.`
1385
+ );
1386
+ }
1387
+ return entry.trim();
1388
+ }).filter(Boolean);
1389
+ return nextValue.length > 0 ? nextValue : void 0;
1390
+ }
1391
+ function normalizeUnknownArray(value, fieldName, logicalKey) {
1392
+ if (value === void 0) {
1393
+ return void 0;
1394
+ }
1395
+ if (!Array.isArray(value)) {
1396
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "${fieldName}" must be an array.`);
1397
+ }
1398
+ return value.length > 0 ? value : void 0;
1399
+ }
1400
+ function assertValidPatternRegex(pattern, logicalKey) {
1401
+ try {
1402
+ void new RegExp(pattern);
1403
+ } catch (error) {
1404
+ const reason = error instanceof Error ? error.message : String(error);
1405
+ throw new CnosManifestError(
1406
+ `Invalid schema rule for ${logicalKey}: "pattern" must be a valid regex (${reason}).`
1407
+ );
1408
+ }
1409
+ }
1410
+ function assertSecretRuleSafety(logicalKey, rule) {
1411
+ if (!logicalKey.startsWith("secret.")) {
1412
+ return;
1413
+ }
1414
+ const offendingFields = SECRET_FORBIDDEN_FIELDS.filter((field) => hasOwn(rule, field));
1415
+ if (offendingFields.length === 0) {
1416
+ return;
1417
+ }
1418
+ throw new CnosManifestError(
1419
+ `Invalid schema rule for ${logicalKey}: secret specs cannot include ${offendingFields.join(", ")}. Store secret values in the vault, not schema metadata. Remove ${offendingFields.map((field) => `schema.${logicalKey}.${field}`).join(", ")} to continue.`
1420
+ );
1421
+ }
1422
+ function normalizeSpecRule(logicalKey, rule) {
1423
+ if (!rule || typeof rule !== "object" || Array.isArray(rule)) {
1424
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: expected an object.`);
1425
+ }
1426
+ const candidate = rule;
1427
+ assertSecretRuleSafety(logicalKey, candidate);
1428
+ const normalized = {};
1429
+ if (candidate.type !== void 0) {
1430
+ if (typeof candidate.type !== "string" || !ALLOWED_TYPES.has(candidate.type)) {
1431
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: unsupported type "${String(candidate.type)}".`);
1432
+ }
1433
+ normalized.type = candidate.type;
1434
+ }
1435
+ if (candidate.required !== void 0) {
1436
+ if (typeof candidate.required !== "boolean") {
1437
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "required" must be a boolean.`);
1438
+ }
1439
+ normalized.required = candidate.required;
1440
+ }
1441
+ if (hasOwn(candidate, "default")) {
1442
+ normalized.default = candidate.default;
1443
+ }
1444
+ const normalizedEnum = normalizeUnknownArray(candidate.enum, "enum", logicalKey);
1445
+ if (normalizedEnum !== void 0) {
1446
+ normalized.enum = normalizedEnum;
1447
+ }
1448
+ const normalizedPattern = normalizeOptionalString(candidate.pattern, "pattern", logicalKey);
1449
+ if (normalizedPattern !== void 0) {
1450
+ assertValidPatternRegex(normalizedPattern, logicalKey);
1451
+ normalized.pattern = normalizedPattern;
1452
+ }
1453
+ const normalizedSummary = normalizeOptionalString(candidate.summary, "summary", logicalKey);
1454
+ if (normalizedSummary !== void 0) {
1455
+ normalized.summary = normalizedSummary;
1456
+ }
1457
+ const normalizedDescription = normalizeOptionalString(candidate.description, "description", logicalKey);
1458
+ if (normalizedDescription !== void 0) {
1459
+ normalized.description = normalizedDescription;
1460
+ }
1461
+ const normalizedExamples = normalizeUnknownArray(candidate.examples, "examples", logicalKey);
1462
+ if (normalizedExamples !== void 0) {
1463
+ normalized.examples = normalizedExamples;
1464
+ }
1465
+ const normalizedUsedBy = normalizeStringArray(candidate.usedBy, "usedBy", logicalKey);
1466
+ if (normalizedUsedBy !== void 0) {
1467
+ normalized.usedBy = normalizedUsedBy;
1468
+ }
1469
+ if (candidate.deprecated !== void 0) {
1470
+ if (typeof candidate.deprecated !== "boolean") {
1471
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "deprecated" must be a boolean.`);
1472
+ }
1473
+ normalized.deprecated = candidate.deprecated;
1474
+ }
1475
+ const normalizedDeprecationMessage = normalizeOptionalString(
1476
+ candidate.deprecationMessage,
1477
+ "deprecationMessage",
1478
+ logicalKey
1479
+ );
1480
+ if (normalizedDeprecationMessage !== void 0) {
1481
+ normalized.deprecationMessage = normalizedDeprecationMessage;
1482
+ }
1483
+ return normalized;
1484
+ }
1485
+
1358
1486
  // ../core/src/manifest/normalizeManifest.ts
1359
1487
  var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
1360
1488
  var DEFAULT_LOADERS = [
@@ -1488,11 +1616,19 @@ function normalizeVaults(vaults) {
1488
1616
  throw new CnosManifestError(`Vault "${name}" requires a provider`);
1489
1617
  }
1490
1618
  const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
1491
- const normalizedMapping = Object.fromEntries(
1492
- Object.entries(definition.mapping ?? {}).filter(
1493
- (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
1494
- ).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
1495
- );
1619
+ const normalizedMapping = normalizeVaultMapping(definition.mapping);
1620
+ const fallback = (definition.fallback ?? []).map((entry, index) => {
1621
+ const fallbackProvider = entry.provider?.trim();
1622
+ if (!fallbackProvider) {
1623
+ throw new CnosManifestError(`Vault "${name}" fallback ${index + 1} requires a provider`);
1624
+ }
1625
+ const fallbackMapping = normalizeVaultMapping(entry.mapping);
1626
+ return {
1627
+ provider: fallbackProvider,
1628
+ auth: normalizeVaultAuth(name, fallbackProvider, entry.auth),
1629
+ ...Object.keys(fallbackMapping).length > 0 ? { mapping: fallbackMapping } : {}
1630
+ };
1631
+ });
1496
1632
  return [
1497
1633
  name,
1498
1634
  {
@@ -1500,12 +1636,20 @@ function normalizeVaults(vaults) {
1500
1636
  auth: normalizedAuth,
1501
1637
  ...Object.keys(normalizedMapping).length > 0 ? {
1502
1638
  mapping: normalizedMapping
1503
- } : {}
1639
+ } : {},
1640
+ ...fallback.length > 0 ? { fallback } : {}
1504
1641
  }
1505
1642
  ];
1506
1643
  })
1507
1644
  );
1508
1645
  }
1646
+ function normalizeVaultMapping(mapping) {
1647
+ return Object.fromEntries(
1648
+ Object.entries(mapping ?? {}).filter(
1649
+ (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
1650
+ ).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
1651
+ );
1652
+ }
1509
1653
  function normalizeAuthSources(value) {
1510
1654
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1511
1655
  return void 0;
@@ -1553,6 +1697,14 @@ function normalizeVaultAuth(vaultName, provider, auth) {
1553
1697
  ...auth?.config ? { config: auth.config } : {}
1554
1698
  };
1555
1699
  }
1700
+ function normalizeSchema(schema) {
1701
+ return Object.fromEntries(
1702
+ Object.entries(schema ?? {}).map(([logicalKey, rule]) => [
1703
+ logicalKey,
1704
+ normalizeSpecRule(logicalKey, rule)
1705
+ ])
1706
+ );
1707
+ }
1556
1708
  function normalizeManifest(manifest) {
1557
1709
  const version = manifest.version ?? 1;
1558
1710
  if (version !== 1) {
@@ -1650,7 +1802,7 @@ function normalizeManifest(manifest) {
1650
1802
  }
1651
1803
  }
1652
1804
  },
1653
- schema: manifest.schema ?? {}
1805
+ schema: normalizeSchema(manifest.schema)
1654
1806
  };
1655
1807
  }
1656
1808
 
@@ -1803,7 +1955,7 @@ function isObject(value) {
1803
1955
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1804
1956
  }
1805
1957
  function isSecretReference(value) {
1806
- 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));
1958
+ return isObject(value) && (value.provider === void 0 || 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));
1807
1959
  }
1808
1960
  function resolveSecretStoreRoot(processEnv = process.env) {
1809
1961
  return path8.resolve(expandHomePath2(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
@@ -2201,16 +2353,19 @@ import { appendFile, mkdir as mkdir5 } from "fs/promises";
2201
2353
  import path9 from "path";
2202
2354
  async function appendAuditEvent(event, processEnv = process.env) {
2203
2355
  const auditFile = processEnv.CNOS_AUDIT_FILE ?? path9.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
2204
- await mkdir5(path9.dirname(auditFile), { recursive: true });
2205
- await appendFile(
2206
- auditFile,
2207
- `${JSON.stringify({
2208
- ts: (/* @__PURE__ */ new Date()).toISOString(),
2209
- ...event
2210
- })}
2356
+ try {
2357
+ await mkdir5(path9.dirname(auditFile), { recursive: true });
2358
+ await appendFile(
2359
+ auditFile,
2360
+ `${JSON.stringify({
2361
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2362
+ ...event
2363
+ })}
2211
2364
  `,
2212
- "utf8"
2213
- );
2365
+ "utf8"
2366
+ );
2367
+ } catch {
2368
+ }
2214
2369
  }
2215
2370
 
2216
2371
  // ../core/src/secrets/providers/local.ts
@@ -2304,7 +2459,7 @@ var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
2304
2459
  };
2305
2460
 
2306
2461
  // ../core/src/secrets/providers/registry.ts
2307
- function createSecretVaultProvider(vaultId, definition, processEnv) {
2462
+ function createSecretVaultProvider(vaultId, definition, processEnv, factories = []) {
2308
2463
  if (definition.provider === "local") {
2309
2464
  return new LocalSecretVaultProvider(vaultId, definition, processEnv);
2310
2465
  }
@@ -2314,9 +2469,30 @@ function createSecretVaultProvider(vaultId, definition, processEnv) {
2314
2469
  if (definition.provider === "github-secrets") {
2315
2470
  return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
2316
2471
  }
2472
+ const factory = factories.find((candidate) => candidate.provider === definition.provider);
2473
+ if (factory) {
2474
+ return factory.create(vaultId, definition, processEnv);
2475
+ }
2317
2476
  throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
2318
2477
  }
2319
2478
 
2479
+ // ../core/src/secrets/providerCompatibility.ts
2480
+ function assertSecretRefVaultProviderCompatible(manifest, ref, logicalKey = "secret ref") {
2481
+ if (!ref.vault || !ref.provider) {
2482
+ return;
2483
+ }
2484
+ const definition = manifest.vaults[ref.vault];
2485
+ if (!definition || definition.provider === ref.provider) {
2486
+ return;
2487
+ }
2488
+ throw new CnosManifestError(
2489
+ `Secret ref "${logicalKey}" declares provider "${ref.provider}" but vault "${ref.vault}" uses provider "${definition.provider}". Remove the ref provider or use a matching vault.`
2490
+ );
2491
+ }
2492
+
2493
+ // ../core/src/secrets/resolveAuth.ts
2494
+ import { readFile as readFile6 } from "fs/promises";
2495
+
2320
2496
  // ../core/src/secrets/prompt.ts
2321
2497
  import readline from "readline";
2322
2498
  import { Writable } from "stream";
@@ -2357,6 +2533,23 @@ function toAuthError(vaultId, sources) {
2357
2533
  `Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
2358
2534
  );
2359
2535
  }
2536
+ async function resolveTokenFromSource(source, processEnv) {
2537
+ if (source.startsWith("env:")) {
2538
+ return processEnv[source.slice(4)] || void 0;
2539
+ }
2540
+ if (source.startsWith("file:")) {
2541
+ try {
2542
+ const value = await readFile6(expandHomePath2(source.slice("file:".length)), "utf8");
2543
+ return value.trim() || void 0;
2544
+ } catch {
2545
+ return void 0;
2546
+ }
2547
+ }
2548
+ if (source.startsWith("keychain:")) {
2549
+ return readKeychain(source.slice("keychain:".length));
2550
+ }
2551
+ return void 0;
2552
+ }
2360
2553
  async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
2361
2554
  const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
2362
2555
  if (sessionKey) {
@@ -2372,6 +2565,32 @@ async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
2372
2565
  ...definition.auth?.config ? { config: definition.auth.config } : {}
2373
2566
  };
2374
2567
  }
2568
+ if (definition.auth?.method === "iam") {
2569
+ return {
2570
+ method: "iam",
2571
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
2572
+ };
2573
+ }
2574
+ if (definition.auth?.method === "environment") {
2575
+ return {
2576
+ method: "environment",
2577
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
2578
+ };
2579
+ }
2580
+ const tokenSources = definition.auth?.token?.from ?? [];
2581
+ for (const source of tokenSources) {
2582
+ const token = await resolveTokenFromSource(source, processEnv);
2583
+ if (token) {
2584
+ return {
2585
+ token,
2586
+ method: "token",
2587
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
2588
+ };
2589
+ }
2590
+ }
2591
+ if (definition.auth?.method === "token") {
2592
+ throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...tokenSources]);
2593
+ }
2375
2594
  const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
2376
2595
  for (const source of sources) {
2377
2596
  if (source.startsWith("env:")) {
@@ -2825,12 +3044,12 @@ function createProvenanceInspector() {
2825
3044
  }
2826
3045
 
2827
3046
  // ../core/src/manifest/loadWorkspaceFile.ts
2828
- import { readFile as readFile6 } from "fs/promises";
3047
+ import { readFile as readFile7 } from "fs/promises";
2829
3048
  import path11 from "path";
2830
3049
  async function loadWorkspaceFile(repoRoot) {
2831
3050
  const workspaceFilePath = path11.join(repoRoot, ".cnos-workspace.yml");
2832
3051
  try {
2833
- const source = await readFile6(workspaceFilePath, "utf8");
3052
+ const source = await readFile7(workspaceFilePath, "utf8");
2834
3053
  const parsed = parseYaml(source);
2835
3054
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2836
3055
  throw new CnosManifestError(".cnos-workspace.yml must be a YAML object", workspaceFilePath);
@@ -2853,7 +3072,7 @@ async function loadWorkspaceFile(repoRoot) {
2853
3072
  }
2854
3073
 
2855
3074
  // ../core/src/profiles/expandProfileChain.ts
2856
- import { access as access4, readFile as readFile7 } from "fs/promises";
3075
+ import { access as access4, readFile as readFile8 } from "fs/promises";
2857
3076
  import path12 from "path";
2858
3077
  async function fileExists(targetPath) {
2859
3078
  try {
@@ -2895,7 +3114,7 @@ async function loadProfileDefinition(profileName, options) {
2895
3114
  if (!await fileExists(profilePath)) {
2896
3115
  continue;
2897
3116
  }
2898
- const document = await readFile7(profilePath, "utf8");
3117
+ const document = await readFile8(profilePath, "utf8");
2899
3118
  const parsed = parseYaml(document);
2900
3119
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2901
3120
  throw new CnosManifestError("Profile definition must be a YAML object", profilePath);
@@ -3380,6 +3599,13 @@ function enumMatches(value, allowed) {
3380
3599
  const serialized = JSON.stringify(value);
3381
3600
  return allowed.some((candidate) => JSON.stringify(candidate) === serialized);
3382
3601
  }
3602
+ function testPattern(pattern, value) {
3603
+ try {
3604
+ return new RegExp(pattern).test(value);
3605
+ } catch {
3606
+ return false;
3607
+ }
3608
+ }
3383
3609
  function applySchemaRules(graph, schema) {
3384
3610
  const nextEntries = new Map(graph.entries);
3385
3611
  const issues = [];
@@ -3446,11 +3672,11 @@ function applySchemaRules(graph, schema) {
3446
3672
  key,
3447
3673
  message: `Config key ${key} must be a string to match pattern ${rule.pattern}`
3448
3674
  });
3449
- } else if (!new RegExp(rule.pattern).test(coercedValue)) {
3675
+ } else if (!testPattern(rule.pattern, coercedValue)) {
3450
3676
  issues.push({
3451
3677
  code: "schema.pattern",
3452
3678
  key,
3453
- message: `Config key ${key} does not match pattern ${rule.pattern}`
3679
+ message: `Config key ${key} does not match pattern ${rule.pattern} (or the pattern is invalid).`
3454
3680
  });
3455
3681
  }
3456
3682
  }
@@ -3504,6 +3730,23 @@ var SecretCache = class {
3504
3730
  get(vaultId, ref) {
3505
3731
  return this.cache.get(`${vaultId}:${ref}`);
3506
3732
  }
3733
+ delete(vaultId, ref) {
3734
+ this.cache.delete(`${vaultId}:${ref}`);
3735
+ }
3736
+ replace(vaultId, secrets) {
3737
+ this.clear(vaultId);
3738
+ this.load(vaultId, secrets);
3739
+ }
3740
+ entriesForVault(vaultId) {
3741
+ const entries = /* @__PURE__ */ new Map();
3742
+ for (const [key, value] of this.cache) {
3743
+ const prefix = `${vaultId}:`;
3744
+ if (key.startsWith(prefix)) {
3745
+ entries.set(key.slice(prefix.length), value);
3746
+ }
3747
+ }
3748
+ return entries;
3749
+ }
3507
3750
  clear(vaultId) {
3508
3751
  if (!vaultId) {
3509
3752
  this.cache.clear();
@@ -3526,22 +3769,76 @@ function collectSecretDescriptors(graph) {
3526
3769
  ref: entry.value
3527
3770
  }));
3528
3771
  }
3529
- async function batchResolveSecrets(graph, manifest, processEnv = process.env) {
3772
+ function secretGroupKey(manifest, descriptor) {
3773
+ assertSecretRefVaultProviderCompatible(manifest, descriptor.ref, descriptor.logicalKey);
3774
+ const vaultId = descriptor.ref.vault ?? "default";
3775
+ const provider = descriptor.ref.provider ?? manifest.vaults[vaultId]?.provider ?? "local";
3776
+ return `${vaultId}\0${provider}`;
3777
+ }
3778
+ function vaultDefinitionForRef(manifest, ref) {
3779
+ assertSecretRefVaultProviderCompatible(manifest, ref);
3780
+ const vaultId = ref.vault ?? "default";
3781
+ const base = manifest.vaults[vaultId] ?? { provider: "local", auth: { passphrase: { from: [] } } };
3782
+ if (!ref.provider || ref.provider === base.provider) {
3783
+ return base;
3784
+ }
3785
+ return {
3786
+ ...base,
3787
+ provider: ref.provider
3788
+ };
3789
+ }
3790
+ async function resolveFromDefinition(vaultId, definition, refs, processEnv, factories) {
3791
+ const runtimeDefinition = {
3792
+ provider: definition.provider,
3793
+ ...definition.auth ? { auth: definition.auth } : {},
3794
+ ...definition.mapping ? { mapping: definition.mapping } : {}
3795
+ };
3796
+ const provider = createSecretVaultProvider(vaultId, runtimeDefinition, processEnv, factories);
3797
+ const auth = await resolveVaultAuth(vaultId, runtimeDefinition, processEnv);
3798
+ await provider.authenticate(auth);
3799
+ return provider.batchGet(refs.map((entry) => entry.ref.ref));
3800
+ }
3801
+ async function batchResolveSecrets(graph, manifest, processEnv = process.env, factories = []) {
3530
3802
  const cache = new SecretCache();
3531
3803
  const descriptors = collectSecretDescriptors(graph);
3532
3804
  const grouped = descriptors.reduce((accumulator, descriptor) => {
3533
- const vaultId = descriptor.ref.vault ?? "default";
3534
- const bucket = accumulator.get(vaultId) ?? [];
3805
+ const key = secretGroupKey(manifest, descriptor);
3806
+ const bucket = accumulator.get(key) ?? [];
3535
3807
  bucket.push(descriptor);
3536
- accumulator.set(vaultId, bucket);
3808
+ accumulator.set(key, bucket);
3537
3809
  return accumulator;
3538
3810
  }, /* @__PURE__ */ new Map());
3539
- for (const [vaultId, refs] of grouped) {
3540
- const definition = manifest.vaults[vaultId] ?? { provider: "local", auth: { passphrase: { from: [] } } };
3541
- const provider = createSecretVaultProvider(vaultId, definition, processEnv);
3542
- const auth = await resolveVaultAuth(vaultId, definition, processEnv);
3543
- await provider.authenticate(auth);
3544
- const resolved = await provider.batchGet(refs.map((entry) => entry.ref.ref));
3811
+ for (const refs of grouped.values()) {
3812
+ const first = refs[0];
3813
+ if (!first) {
3814
+ continue;
3815
+ }
3816
+ const vaultId = first.ref.vault ?? "default";
3817
+ const definition = vaultDefinitionForRef(manifest, first.ref);
3818
+ const definitions = [definition, ...definition.fallback ?? []];
3819
+ const resolved = /* @__PURE__ */ new Map();
3820
+ let remaining = refs;
3821
+ let lastError;
3822
+ for (const candidate of definitions) {
3823
+ try {
3824
+ const candidateValues = await resolveFromDefinition(vaultId, candidate, remaining, processEnv, factories);
3825
+ for (const descriptor of remaining) {
3826
+ const value = candidateValues.get(descriptor.ref.ref);
3827
+ if (value !== void 0) {
3828
+ resolved.set(descriptor.ref.ref, value);
3829
+ }
3830
+ }
3831
+ remaining = remaining.filter((descriptor) => !resolved.has(descriptor.ref.ref));
3832
+ if (remaining.length === 0) {
3833
+ break;
3834
+ }
3835
+ } catch (error) {
3836
+ lastError = error;
3837
+ }
3838
+ }
3839
+ if (resolved.size === 0 && lastError) {
3840
+ throw lastError;
3841
+ }
3545
3842
  cache.load(vaultId, resolved);
3546
3843
  await appendAuditEvent(
3547
3844
  {
@@ -3562,7 +3859,11 @@ function resolveSecretEntryValue(key, value, cache) {
3562
3859
  return value;
3563
3860
  }
3564
3861
  const vaultId = value.vault ?? "default";
3565
- return cache.get(vaultId, value.ref) ?? value;
3862
+ const resolved = cache.get(vaultId, value.ref);
3863
+ if (resolved !== void 0 || cache.isVaultAuthenticated(vaultId)) {
3864
+ return resolved;
3865
+ }
3866
+ return value;
3566
3867
  }
3567
3868
 
3568
3869
  // ../core/src/runtime/toServerProjection.ts
@@ -3587,19 +3888,117 @@ function configHash(values) {
3587
3888
  function shouldProjectResolvedValue(sourceId) {
3588
3889
  return sourceId !== "process-env";
3589
3890
  }
3891
+ var SAFE_PROJECTED_CONFIG_KEYS = /* @__PURE__ */ new Set([
3892
+ "address",
3893
+ "audience",
3894
+ "clientid",
3895
+ "endpoint",
3896
+ "mount",
3897
+ "namespace",
3898
+ "path",
3899
+ "projectid",
3900
+ "region",
3901
+ "scope",
3902
+ "scopes",
3903
+ "serviceaccountemail",
3904
+ "tenant",
3905
+ "tenantid",
3906
+ "url",
3907
+ "version",
3908
+ "vaulturl"
3909
+ ]);
3910
+ function isSafeProjectedConfigKey(key) {
3911
+ return SAFE_PROJECTED_CONFIG_KEYS.has(key.replace(/[^A-Za-z0-9]/g, "").toLowerCase());
3912
+ }
3913
+ function sanitizeProjectedConfigValue(value) {
3914
+ if (Array.isArray(value)) {
3915
+ return value.map((item) => sanitizeProjectedConfigValue(item));
3916
+ }
3917
+ if (!value || typeof value !== "object") {
3918
+ return value;
3919
+ }
3920
+ return stableSortObject(
3921
+ Object.fromEntries(
3922
+ Object.entries(value).map(([key, item]) => [key, sanitizeProjectedConfigValue(item)]).filter(([key, item]) => {
3923
+ if (item && typeof item === "object" && !Array.isArray(item)) {
3924
+ return Object.keys(item).length > 0;
3925
+ }
3926
+ return isSafeProjectedConfigKey(key);
3927
+ })
3928
+ )
3929
+ );
3930
+ }
3931
+ function sanitizeProjectedConfig(config) {
3932
+ const sanitized = sanitizeProjectedConfigValue(config);
3933
+ if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized)) {
3934
+ return void 0;
3935
+ }
3936
+ return Object.keys(sanitized).length > 0 ? sanitized : void 0;
3937
+ }
3938
+ function projectVaultAuth(definition) {
3939
+ const auth = definition.auth;
3940
+ if (!auth) {
3941
+ return void 0;
3942
+ }
3943
+ const config = auth.config ? sanitizeProjectedConfig(auth.config) : void 0;
3944
+ const projected = {
3945
+ ...auth.method ? { method: auth.method } : {},
3946
+ ...auth.passphrase?.from ? {
3947
+ passphrase: {
3948
+ from: [...auth.passphrase.from]
3949
+ }
3950
+ } : {},
3951
+ ...auth.token?.from ? {
3952
+ token: {
3953
+ from: [...auth.token.from]
3954
+ }
3955
+ } : {},
3956
+ ...config ? { config } : {}
3957
+ };
3958
+ return Object.keys(projected).length > 0 ? projected : void 0;
3959
+ }
3960
+ function projectVaultDefinition(definition) {
3961
+ const auth = projectVaultAuth(definition);
3962
+ const mapping = definition.mapping ? stableSortObject(definition.mapping) : void 0;
3963
+ const fallback = definition.fallback?.map((entry) => projectVaultDefinition({
3964
+ provider: entry.provider,
3965
+ ...entry.auth ? { auth: entry.auth } : {},
3966
+ ...entry.mapping ? { mapping: entry.mapping } : {}
3967
+ }));
3968
+ return {
3969
+ provider: definition.provider,
3970
+ ...auth ? { auth } : {},
3971
+ ...mapping && Object.keys(mapping).length > 0 ? { mapping } : {},
3972
+ ...fallback && fallback.length > 0 ? { fallback } : {}
3973
+ };
3974
+ }
3975
+ function projectReferencedVaults(manifest, vaultIds) {
3976
+ const projected = {};
3977
+ for (const vaultId of Array.from(vaultIds).sort((left, right) => left.localeCompare(right))) {
3978
+ const definition = manifest.vaults[vaultId];
3979
+ if (definition) {
3980
+ projected[vaultId] = projectVaultDefinition(definition);
3981
+ }
3982
+ }
3983
+ return Object.keys(projected).length > 0 ? projected : void 0;
3984
+ }
3590
3985
  function toServerProjection(graph, manifest, cnosVersion = "0.0.0-dev", helpers = {}) {
3591
3986
  const values = {};
3592
3987
  const derived = {};
3593
3988
  const secretRefs = {};
3989
+ const referencedVaultIds = /* @__PURE__ */ new Set();
3594
3990
  const namespaces = /* @__PURE__ */ new Set();
3595
3991
  const runtimeNamespaces = /* @__PURE__ */ new Set();
3596
3992
  const publicKeys = Array.from(graph.entries.values()).filter((entry) => entry.namespace === "public").map((entry) => entry.key.slice("public.".length)).sort((left, right) => left.localeCompare(right));
3597
3993
  for (const [key, entry] of graph.entries) {
3598
3994
  if (entry.namespace === "secret" && isSecretReference(entry.value)) {
3995
+ assertSecretRefVaultProviderCompatible(manifest, entry.value, key);
3599
3996
  const vaultId = entry.value.vault ?? "default";
3997
+ const provider = entry.value.provider ?? manifest.vaults[vaultId]?.provider ?? "local";
3600
3998
  const envVar = resolveProjectedEnvVar(manifest, vaultId, entry.value.ref);
3999
+ referencedVaultIds.add(vaultId);
3601
4000
  secretRefs[key.slice("secret.".length)] = {
3602
- provider: entry.value.provider,
4001
+ provider,
3603
4002
  vault: vaultId,
3604
4003
  ref: entry.value.ref,
3605
4004
  ...envVar ? {
@@ -3645,6 +4044,7 @@ function toServerProjection(graph, manifest, cnosVersion = "0.0.0-dev", helpers
3645
4044
  namespaces.add(entry.namespace);
3646
4045
  }
3647
4046
  }
4047
+ const vaults = projectReferencedVaults(manifest, referencedVaultIds);
3648
4048
  return {
3649
4049
  version: 1,
3650
4050
  workspace: graph.workspace.workspaceId,
@@ -3654,6 +4054,7 @@ function toServerProjection(graph, manifest, cnosVersion = "0.0.0-dev", helpers
3654
4054
  values: stableSortObject(values),
3655
4055
  derived: stableSortObject(derived),
3656
4056
  secretRefs: stableSortObject(secretRefs),
4057
+ ...vaults ? { vaults } : {},
3657
4058
  publicKeys,
3658
4059
  runtimeNamespaces: Array.from(runtimeNamespaces).sort((left, right) => left.localeCompare(right)),
3659
4060
  meta: {
@@ -3666,9 +4067,10 @@ function toServerProjection(graph, manifest, cnosVersion = "0.0.0-dev", helpers
3666
4067
  }
3667
4068
 
3668
4069
  // ../core/src/orchestrator/runtime.ts
3669
- function createRuntime(manifest, graph, plugins = [], secretCache, processEnv = process.env, cnosVersion = "0.0.0-dev") {
4070
+ function createRuntime(manifest, graph, plugins = [], secretCache, processEnv = process.env, cnosVersion = "0.0.0-dev", secretVaultProviders = []) {
3670
4071
  const runtimeProviders = createDefaultRuntimeProviders(manifest, processEnv);
3671
4072
  const derivedSupport = createDerivedRuntimeSupport(graph, manifest, runtimeProviders);
4073
+ let activeSecretCache = secretCache;
3672
4074
  function resolveProjectedSourceKey(key) {
3673
4075
  if (!key.startsWith("public.")) {
3674
4076
  return key;
@@ -3685,30 +4087,38 @@ function createRuntime(manifest, graph, plugins = [], secretCache, processEnv =
3685
4087
  if (!entry || entry.namespace !== "secret" || !isSecretReference(entry.value)) {
3686
4088
  return;
3687
4089
  }
3688
- if (!secretCache) {
4090
+ if (!activeSecretCache) {
3689
4091
  return;
3690
4092
  }
3691
4093
  const vaultId = entry.value.vault ?? "default";
3692
- const definition = manifest.vaults[vaultId] ?? {
3693
- provider: entry.value.provider,
3694
- auth: { passphrase: { from: [] } }
3695
- };
3696
- const provider = createSecretVaultProvider(vaultId, definition, processEnv);
3697
- const auth = await resolveVaultAuth(vaultId, definition, processEnv);
3698
- await provider.authenticate(auth);
3699
- const value = await provider.get(entry.value.ref);
3700
- if (value !== void 0) {
3701
- secretCache.load(vaultId, /* @__PURE__ */ new Map([[entry.value.ref, value]]));
4094
+ const refreshed = await batchResolveSecrets(
4095
+ {
4096
+ ...graph,
4097
+ entries: /* @__PURE__ */ new Map([[key, entry]])
4098
+ },
4099
+ manifest,
4100
+ processEnv,
4101
+ secretVaultProviders
4102
+ );
4103
+ const resolved = refreshed.get(vaultId, entry.value.ref);
4104
+ const existing = activeSecretCache.entriesForVault(vaultId);
4105
+ existing.delete(entry.value.ref);
4106
+ if (resolved !== void 0) {
4107
+ existing.set(entry.value.ref, resolved);
3702
4108
  }
4109
+ activeSecretCache.replace(vaultId, existing);
3703
4110
  }
3704
4111
  async function refreshAllSecrets() {
3705
- if (!secretCache) {
4112
+ if (!activeSecretCache) {
3706
4113
  return;
3707
4114
  }
3708
- const secretKeys = Array.from(graph.entries.values()).filter((entry) => entry.namespace === "secret" && isSecretReference(entry.value)).map((entry) => entry.key);
3709
- for (const key of secretKeys) {
3710
- await refreshSecretEntry(key);
3711
- }
4115
+ const refreshed = await batchResolveSecrets(
4116
+ graph,
4117
+ manifest,
4118
+ processEnv,
4119
+ secretVaultProviders
4120
+ );
4121
+ activeSecretCache = refreshed;
3712
4122
  }
3713
4123
  function readLogicalKey(key) {
3714
4124
  const resolved = derivedSupport.read(key, (ref) => {
@@ -3716,10 +4126,10 @@ function createRuntime(manifest, graph, plugins = [], secretCache, processEnv =
3716
4126
  if (!entry2) {
3717
4127
  return void 0;
3718
4128
  }
3719
- if (!secretCache) {
4129
+ if (!activeSecretCache) {
3720
4130
  return entry2.value;
3721
4131
  }
3722
- return resolveSecretEntryValue(ref, entry2.value, secretCache);
4132
+ return resolveSecretEntryValue(ref, entry2.value, activeSecretCache);
3723
4133
  });
3724
4134
  if (resolved !== void 0 || graph.entries.has(key) || manifest.runtimeNamespaces[key.split(".")[0] ?? ""]) {
3725
4135
  return resolved;
@@ -3728,10 +4138,10 @@ function createRuntime(manifest, graph, plugins = [], secretCache, processEnv =
3728
4138
  if (!entry) {
3729
4139
  return void 0;
3730
4140
  }
3731
- if (!secretCache) {
4141
+ if (!activeSecretCache) {
3732
4142
  return entry.value;
3733
4143
  }
3734
- return resolveSecretEntryValue(key, entry.value, secretCache);
4144
+ return resolveSecretEntryValue(key, entry.value, activeSecretCache);
3735
4145
  }
3736
4146
  return {
3737
4147
  manifest,
@@ -3768,10 +4178,10 @@ function createRuntime(manifest, graph, plugins = [], secretCache, processEnv =
3768
4178
  if (!entry) {
3769
4179
  return void 0;
3770
4180
  }
3771
- if (!secretCache) {
4181
+ if (!activeSecretCache) {
3772
4182
  return entry.value;
3773
4183
  }
3774
- return resolveSecretEntryValue(candidate, entry.value, secretCache);
4184
+ return resolveSecretEntryValue(candidate, entry.value, activeSecretCache);
3775
4185
  })
3776
4186
  });
3777
4187
  },
@@ -3962,7 +4372,12 @@ async function createCnos(options = {}) {
3962
4372
  });
3963
4373
  const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
3964
4374
  const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
3965
- const secretCache = options.secretResolution === "lazy" ? new SecretCache() : await batchResolveSecrets(promotedGraph, loadedManifest.manifest, options.processEnv);
4375
+ const secretCache = options.secretResolution === "lazy" ? new SecretCache() : await batchResolveSecrets(
4376
+ promotedGraph,
4377
+ loadedManifest.manifest,
4378
+ options.processEnv,
4379
+ options.secretVaultProviders
4380
+ );
3966
4381
  return createRuntime(
3967
4382
  loadedManifest.manifest,
3968
4383
  appendMetaEntries({
@@ -3972,7 +4387,8 @@ async function createCnos(options = {}) {
3972
4387
  plugins,
3973
4388
  secretCache,
3974
4389
  options.processEnv,
3975
- options.cnosVersion
4390
+ options.cnosVersion,
4391
+ options.secretVaultProviders
3976
4392
  );
3977
4393
  }
3978
4394
 
@@ -4035,6 +4451,7 @@ export {
4035
4451
  resolveVaultDefinition,
4036
4452
  removeLocalVaultFiles,
4037
4453
  createSecretVaultProvider,
4454
+ assertSecretRefVaultProviderCompatible,
4038
4455
  resolveVaultAuth,
4039
4456
  toNamespaceObject,
4040
4457
  readValue,