@clef-sh/core 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -411,6 +411,12 @@ var ManifestParser = class {
411
411
  "namespaces"
412
412
  );
413
413
  }
414
+ if (!ENV_NAME_PATTERN.test(nsObj.name)) {
415
+ throw new ManifestValidationError(
416
+ `Namespace name '${nsObj.name}' is invalid. Names must start with a lowercase letter and contain only lowercase letters, digits, hyphens, and underscores.`,
417
+ "namespaces"
418
+ );
419
+ }
414
420
  if (!nsObj.description || typeof nsObj.description !== "string") {
415
421
  throw new ManifestValidationError(
416
422
  `Namespace '${nsObj.name}' is missing a 'description' string.`,
@@ -511,9 +517,9 @@ var ManifestParser = class {
511
517
  );
512
518
  }
513
519
  const siName = siObj.name;
514
- if (!siObj.description || typeof siObj.description !== "string") {
520
+ if (siObj.description != null && typeof siObj.description !== "string") {
515
521
  throw new ManifestValidationError(
516
- `Service identity '${siName}' is missing a 'description' string.`,
522
+ `Service identity '${siName}' has a non-string 'description'.`,
517
523
  "service_identities"
518
524
  );
519
525
  }
@@ -628,7 +634,7 @@ var ManifestParser = class {
628
634
  }
629
635
  return {
630
636
  name: siName,
631
- description: siObj.description,
637
+ description: siObj.description ?? "",
632
638
  namespaces: siObj.namespaces,
633
639
  environments: parsedEnvs
634
640
  };
@@ -809,7 +815,11 @@ function matchesGlob(filePath, pattern) {
809
815
 
810
816
  // src/scanner/index.ts
811
817
  var ALWAYS_SKIP_EXTENSIONS = [".enc.yaml", ".enc.json"];
812
- var ALWAYS_SKIP_NAMES = [".clef-meta.yaml"];
818
+ var ALWAYS_SKIP_NAMES = [
819
+ ".clef-meta.yaml",
820
+ ".sops.yaml"
821
+ // contains age public keys and KMS ARNs — configuration, not secrets
822
+ ];
813
823
  var ALWAYS_SKIP_DIRS = ["node_modules", ".git"];
814
824
  var MAX_FILE_SIZE = 1024 * 1024;
815
825
  var ScanRunner = class {
@@ -1010,9 +1020,9 @@ var ScanRunner = class {
1010
1020
  };
1011
1021
 
1012
1022
  // src/matrix/manager.ts
1013
- import * as fs5 from "fs";
1023
+ import * as fs6 from "fs";
1014
1024
  import * as path4 from "path";
1015
- import * as YAML3 from "yaml";
1025
+ import * as YAML4 from "yaml";
1016
1026
 
1017
1027
  // src/pending/metadata.ts
1018
1028
  import * as fs4 from "fs";
@@ -1102,6 +1112,20 @@ async function markPendingWithRetry(filePath, keys, setBy, retryDelayMs = 200) {
1102
1112
  }
1103
1113
  }
1104
1114
 
1115
+ // src/sops/keys.ts
1116
+ import * as fs5 from "fs";
1117
+ import * as YAML3 from "yaml";
1118
+ function readSopsKeyNames(filePath) {
1119
+ try {
1120
+ const raw = fs5.readFileSync(filePath, "utf-8");
1121
+ const parsed = YAML3.parse(raw);
1122
+ if (parsed === null || parsed === void 0 || typeof parsed !== "object") return null;
1123
+ return Object.keys(parsed).filter((k) => k !== "sops");
1124
+ } catch {
1125
+ return null;
1126
+ }
1127
+ }
1128
+
1105
1129
  // src/matrix/manager.ts
1106
1130
  var MatrixManager = class {
1107
1131
  /**
@@ -1121,7 +1145,7 @@ var MatrixManager = class {
1121
1145
  namespace: ns.name,
1122
1146
  environment: env.name,
1123
1147
  filePath,
1124
- exists: fs5.existsSync(filePath)
1148
+ exists: fs6.existsSync(filePath)
1125
1149
  });
1126
1150
  }
1127
1151
  }
@@ -1145,8 +1169,8 @@ var MatrixManager = class {
1145
1169
  */
1146
1170
  async scaffoldCell(cell, sopsClient, manifest) {
1147
1171
  const dir = path4.dirname(cell.filePath);
1148
- if (!fs5.existsSync(dir)) {
1149
- fs5.mkdirSync(dir, { recursive: true });
1172
+ if (!fs6.existsSync(dir)) {
1173
+ fs6.mkdirSync(dir, { recursive: true });
1150
1174
  }
1151
1175
  await sopsClient.encrypt(cell.filePath, {}, manifest, cell.environment);
1152
1176
  }
@@ -1215,22 +1239,15 @@ var MatrixManager = class {
1215
1239
  * SOPS stores key names in plaintext — only values are encrypted.
1216
1240
  */
1217
1241
  readKeyNames(filePath) {
1218
- try {
1219
- const raw = fs5.readFileSync(filePath, "utf-8");
1220
- const parsed = YAML3.parse(raw);
1221
- if (!parsed || typeof parsed !== "object") return [];
1222
- return Object.keys(parsed).filter((k) => k !== "sops");
1223
- } catch {
1224
- return [];
1225
- }
1242
+ return readSopsKeyNames(filePath) ?? [];
1226
1243
  }
1227
1244
  /**
1228
1245
  * Read the lastModified timestamp from SOPS metadata without decryption.
1229
1246
  */
1230
1247
  readLastModified(filePath) {
1231
1248
  try {
1232
- const raw = fs5.readFileSync(filePath, "utf-8");
1233
- const parsed = YAML3.parse(raw);
1249
+ const raw = fs6.readFileSync(filePath, "utf-8");
1250
+ const parsed = YAML4.parse(raw);
1234
1251
  const sops = parsed?.sops;
1235
1252
  if (sops?.lastmodified) return new Date(String(sops.lastmodified));
1236
1253
  return null;
@@ -1251,8 +1268,8 @@ var MatrixManager = class {
1251
1268
  };
1252
1269
 
1253
1270
  // src/schema/validator.ts
1254
- import * as fs6 from "fs";
1255
- import * as YAML4 from "yaml";
1271
+ import * as fs7 from "fs";
1272
+ import * as YAML5 from "yaml";
1256
1273
  var SchemaValidator = class {
1257
1274
  /**
1258
1275
  * Read and parse a YAML schema file from disk.
@@ -1264,13 +1281,13 @@ var SchemaValidator = class {
1264
1281
  loadSchema(filePath) {
1265
1282
  let raw;
1266
1283
  try {
1267
- raw = fs6.readFileSync(filePath, "utf-8");
1284
+ raw = fs7.readFileSync(filePath, "utf-8");
1268
1285
  } catch {
1269
1286
  throw new SchemaLoadError(`Could not read schema file at '${filePath}'.`, filePath);
1270
1287
  }
1271
1288
  let parsed;
1272
1289
  try {
1273
- parsed = YAML4.parse(raw);
1290
+ parsed = YAML5.parse(raw);
1274
1291
  } catch {
1275
1292
  throw new SchemaLoadError(`Schema file '${filePath}' contains invalid YAML.`, filePath);
1276
1293
  }
@@ -1571,7 +1588,7 @@ ${details}`
1571
1588
  };
1572
1589
 
1573
1590
  // src/git/integration.ts
1574
- import * as fs7 from "fs";
1591
+ import * as fs8 from "fs";
1575
1592
  import * as path7 from "path";
1576
1593
  var PRE_COMMIT_HOOK = `#!/bin/sh
1577
1594
  # Clef pre-commit hook \u2014 blocks commits of files missing SOPS encryption metadata
@@ -1780,14 +1797,14 @@ var GitIntegration = class {
1780
1797
  });
1781
1798
  const gitConfig = configResult.exitCode === 0 && configResult.stdout.trim().length > 0;
1782
1799
  const attrFilePath = path7.join(repoRoot, ".gitattributes");
1783
- const attrContent = fs7.existsSync(attrFilePath) ? fs7.readFileSync(attrFilePath, "utf-8") : "";
1800
+ const attrContent = fs8.existsSync(attrFilePath) ? fs8.readFileSync(attrFilePath, "utf-8") : "";
1784
1801
  const gitattributes = attrContent.includes("merge=sops");
1785
1802
  return { gitConfig, gitattributes };
1786
1803
  }
1787
1804
  async ensureGitattributes(repoRoot) {
1788
1805
  const attrPath = path7.join(repoRoot, ".gitattributes");
1789
1806
  const mergeRule = "*.enc.yaml merge=sops\n*.enc.json merge=sops";
1790
- const existing = fs7.existsSync(attrPath) ? fs7.readFileSync(attrPath, "utf-8") : "";
1807
+ const existing = fs8.existsSync(attrPath) ? fs8.readFileSync(attrPath, "utf-8") : "";
1791
1808
  if (existing.includes("merge=sops")) {
1792
1809
  return;
1793
1810
  }
@@ -1835,17 +1852,17 @@ ${mergeRule}
1835
1852
  };
1836
1853
 
1837
1854
  // src/sops/client.ts
1838
- import * as fs10 from "fs";
1855
+ import * as fs11 from "fs";
1839
1856
  import * as net from "net";
1840
1857
  import { randomBytes as randomBytes2 } from "crypto";
1841
- import * as YAML5 from "yaml";
1858
+ import * as YAML6 from "yaml";
1842
1859
 
1843
1860
  // src/sops/resolver.ts
1844
- import * as fs9 from "fs";
1861
+ import * as fs10 from "fs";
1845
1862
  import * as path9 from "path";
1846
1863
 
1847
1864
  // src/sops/bundled.ts
1848
- import * as fs8 from "fs";
1865
+ import * as fs9 from "fs";
1849
1866
  import * as path8 from "path";
1850
1867
  function tryBundled() {
1851
1868
  const platform = process.platform;
@@ -1860,7 +1877,7 @@ function tryBundled() {
1860
1877
  const packageMain = __require.resolve(`${packageName}/package.json`);
1861
1878
  const packageDir = path8.dirname(packageMain);
1862
1879
  const binPath = path8.join(packageDir, "bin", binName);
1863
- return fs8.existsSync(binPath) ? binPath : null;
1880
+ return fs9.existsSync(binPath) ? binPath : null;
1864
1881
  } catch {
1865
1882
  return null;
1866
1883
  }
@@ -1884,7 +1901,7 @@ function resolveSopsPath() {
1884
1901
  const envPath = process.env.CLEF_SOPS_PATH?.trim();
1885
1902
  if (envPath) {
1886
1903
  validateSopsPath(envPath);
1887
- if (!fs9.existsSync(envPath)) {
1904
+ if (!fs10.existsSync(envPath)) {
1888
1905
  throw new Error(`CLEF_SOPS_PATH points to '${envPath}' but the file does not exist.`);
1889
1906
  }
1890
1907
  cached = { path: envPath, source: "env" };
@@ -2092,7 +2109,7 @@ var SopsClient = class {
2092
2109
  }
2093
2110
  let parsed;
2094
2111
  try {
2095
- parsed = YAML5.parse(result.stdout) ?? {};
2112
+ parsed = YAML6.parse(result.stdout) ?? {};
2096
2113
  } catch {
2097
2114
  throw new SopsDecryptionError(
2098
2115
  `Decrypted content of '${filePath}' is not valid YAML.`,
@@ -2119,7 +2136,7 @@ var SopsClient = class {
2119
2136
  async encrypt(filePath, values, manifest, environment) {
2120
2137
  await assertSops(this.runner, this.sopsCommand);
2121
2138
  const fmt = formatFromPath(filePath);
2122
- const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML5.stringify(values);
2139
+ const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML6.stringify(values);
2123
2140
  const args = this.buildEncryptArgs(filePath, manifest, environment);
2124
2141
  const env = this.buildSopsEnv();
2125
2142
  let inputArg;
@@ -2163,7 +2180,7 @@ var SopsClient = class {
2163
2180
  );
2164
2181
  }
2165
2182
  try {
2166
- fs10.writeFileSync(filePath, result.stdout);
2183
+ fs11.writeFileSync(filePath, result.stdout);
2167
2184
  } catch {
2168
2185
  throw new SopsEncryptionError(`Failed to write encrypted data to '${filePath}'.`, filePath);
2169
2186
  }
@@ -2176,21 +2193,7 @@ var SopsClient = class {
2176
2193
  * @throws {@link SopsEncryptionError} On failure.
2177
2194
  */
2178
2195
  async reEncrypt(filePath, newKey) {
2179
- await assertSops(this.runner, this.sopsCommand);
2180
- const env = this.buildSopsEnv();
2181
- const result = await this.runner.run(
2182
- this.sopsCommand,
2183
- ["rotate", "-i", "--add-age", newKey, filePath],
2184
- {
2185
- ...env ? { env } : {}
2186
- }
2187
- );
2188
- if (result.exitCode !== 0) {
2189
- throw new SopsEncryptionError(
2190
- `Failed to re-encrypt '${filePath}': ${result.stderr.trim()}`,
2191
- filePath
2192
- );
2193
- }
2196
+ await this.addRecipient(filePath, newKey);
2194
2197
  }
2195
2198
  /**
2196
2199
  * Add an age recipient to an existing SOPS file.
@@ -2293,7 +2296,7 @@ var SopsClient = class {
2293
2296
  if (!this.ageKey && !this.ageKeyFile) return "key-not-found";
2294
2297
  let keyContent;
2295
2298
  try {
2296
- keyContent = this.ageKey ?? fs10.readFileSync(this.ageKeyFile, "utf-8");
2299
+ keyContent = this.ageKey ?? fs11.readFileSync(this.ageKeyFile, "utf-8");
2297
2300
  } catch {
2298
2301
  return "key-not-found";
2299
2302
  }
@@ -2310,7 +2313,7 @@ var SopsClient = class {
2310
2313
  parseMetadataFromFile(filePath) {
2311
2314
  let content;
2312
2315
  try {
2313
- content = fs10.readFileSync(filePath, "utf-8");
2316
+ content = fs11.readFileSync(filePath, "utf-8");
2314
2317
  } catch {
2315
2318
  throw new SopsDecryptionError(
2316
2319
  `Could not read file '${filePath}' to extract SOPS metadata.`,
@@ -2319,7 +2322,7 @@ var SopsClient = class {
2319
2322
  }
2320
2323
  let parsed;
2321
2324
  try {
2322
- parsed = YAML5.parse(content);
2325
+ parsed = YAML6.parse(content);
2323
2326
  } catch {
2324
2327
  throw new SopsDecryptionError(
2325
2328
  `File '${filePath}' is not valid YAML. Cannot extract SOPS metadata.`,
@@ -2751,7 +2754,7 @@ import * as path12 from "path";
2751
2754
 
2752
2755
  // src/import/parsers.ts
2753
2756
  import * as path11 from "path";
2754
- import * as YAML6 from "yaml";
2757
+ import * as YAML7 from "yaml";
2755
2758
  function detectFormat(filePath, content) {
2756
2759
  const base = path11.basename(filePath);
2757
2760
  const ext = path11.extname(filePath).toLowerCase();
@@ -2775,7 +2778,7 @@ function detectFormat(filePath, content) {
2775
2778
  } catch {
2776
2779
  }
2777
2780
  try {
2778
- const parsed = YAML6.parse(content);
2781
+ const parsed = YAML7.parse(content);
2779
2782
  if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
2780
2783
  return "yaml";
2781
2784
  }
@@ -2856,7 +2859,7 @@ function parseJson(content) {
2856
2859
  function parseYaml(content) {
2857
2860
  let parsed;
2858
2861
  try {
2859
- parsed = YAML6.parse(content);
2862
+ parsed = YAML7.parse(content);
2860
2863
  } catch (err) {
2861
2864
  throw new Error(`Invalid YAML: ${err.message}`);
2862
2865
  }
@@ -2890,7 +2893,7 @@ function parseYaml(content) {
2890
2893
  }
2891
2894
  return { pairs, format: "yaml", skipped, warnings };
2892
2895
  }
2893
- function parse7(content, format, filePath) {
2896
+ function parse8(content, format, filePath) {
2894
2897
  const resolved = format === "auto" ? detectFormat(filePath ?? "", content) : format;
2895
2898
  switch (resolved) {
2896
2899
  case "dotenv":
@@ -2923,7 +2926,7 @@ var ImportRunner = class {
2923
2926
  repoRoot,
2924
2927
  manifest.file_pattern.replace("{namespace}", ns).replace("{environment}", env)
2925
2928
  );
2926
- const parsed = parse7(content, options.format ?? "auto", sourcePath ?? "");
2929
+ const parsed = parse8(content, options.format ?? "auto", sourcePath ?? "");
2927
2930
  let candidates = Object.entries(parsed.pairs);
2928
2931
  if (options.prefix) {
2929
2932
  const prefix = options.prefix;
@@ -2977,9 +2980,9 @@ var ImportRunner = class {
2977
2980
  };
2978
2981
 
2979
2982
  // src/recipients/index.ts
2980
- import * as fs11 from "fs";
2983
+ import * as fs12 from "fs";
2981
2984
  import * as path13 from "path";
2982
- import * as YAML7 from "yaml";
2985
+ import * as YAML8 from "yaml";
2983
2986
  function parseRecipientEntry(entry) {
2984
2987
  if (typeof entry === "string") {
2985
2988
  return { key: entry };
@@ -3002,12 +3005,12 @@ function toRecipient(entry) {
3002
3005
  }
3003
3006
  function readManifestYaml(repoRoot) {
3004
3007
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
3005
- const raw = fs11.readFileSync(manifestPath, "utf-8");
3006
- return YAML7.parse(raw);
3008
+ const raw = fs12.readFileSync(manifestPath, "utf-8");
3009
+ return YAML8.parse(raw);
3007
3010
  }
3008
3011
  function writeManifestYaml(repoRoot, doc) {
3009
3012
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
3010
- fs11.writeFileSync(manifestPath, YAML7.stringify(doc), "utf-8");
3013
+ fs12.writeFileSync(manifestPath, YAML8.stringify(doc), "utf-8");
3011
3014
  }
3012
3015
  function getRecipientsArray(doc) {
3013
3016
  const sops = doc.sops;
@@ -3108,7 +3111,7 @@ var RecipientManager = class {
3108
3111
  throw new Error(`Recipient '${keyPreview(normalizedKey)}' is already present.`);
3109
3112
  }
3110
3113
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
3111
- const manifestBackup = fs11.readFileSync(manifestPath, "utf-8");
3114
+ const manifestBackup = fs12.readFileSync(manifestPath, "utf-8");
3112
3115
  const recipients = environment ? ensureEnvironmentRecipientsArray(doc, environment) : ensureRecipientsArray(doc);
3113
3116
  if (label) {
3114
3117
  recipients.push({ key: normalizedKey, label });
@@ -3123,16 +3126,16 @@ var RecipientManager = class {
3123
3126
  const fileBackups = /* @__PURE__ */ new Map();
3124
3127
  for (const cell of cells) {
3125
3128
  try {
3126
- fileBackups.set(cell.filePath, fs11.readFileSync(cell.filePath, "utf-8"));
3129
+ fileBackups.set(cell.filePath, fs12.readFileSync(cell.filePath, "utf-8"));
3127
3130
  await this.encryption.addRecipient(cell.filePath, normalizedKey);
3128
3131
  reEncryptedFiles.push(cell.filePath);
3129
3132
  } catch {
3130
3133
  failedFiles.push(cell.filePath);
3131
- fs11.writeFileSync(manifestPath, manifestBackup, "utf-8");
3134
+ fs12.writeFileSync(manifestPath, manifestBackup, "utf-8");
3132
3135
  for (const reEncryptedFile of reEncryptedFiles) {
3133
3136
  const backup = fileBackups.get(reEncryptedFile);
3134
3137
  if (backup) {
3135
- fs11.writeFileSync(reEncryptedFile, backup, "utf-8");
3138
+ fs12.writeFileSync(reEncryptedFile, backup, "utf-8");
3136
3139
  }
3137
3140
  }
3138
3141
  const restoredDoc = readManifestYaml(repoRoot);
@@ -3186,7 +3189,7 @@ var RecipientManager = class {
3186
3189
  }
3187
3190
  const removedEntry = parsed[matchIndex];
3188
3191
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
3189
- const manifestBackup = fs11.readFileSync(manifestPath, "utf-8");
3192
+ const manifestBackup = fs12.readFileSync(manifestPath, "utf-8");
3190
3193
  const recipients = environment ? ensureEnvironmentRecipientsArray(doc, environment) : ensureRecipientsArray(doc);
3191
3194
  recipients.splice(matchIndex, 1);
3192
3195
  writeManifestYaml(repoRoot, doc);
@@ -3197,16 +3200,16 @@ var RecipientManager = class {
3197
3200
  const fileBackups = /* @__PURE__ */ new Map();
3198
3201
  for (const cell of cells) {
3199
3202
  try {
3200
- fileBackups.set(cell.filePath, fs11.readFileSync(cell.filePath, "utf-8"));
3203
+ fileBackups.set(cell.filePath, fs12.readFileSync(cell.filePath, "utf-8"));
3201
3204
  await this.encryption.removeRecipient(cell.filePath, trimmedKey);
3202
3205
  reEncryptedFiles.push(cell.filePath);
3203
3206
  } catch {
3204
3207
  failedFiles.push(cell.filePath);
3205
- fs11.writeFileSync(manifestPath, manifestBackup, "utf-8");
3208
+ fs12.writeFileSync(manifestPath, manifestBackup, "utf-8");
3206
3209
  for (const reEncryptedFile of reEncryptedFiles) {
3207
3210
  const backup = fileBackups.get(reEncryptedFile);
3208
3211
  if (backup) {
3209
- fs11.writeFileSync(reEncryptedFile, backup, "utf-8");
3212
+ fs12.writeFileSync(reEncryptedFile, backup, "utf-8");
3210
3213
  }
3211
3214
  }
3212
3215
  const restoredDoc = readManifestYaml(repoRoot);
@@ -3240,9 +3243,9 @@ var RecipientManager = class {
3240
3243
  };
3241
3244
 
3242
3245
  // src/recipients/requests.ts
3243
- import * as fs12 from "fs";
3246
+ import * as fs13 from "fs";
3244
3247
  import * as path14 from "path";
3245
- import * as YAML8 from "yaml";
3248
+ import * as YAML9 from "yaml";
3246
3249
  var REQUESTS_FILENAME = ".clef-requests.yaml";
3247
3250
  var HEADER_COMMENT2 = "# Pending recipient access requests. Approve with: clef recipients approve <label>\n";
3248
3251
  function requestsFilePath(repoRoot) {
@@ -3251,9 +3254,9 @@ function requestsFilePath(repoRoot) {
3251
3254
  function loadRequests(repoRoot) {
3252
3255
  const filePath = requestsFilePath(repoRoot);
3253
3256
  try {
3254
- if (!fs12.existsSync(filePath)) return [];
3255
- const content = fs12.readFileSync(filePath, "utf-8");
3256
- const parsed = YAML8.parse(content);
3257
+ if (!fs13.existsSync(filePath)) return [];
3258
+ const content = fs13.readFileSync(filePath, "utf-8");
3259
+ const parsed = YAML9.parse(content);
3257
3260
  if (!parsed || !Array.isArray(parsed.requests)) return [];
3258
3261
  return parsed.requests.map((r) => ({
3259
3262
  key: r.key,
@@ -3269,7 +3272,7 @@ function saveRequests(repoRoot, requests) {
3269
3272
  const filePath = requestsFilePath(repoRoot);
3270
3273
  if (requests.length === 0) {
3271
3274
  try {
3272
- fs12.unlinkSync(filePath);
3275
+ fs13.unlinkSync(filePath);
3273
3276
  } catch {
3274
3277
  }
3275
3278
  return;
@@ -3285,7 +3288,7 @@ function saveRequests(repoRoot, requests) {
3285
3288
  return raw;
3286
3289
  })
3287
3290
  };
3288
- fs12.writeFileSync(filePath, HEADER_COMMENT2 + YAML8.stringify(data), "utf-8");
3291
+ fs13.writeFileSync(filePath, HEADER_COMMENT2 + YAML9.stringify(data), "utf-8");
3289
3292
  }
3290
3293
  function upsertRequest(repoRoot, key, label, environment) {
3291
3294
  const requests = loadRequests(repoRoot);
@@ -3321,9 +3324,7 @@ function findInList(requests, identifier) {
3321
3324
  }
3322
3325
 
3323
3326
  // src/drift/detector.ts
3324
- import * as fs13 from "fs";
3325
3327
  import * as path15 from "path";
3326
- import * as YAML9 from "yaml";
3327
3328
  var DriftDetector = class {
3328
3329
  parser = new ManifestParser();
3329
3330
  matrix = new MatrixManager();
@@ -3351,45 +3352,30 @@ var DriftDetector = class {
3351
3352
  }
3352
3353
  const issues = [];
3353
3354
  let namespacesClean = 0;
3355
+ const remoteEnvSet = new Set(remoteEnvNames);
3356
+ const sharedEnvSet = new Set(localEnvNames.filter((e) => remoteEnvSet.has(e)));
3354
3357
  for (const ns of sharedNamespaces) {
3355
- const keyEnvs = /* @__PURE__ */ new Map();
3356
- const allEnvs = /* @__PURE__ */ new Set();
3357
- const localNsCells = localCells.filter((c) => c.namespace === ns);
3358
- for (const cell of localNsCells) {
3359
- const keys = this.readKeysFromFile(cell.filePath);
3360
- if (keys === null) continue;
3361
- allEnvs.add(cell.environment);
3362
- for (const key of keys) {
3363
- if (!keyEnvs.has(key)) keyEnvs.set(key, /* @__PURE__ */ new Set());
3364
- keyEnvs.get(key).add(cell.environment);
3365
- }
3366
- }
3367
- const remoteNsCells = remoteCells.filter((c) => c.namespace === ns);
3368
- for (const cell of remoteNsCells) {
3369
- const keys = this.readKeysFromFile(cell.filePath);
3370
- if (keys === null) continue;
3371
- allEnvs.add(cell.environment);
3372
- for (const key of keys) {
3373
- if (!keyEnvs.has(key)) keyEnvs.set(key, /* @__PURE__ */ new Set());
3374
- keyEnvs.get(key).add(cell.environment);
3375
- }
3376
- }
3377
- const envList = [...allEnvs];
3358
+ const localKeyEnvs = this.collectKeyEnvs(localCells, ns, sharedEnvSet);
3359
+ const remoteKeyEnvs = this.collectKeyEnvs(remoteCells, ns, sharedEnvSet);
3378
3360
  let nsClean = true;
3379
- for (const [key, envSet] of keyEnvs) {
3380
- const missingFrom = envList.filter((e) => !envSet.has(e));
3381
- if (missingFrom.length > 0) {
3382
- nsClean = false;
3383
- const presentIn = [...envSet].sort();
3384
- issues.push({
3385
- namespace: ns,
3386
- key,
3387
- presentIn,
3388
- missingFrom: missingFrom.sort(),
3389
- message: `Key '${key}' in namespace '${ns}' exists in [${presentIn.join(", ")}] but is missing from [${missingFrom.sort().join(", ")}]`
3390
- });
3361
+ const reportDrift = (sourceMap, targetMap, direction) => {
3362
+ for (const [key, sourceEnvs] of sourceMap) {
3363
+ const targetEnvs = targetMap.get(key);
3364
+ const missingFrom = [...sourceEnvs].filter((e) => !targetEnvs?.has(e)).sort();
3365
+ if (missingFrom.length > 0) {
3366
+ nsClean = false;
3367
+ issues.push({
3368
+ namespace: ns,
3369
+ key,
3370
+ presentIn: [...sourceEnvs].sort(),
3371
+ missingFrom,
3372
+ message: `Key '${key}' in namespace '${ns}' exists in ${direction} [${[...sourceEnvs].sort().join(", ")}] but is missing from [${missingFrom.join(", ")}]`
3373
+ });
3374
+ }
3391
3375
  }
3392
- }
3376
+ };
3377
+ reportDrift(remoteKeyEnvs, localKeyEnvs, "remote");
3378
+ reportDrift(localKeyEnvs, remoteKeyEnvs, "local");
3393
3379
  if (nsClean) namespacesClean++;
3394
3380
  }
3395
3381
  return {
@@ -3400,29 +3386,23 @@ var DriftDetector = class {
3400
3386
  remoteEnvironments: remoteEnvNames
3401
3387
  };
3402
3388
  }
3403
- /**
3404
- * Read top-level key names from an encrypted SOPS YAML file,
3405
- * filtering out the `sops` metadata key.
3406
- *
3407
- * @returns Array of key names, or `null` if the file cannot be read.
3408
- */
3409
- readKeysFromFile(filePath) {
3410
- try {
3411
- if (!fs13.existsSync(filePath)) return null;
3412
- const raw = fs13.readFileSync(filePath, "utf-8");
3413
- const parsed = YAML9.parse(raw);
3414
- if (parsed === null || parsed === void 0 || typeof parsed !== "object") return null;
3415
- return Object.keys(parsed).filter((k) => k !== "sops");
3416
- } catch {
3417
- return null;
3389
+ collectKeyEnvs(cells, ns, sharedEnvSet) {
3390
+ const keyEnvs = /* @__PURE__ */ new Map();
3391
+ for (const cell of cells) {
3392
+ if (cell.namespace !== ns || !sharedEnvSet.has(cell.environment)) continue;
3393
+ const keys = readSopsKeyNames(cell.filePath);
3394
+ if (keys === null) continue;
3395
+ for (const key of keys) {
3396
+ if (!keyEnvs.has(key)) keyEnvs.set(key, /* @__PURE__ */ new Set());
3397
+ keyEnvs.get(key).add(cell.environment);
3398
+ }
3418
3399
  }
3400
+ return keyEnvs;
3419
3401
  }
3420
3402
  };
3421
3403
 
3422
3404
  // src/report/generator.ts
3423
- import * as fs14 from "fs";
3424
3405
  import * as path16 from "path";
3425
- import * as YAML10 from "yaml";
3426
3406
 
3427
3407
  // src/report/sanitizer.ts
3428
3408
  var ReportSanitizer = class {
@@ -3727,15 +3707,7 @@ var ReportGenerator = class {
3727
3707
  };
3728
3708
  }
3729
3709
  readKeyCount(filePath) {
3730
- try {
3731
- if (!fs14.existsSync(filePath)) return 0;
3732
- const raw = fs14.readFileSync(filePath, "utf-8");
3733
- const parsed = YAML10.parse(raw);
3734
- if (parsed === null || parsed === void 0 || typeof parsed !== "object") return 0;
3735
- return Object.keys(parsed).filter((k) => k !== "sops").length;
3736
- } catch {
3737
- return 0;
3738
- }
3710
+ return readSopsKeyNames(filePath)?.length ?? 0;
3739
3711
  }
3740
3712
  async buildPolicy(manifest, repoRoot) {
3741
3713
  try {
@@ -4064,10 +4036,10 @@ var SopsMergeDriver = class {
4064
4036
  };
4065
4037
 
4066
4038
  // src/service-identity/manager.ts
4067
- import * as fs15 from "fs";
4039
+ import * as fs14 from "fs";
4068
4040
  import * as os from "os";
4069
4041
  import * as path17 from "path";
4070
- import * as YAML11 from "yaml";
4042
+ import * as YAML10 from "yaml";
4071
4043
  var PartialRotationError = class extends Error {
4072
4044
  constructor(message, rotatedKeys) {
4073
4045
  super(message);
@@ -4119,8 +4091,8 @@ var ServiceIdentityManager = class {
4119
4091
  };
4120
4092
  await this.registerRecipients(definition, manifest, repoRoot);
4121
4093
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4122
- const raw = fs15.readFileSync(manifestPath, "utf-8");
4123
- const doc = YAML11.parse(raw);
4094
+ const raw = fs14.readFileSync(manifestPath, "utf-8");
4095
+ const doc = YAML10.parse(raw);
4124
4096
  if (!Array.isArray(doc.service_identities)) {
4125
4097
  doc.service_identities = [];
4126
4098
  }
@@ -4132,11 +4104,11 @@ var ServiceIdentityManager = class {
4132
4104
  });
4133
4105
  const tmpCreate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4134
4106
  try {
4135
- fs15.writeFileSync(tmpCreate, YAML11.stringify(doc), "utf-8");
4136
- fs15.renameSync(tmpCreate, manifestPath);
4107
+ fs14.writeFileSync(tmpCreate, YAML10.stringify(doc), "utf-8");
4108
+ fs14.renameSync(tmpCreate, manifestPath);
4137
4109
  } finally {
4138
4110
  try {
4139
- fs15.unlinkSync(tmpCreate);
4111
+ fs14.unlinkSync(tmpCreate);
4140
4112
  } catch {
4141
4113
  }
4142
4114
  }
@@ -4175,8 +4147,8 @@ var ServiceIdentityManager = class {
4175
4147
  }
4176
4148
  }
4177
4149
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4178
- const raw = fs15.readFileSync(manifestPath, "utf-8");
4179
- const doc = YAML11.parse(raw);
4150
+ const raw = fs14.readFileSync(manifestPath, "utf-8");
4151
+ const doc = YAML10.parse(raw);
4180
4152
  const identities = doc.service_identities;
4181
4153
  if (Array.isArray(identities)) {
4182
4154
  doc.service_identities = identities.filter(
@@ -4185,11 +4157,11 @@ var ServiceIdentityManager = class {
4185
4157
  }
4186
4158
  const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4187
4159
  try {
4188
- fs15.writeFileSync(tmp, YAML11.stringify(doc), "utf-8");
4189
- fs15.renameSync(tmp, manifestPath);
4160
+ fs14.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
4161
+ fs14.renameSync(tmp, manifestPath);
4190
4162
  } finally {
4191
4163
  try {
4192
- fs15.unlinkSync(tmp);
4164
+ fs14.unlinkSync(tmp);
4193
4165
  } catch {
4194
4166
  }
4195
4167
  }
@@ -4205,8 +4177,8 @@ var ServiceIdentityManager = class {
4205
4177
  throw new Error(`Service identity '${name}' not found.`);
4206
4178
  }
4207
4179
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4208
- const raw = fs15.readFileSync(manifestPath, "utf-8");
4209
- const doc = YAML11.parse(raw);
4180
+ const raw = fs14.readFileSync(manifestPath, "utf-8");
4181
+ const doc = YAML10.parse(raw);
4210
4182
  const identities = doc.service_identities;
4211
4183
  const siDoc = identities.find((si) => si.name === name);
4212
4184
  const envs = siDoc.environments;
@@ -4233,11 +4205,11 @@ var ServiceIdentityManager = class {
4233
4205
  }
4234
4206
  const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4235
4207
  try {
4236
- fs15.writeFileSync(tmp, YAML11.stringify(doc), "utf-8");
4237
- fs15.renameSync(tmp, manifestPath);
4208
+ fs14.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
4209
+ fs14.renameSync(tmp, manifestPath);
4238
4210
  } finally {
4239
4211
  try {
4240
- fs15.unlinkSync(tmp);
4212
+ fs14.unlinkSync(tmp);
4241
4213
  } catch {
4242
4214
  }
4243
4215
  }
@@ -4274,8 +4246,8 @@ var ServiceIdentityManager = class {
4274
4246
  throw new Error(`Service identity '${name}' not found.`);
4275
4247
  }
4276
4248
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4277
- const raw = fs15.readFileSync(manifestPath, "utf-8");
4278
- const doc = YAML11.parse(raw);
4249
+ const raw = fs14.readFileSync(manifestPath, "utf-8");
4250
+ const doc = YAML10.parse(raw);
4279
4251
  const identities = doc.service_identities;
4280
4252
  const siDoc = identities.find((si) => si.name === name);
4281
4253
  const envs = siDoc.environments;
@@ -4330,11 +4302,11 @@ var ServiceIdentityManager = class {
4330
4302
  }
4331
4303
  const tmpRotate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4332
4304
  try {
4333
- fs15.writeFileSync(tmpRotate, YAML11.stringify(doc), "utf-8");
4334
- fs15.renameSync(tmpRotate, manifestPath);
4305
+ fs14.writeFileSync(tmpRotate, YAML10.stringify(doc), "utf-8");
4306
+ fs14.renameSync(tmpRotate, manifestPath);
4335
4307
  } finally {
4336
4308
  try {
4337
- fs15.unlinkSync(tmpRotate);
4309
+ fs14.unlinkSync(tmpRotate);
4338
4310
  } catch {
4339
4311
  }
4340
4312
  }
@@ -4455,7 +4427,7 @@ async function resolveIdentitySecrets(identityName, environment, manifest, repoR
4455
4427
  }
4456
4428
 
4457
4429
  // src/artifact/packer.ts
4458
- import * as fs16 from "fs";
4430
+ import * as fs15 from "fs";
4459
4431
  import * as path18 from "path";
4460
4432
  import * as crypto3 from "crypto";
4461
4433
 
@@ -4463,7 +4435,7 @@ import * as crypto3 from "crypto";
4463
4435
  import * as crypto2 from "crypto";
4464
4436
  function buildSigningPayload(artifact) {
4465
4437
  const fields = [
4466
- "clef-sig-v1",
4438
+ "clef-sig-v2",
4467
4439
  String(artifact.version),
4468
4440
  artifact.identity,
4469
4441
  artifact.environment,
@@ -4475,7 +4447,9 @@ function buildSigningPayload(artifact) {
4475
4447
  artifact.envelope?.provider ?? "",
4476
4448
  artifact.envelope?.keyId ?? "",
4477
4449
  artifact.envelope?.wrappedKey ?? "",
4478
- artifact.envelope?.algorithm ?? ""
4450
+ artifact.envelope?.algorithm ?? "",
4451
+ artifact.envelope?.iv ?? "",
4452
+ artifact.envelope?.authTag ?? ""
4479
4453
  ];
4480
4454
  return Buffer.from(fields.join("\n"), "utf-8");
4481
4455
  }
@@ -4569,40 +4543,41 @@ var ArtifactPacker = class {
4569
4543
  if (!this.kms) {
4570
4544
  throw new Error("KMS provider required for envelope encryption but none was provided.");
4571
4545
  }
4572
- const { generateIdentity, identityToRecipient, Encrypter } = await import(
4573
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic ESM import of CJS-incompatible package
4574
- "age-encryption"
4575
- );
4576
- const ephemeralPrivateKey = await generateIdentity();
4577
- const ephemeralPublicKey = await identityToRecipient(ephemeralPrivateKey);
4546
+ const dek = crypto3.randomBytes(32);
4547
+ const iv = crypto3.randomBytes(12);
4578
4548
  try {
4579
- const e = new Encrypter();
4580
- e.addRecipient(ephemeralPublicKey);
4581
- const encrypted = await e.encrypt(plaintext);
4582
- ciphertext = Buffer.from(encrypted).toString("base64");
4583
- } catch {
4584
- throw new Error("Failed to age-encrypt artifact with ephemeral key.");
4549
+ const cipher = crypto3.createCipheriv("aes-256-gcm", dek, iv);
4550
+ const ciphertextBuf = Buffer.concat([
4551
+ cipher.update(Buffer.from(plaintext, "utf-8")),
4552
+ cipher.final()
4553
+ ]);
4554
+ const authTag = cipher.getAuthTag();
4555
+ ciphertext = ciphertextBuf.toString("base64");
4556
+ const kmsConfig = resolved.envConfig.kms;
4557
+ const wrapped = await this.kms.wrap(kmsConfig.keyId, dek);
4558
+ const revision = `${Date.now()}-${crypto3.randomBytes(4).toString("hex")}`;
4559
+ const ciphertextHash = crypto3.createHash("sha256").update(ciphertext).digest("hex");
4560
+ artifact = {
4561
+ version: 1,
4562
+ identity: config.identity,
4563
+ environment: config.environment,
4564
+ packedAt: (/* @__PURE__ */ new Date()).toISOString(),
4565
+ revision,
4566
+ ciphertextHash,
4567
+ ciphertext,
4568
+ keys: Object.keys(resolved.values),
4569
+ envelope: {
4570
+ provider: kmsConfig.provider,
4571
+ keyId: kmsConfig.keyId,
4572
+ wrappedKey: wrapped.wrappedKey.toString("base64"),
4573
+ algorithm: wrapped.algorithm,
4574
+ iv: iv.toString("base64"),
4575
+ authTag: authTag.toString("base64")
4576
+ }
4577
+ };
4578
+ } finally {
4579
+ dek.fill(0);
4585
4580
  }
4586
- const kmsConfig = resolved.envConfig.kms;
4587
- const wrapped = await this.kms.wrap(kmsConfig.keyId, Buffer.from(ephemeralPrivateKey));
4588
- const revision = `${Date.now()}-${crypto3.randomBytes(4).toString("hex")}`;
4589
- const ciphertextHash = crypto3.createHash("sha256").update(ciphertext).digest("hex");
4590
- artifact = {
4591
- version: 1,
4592
- identity: config.identity,
4593
- environment: config.environment,
4594
- packedAt: (/* @__PURE__ */ new Date()).toISOString(),
4595
- revision,
4596
- ciphertextHash,
4597
- ciphertext,
4598
- keys: Object.keys(resolved.values),
4599
- envelope: {
4600
- provider: kmsConfig.provider,
4601
- keyId: kmsConfig.keyId,
4602
- wrappedKey: wrapped.wrappedKey.toString("base64"),
4603
- algorithm: wrapped.algorithm
4604
- }
4605
- };
4606
4581
  } else {
4607
4582
  try {
4608
4583
  const { Encrypter } = await import("age-encryption");
@@ -4610,8 +4585,10 @@ var ArtifactPacker = class {
4610
4585
  e.addRecipient(resolved.recipient);
4611
4586
  const encrypted = await e.encrypt(plaintext);
4612
4587
  ciphertext = Buffer.from(encrypted).toString("base64");
4613
- } catch {
4614
- throw new Error("Failed to age-encrypt artifact. Check recipient key.");
4588
+ } catch (err) {
4589
+ throw new Error(
4590
+ `Failed to age-encrypt artifact: ${err instanceof Error ? err.message : String(err)}`
4591
+ );
4615
4592
  }
4616
4593
  const revision = `${Date.now()}-${crypto3.randomBytes(4).toString("hex")}`;
4617
4594
  const ciphertextHash = crypto3.createHash("sha256").update(ciphertext).digest("hex");
@@ -4627,8 +4604,8 @@ var ArtifactPacker = class {
4627
4604
  };
4628
4605
  }
4629
4606
  const outputDir = path18.dirname(config.outputPath);
4630
- if (!fs16.existsSync(outputDir)) {
4631
- fs16.mkdirSync(outputDir, { recursive: true });
4607
+ if (!fs15.existsSync(outputDir)) {
4608
+ fs15.mkdirSync(outputDir, { recursive: true });
4632
4609
  }
4633
4610
  if (config.ttl && config.ttl > 0) {
4634
4611
  artifact.expiresAt = new Date(Date.now() + config.ttl * 1e3).toISOString();
@@ -4647,8 +4624,8 @@ var ArtifactPacker = class {
4647
4624
  }
4648
4625
  const json = JSON.stringify(artifact, null, 2);
4649
4626
  const tmpOutput = `${config.outputPath}.tmp.${process.pid}`;
4650
- fs16.writeFileSync(tmpOutput, json, "utf-8");
4651
- fs16.renameSync(tmpOutput, config.outputPath);
4627
+ fs15.writeFileSync(tmpOutput, json, "utf-8");
4628
+ fs15.renameSync(tmpOutput, config.outputPath);
4652
4629
  return {
4653
4630
  outputPath: config.outputPath,
4654
4631
  namespaceCount: resolved.identity.namespaces.length,
@@ -4658,6 +4635,9 @@ var ArtifactPacker = class {
4658
4635
  };
4659
4636
  }
4660
4637
  };
4638
+
4639
+ // src/kms/types.ts
4640
+ var VALID_KMS_PROVIDERS = ["aws", "gcp", "azure"];
4661
4641
  export {
4662
4642
  ArtifactPacker,
4663
4643
  BulkOps,
@@ -4695,6 +4675,7 @@ export {
4695
4675
  SopsMergeDriver,
4696
4676
  SopsMissingError,
4697
4677
  SopsVersionError,
4678
+ VALID_KMS_PROVIDERS,
4698
4679
  assertSops,
4699
4680
  buildSigningPayload,
4700
4681
  checkAll,
@@ -4721,7 +4702,7 @@ export {
4721
4702
  markResolved,
4722
4703
  matchPatterns,
4723
4704
  metadataPath,
4724
- parse7 as parse,
4705
+ parse8 as parse,
4725
4706
  parseDotenv,
4726
4707
  parseIgnoreContent,
4727
4708
  parseJson,