@clef-sh/core 0.1.8 → 0.1.9-beta.57

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.js CHANGED
@@ -6949,6 +6949,7 @@ __export(src_exports, {
6949
6949
  SopsMergeDriver: () => SopsMergeDriver,
6950
6950
  SopsMissingError: () => SopsMissingError,
6951
6951
  SopsVersionError: () => SopsVersionError,
6952
+ VALID_KMS_PROVIDERS: () => VALID_KMS_PROVIDERS,
6952
6953
  assertSops: () => assertSops,
6953
6954
  buildSigningPayload: () => buildSigningPayload,
6954
6955
  checkAll: () => checkAll,
@@ -6975,7 +6976,7 @@ __export(src_exports, {
6975
6976
  markResolved: () => markResolved,
6976
6977
  matchPatterns: () => matchPatterns,
6977
6978
  metadataPath: () => metadataPath,
6978
- parse: () => parse7,
6979
+ parse: () => parse8,
6979
6980
  parseDotenv: () => parseDotenv,
6980
6981
  parseIgnoreContent: () => parseIgnoreContent,
6981
6982
  parseJson: () => parseJson,
@@ -7407,6 +7408,12 @@ var ManifestParser = class {
7407
7408
  "namespaces"
7408
7409
  );
7409
7410
  }
7411
+ if (!ENV_NAME_PATTERN.test(nsObj.name)) {
7412
+ throw new ManifestValidationError(
7413
+ `Namespace name '${nsObj.name}' is invalid. Names must start with a lowercase letter and contain only lowercase letters, digits, hyphens, and underscores.`,
7414
+ "namespaces"
7415
+ );
7416
+ }
7410
7417
  if (!nsObj.description || typeof nsObj.description !== "string") {
7411
7418
  throw new ManifestValidationError(
7412
7419
  `Namespace '${nsObj.name}' is missing a 'description' string.`,
@@ -7507,9 +7514,9 @@ var ManifestParser = class {
7507
7514
  );
7508
7515
  }
7509
7516
  const siName = siObj.name;
7510
- if (!siObj.description || typeof siObj.description !== "string") {
7517
+ if (siObj.description != null && typeof siObj.description !== "string") {
7511
7518
  throw new ManifestValidationError(
7512
- `Service identity '${siName}' is missing a 'description' string.`,
7519
+ `Service identity '${siName}' has a non-string 'description'.`,
7513
7520
  "service_identities"
7514
7521
  );
7515
7522
  }
@@ -7624,7 +7631,7 @@ var ManifestParser = class {
7624
7631
  }
7625
7632
  return {
7626
7633
  name: siName,
7627
- description: siObj.description,
7634
+ description: siObj.description ?? "",
7628
7635
  namespaces: siObj.namespaces,
7629
7636
  environments: parsedEnvs
7630
7637
  };
@@ -7805,7 +7812,11 @@ function matchesGlob(filePath, pattern) {
7805
7812
 
7806
7813
  // src/scanner/index.ts
7807
7814
  var ALWAYS_SKIP_EXTENSIONS = [".enc.yaml", ".enc.json"];
7808
- var ALWAYS_SKIP_NAMES = [".clef-meta.yaml"];
7815
+ var ALWAYS_SKIP_NAMES = [
7816
+ ".clef-meta.yaml",
7817
+ ".sops.yaml"
7818
+ // contains age public keys and KMS ARNs — configuration, not secrets
7819
+ ];
7809
7820
  var ALWAYS_SKIP_DIRS = ["node_modules", ".git"];
7810
7821
  var MAX_FILE_SIZE = 1024 * 1024;
7811
7822
  var ScanRunner = class {
@@ -8006,9 +8017,9 @@ var ScanRunner = class {
8006
8017
  };
8007
8018
 
8008
8019
  // src/matrix/manager.ts
8009
- var fs5 = __toESM(require("fs"));
8020
+ var fs6 = __toESM(require("fs"));
8010
8021
  var path4 = __toESM(require("path"));
8011
- var YAML3 = __toESM(require("yaml"));
8022
+ var YAML4 = __toESM(require("yaml"));
8012
8023
 
8013
8024
  // src/pending/metadata.ts
8014
8025
  var fs4 = __toESM(require("fs"));
@@ -8098,6 +8109,20 @@ async function markPendingWithRetry(filePath, keys, setBy, retryDelayMs = 200) {
8098
8109
  }
8099
8110
  }
8100
8111
 
8112
+ // src/sops/keys.ts
8113
+ var fs5 = __toESM(require("fs"));
8114
+ var YAML3 = __toESM(require("yaml"));
8115
+ function readSopsKeyNames(filePath) {
8116
+ try {
8117
+ const raw = fs5.readFileSync(filePath, "utf-8");
8118
+ const parsed = YAML3.parse(raw);
8119
+ if (parsed === null || parsed === void 0 || typeof parsed !== "object") return null;
8120
+ return Object.keys(parsed).filter((k) => k !== "sops");
8121
+ } catch {
8122
+ return null;
8123
+ }
8124
+ }
8125
+
8101
8126
  // src/matrix/manager.ts
8102
8127
  var MatrixManager = class {
8103
8128
  /**
@@ -8117,7 +8142,7 @@ var MatrixManager = class {
8117
8142
  namespace: ns.name,
8118
8143
  environment: env.name,
8119
8144
  filePath,
8120
- exists: fs5.existsSync(filePath)
8145
+ exists: fs6.existsSync(filePath)
8121
8146
  });
8122
8147
  }
8123
8148
  }
@@ -8141,8 +8166,8 @@ var MatrixManager = class {
8141
8166
  */
8142
8167
  async scaffoldCell(cell, sopsClient, manifest) {
8143
8168
  const dir = path4.dirname(cell.filePath);
8144
- if (!fs5.existsSync(dir)) {
8145
- fs5.mkdirSync(dir, { recursive: true });
8169
+ if (!fs6.existsSync(dir)) {
8170
+ fs6.mkdirSync(dir, { recursive: true });
8146
8171
  }
8147
8172
  await sopsClient.encrypt(cell.filePath, {}, manifest, cell.environment);
8148
8173
  }
@@ -8211,22 +8236,15 @@ var MatrixManager = class {
8211
8236
  * SOPS stores key names in plaintext — only values are encrypted.
8212
8237
  */
8213
8238
  readKeyNames(filePath) {
8214
- try {
8215
- const raw = fs5.readFileSync(filePath, "utf-8");
8216
- const parsed = YAML3.parse(raw);
8217
- if (!parsed || typeof parsed !== "object") return [];
8218
- return Object.keys(parsed).filter((k) => k !== "sops");
8219
- } catch {
8220
- return [];
8221
- }
8239
+ return readSopsKeyNames(filePath) ?? [];
8222
8240
  }
8223
8241
  /**
8224
8242
  * Read the lastModified timestamp from SOPS metadata without decryption.
8225
8243
  */
8226
8244
  readLastModified(filePath) {
8227
8245
  try {
8228
- const raw = fs5.readFileSync(filePath, "utf-8");
8229
- const parsed = YAML3.parse(raw);
8246
+ const raw = fs6.readFileSync(filePath, "utf-8");
8247
+ const parsed = YAML4.parse(raw);
8230
8248
  const sops = parsed?.sops;
8231
8249
  if (sops?.lastmodified) return new Date(String(sops.lastmodified));
8232
8250
  return null;
@@ -8247,8 +8265,8 @@ var MatrixManager = class {
8247
8265
  };
8248
8266
 
8249
8267
  // src/schema/validator.ts
8250
- var fs6 = __toESM(require("fs"));
8251
- var YAML4 = __toESM(require("yaml"));
8268
+ var fs7 = __toESM(require("fs"));
8269
+ var YAML5 = __toESM(require("yaml"));
8252
8270
  var SchemaValidator = class {
8253
8271
  /**
8254
8272
  * Read and parse a YAML schema file from disk.
@@ -8260,13 +8278,13 @@ var SchemaValidator = class {
8260
8278
  loadSchema(filePath) {
8261
8279
  let raw;
8262
8280
  try {
8263
- raw = fs6.readFileSync(filePath, "utf-8");
8281
+ raw = fs7.readFileSync(filePath, "utf-8");
8264
8282
  } catch {
8265
8283
  throw new SchemaLoadError(`Could not read schema file at '${filePath}'.`, filePath);
8266
8284
  }
8267
8285
  let parsed;
8268
8286
  try {
8269
- parsed = YAML4.parse(raw);
8287
+ parsed = YAML5.parse(raw);
8270
8288
  } catch {
8271
8289
  throw new SchemaLoadError(`Schema file '${filePath}' contains invalid YAML.`, filePath);
8272
8290
  }
@@ -8567,7 +8585,7 @@ ${details}`
8567
8585
  };
8568
8586
 
8569
8587
  // src/git/integration.ts
8570
- var fs7 = __toESM(require("fs"));
8588
+ var fs8 = __toESM(require("fs"));
8571
8589
  var path7 = __toESM(require("path"));
8572
8590
  var PRE_COMMIT_HOOK = `#!/bin/sh
8573
8591
  # Clef pre-commit hook \u2014 blocks commits of files missing SOPS encryption metadata
@@ -8776,14 +8794,14 @@ var GitIntegration = class {
8776
8794
  });
8777
8795
  const gitConfig = configResult.exitCode === 0 && configResult.stdout.trim().length > 0;
8778
8796
  const attrFilePath = path7.join(repoRoot, ".gitattributes");
8779
- const attrContent = fs7.existsSync(attrFilePath) ? fs7.readFileSync(attrFilePath, "utf-8") : "";
8797
+ const attrContent = fs8.existsSync(attrFilePath) ? fs8.readFileSync(attrFilePath, "utf-8") : "";
8780
8798
  const gitattributes = attrContent.includes("merge=sops");
8781
8799
  return { gitConfig, gitattributes };
8782
8800
  }
8783
8801
  async ensureGitattributes(repoRoot) {
8784
8802
  const attrPath = path7.join(repoRoot, ".gitattributes");
8785
8803
  const mergeRule = "*.enc.yaml merge=sops\n*.enc.json merge=sops";
8786
- const existing = fs7.existsSync(attrPath) ? fs7.readFileSync(attrPath, "utf-8") : "";
8804
+ const existing = fs8.existsSync(attrPath) ? fs8.readFileSync(attrPath, "utf-8") : "";
8787
8805
  if (existing.includes("merge=sops")) {
8788
8806
  return;
8789
8807
  }
@@ -8831,17 +8849,17 @@ ${mergeRule}
8831
8849
  };
8832
8850
 
8833
8851
  // src/sops/client.ts
8834
- var fs10 = __toESM(require("fs"));
8852
+ var fs11 = __toESM(require("fs"));
8835
8853
  var net = __toESM(require("net"));
8836
8854
  var import_crypto = require("crypto");
8837
- var YAML5 = __toESM(require("yaml"));
8855
+ var YAML6 = __toESM(require("yaml"));
8838
8856
 
8839
8857
  // src/sops/resolver.ts
8840
- var fs9 = __toESM(require("fs"));
8858
+ var fs10 = __toESM(require("fs"));
8841
8859
  var path9 = __toESM(require("path"));
8842
8860
 
8843
8861
  // src/sops/bundled.ts
8844
- var fs8 = __toESM(require("fs"));
8862
+ var fs9 = __toESM(require("fs"));
8845
8863
  var path8 = __toESM(require("path"));
8846
8864
  function tryBundled() {
8847
8865
  const platform = process.platform;
@@ -8856,7 +8874,7 @@ function tryBundled() {
8856
8874
  const packageMain = require.resolve(`${packageName}/package.json`);
8857
8875
  const packageDir = path8.dirname(packageMain);
8858
8876
  const binPath = path8.join(packageDir, "bin", binName);
8859
- return fs8.existsSync(binPath) ? binPath : null;
8877
+ return fs9.existsSync(binPath) ? binPath : null;
8860
8878
  } catch {
8861
8879
  return null;
8862
8880
  }
@@ -8880,7 +8898,7 @@ function resolveSopsPath() {
8880
8898
  const envPath = process.env.CLEF_SOPS_PATH?.trim();
8881
8899
  if (envPath) {
8882
8900
  validateSopsPath(envPath);
8883
- if (!fs9.existsSync(envPath)) {
8901
+ if (!fs10.existsSync(envPath)) {
8884
8902
  throw new Error(`CLEF_SOPS_PATH points to '${envPath}' but the file does not exist.`);
8885
8903
  }
8886
8904
  cached = { path: envPath, source: "env" };
@@ -9088,7 +9106,7 @@ var SopsClient = class {
9088
9106
  }
9089
9107
  let parsed;
9090
9108
  try {
9091
- parsed = YAML5.parse(result.stdout) ?? {};
9109
+ parsed = YAML6.parse(result.stdout) ?? {};
9092
9110
  } catch {
9093
9111
  throw new SopsDecryptionError(
9094
9112
  `Decrypted content of '${filePath}' is not valid YAML.`,
@@ -9115,7 +9133,7 @@ var SopsClient = class {
9115
9133
  async encrypt(filePath, values, manifest, environment) {
9116
9134
  await assertSops(this.runner, this.sopsCommand);
9117
9135
  const fmt = formatFromPath(filePath);
9118
- const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML5.stringify(values);
9136
+ const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML6.stringify(values);
9119
9137
  const args = this.buildEncryptArgs(filePath, manifest, environment);
9120
9138
  const env = this.buildSopsEnv();
9121
9139
  let inputArg;
@@ -9159,7 +9177,7 @@ var SopsClient = class {
9159
9177
  );
9160
9178
  }
9161
9179
  try {
9162
- fs10.writeFileSync(filePath, result.stdout);
9180
+ fs11.writeFileSync(filePath, result.stdout);
9163
9181
  } catch {
9164
9182
  throw new SopsEncryptionError(`Failed to write encrypted data to '${filePath}'.`, filePath);
9165
9183
  }
@@ -9172,21 +9190,7 @@ var SopsClient = class {
9172
9190
  * @throws {@link SopsEncryptionError} On failure.
9173
9191
  */
9174
9192
  async reEncrypt(filePath, newKey) {
9175
- await assertSops(this.runner, this.sopsCommand);
9176
- const env = this.buildSopsEnv();
9177
- const result = await this.runner.run(
9178
- this.sopsCommand,
9179
- ["rotate", "-i", "--add-age", newKey, filePath],
9180
- {
9181
- ...env ? { env } : {}
9182
- }
9183
- );
9184
- if (result.exitCode !== 0) {
9185
- throw new SopsEncryptionError(
9186
- `Failed to re-encrypt '${filePath}': ${result.stderr.trim()}`,
9187
- filePath
9188
- );
9189
- }
9193
+ await this.addRecipient(filePath, newKey);
9190
9194
  }
9191
9195
  /**
9192
9196
  * Add an age recipient to an existing SOPS file.
@@ -9289,7 +9293,7 @@ var SopsClient = class {
9289
9293
  if (!this.ageKey && !this.ageKeyFile) return "key-not-found";
9290
9294
  let keyContent;
9291
9295
  try {
9292
- keyContent = this.ageKey ?? fs10.readFileSync(this.ageKeyFile, "utf-8");
9296
+ keyContent = this.ageKey ?? fs11.readFileSync(this.ageKeyFile, "utf-8");
9293
9297
  } catch {
9294
9298
  return "key-not-found";
9295
9299
  }
@@ -9306,7 +9310,7 @@ var SopsClient = class {
9306
9310
  parseMetadataFromFile(filePath) {
9307
9311
  let content;
9308
9312
  try {
9309
- content = fs10.readFileSync(filePath, "utf-8");
9313
+ content = fs11.readFileSync(filePath, "utf-8");
9310
9314
  } catch {
9311
9315
  throw new SopsDecryptionError(
9312
9316
  `Could not read file '${filePath}' to extract SOPS metadata.`,
@@ -9315,7 +9319,7 @@ var SopsClient = class {
9315
9319
  }
9316
9320
  let parsed;
9317
9321
  try {
9318
- parsed = YAML5.parse(content);
9322
+ parsed = YAML6.parse(content);
9319
9323
  } catch {
9320
9324
  throw new SopsDecryptionError(
9321
9325
  `File '${filePath}' is not valid YAML. Cannot extract SOPS metadata.`,
@@ -9747,7 +9751,7 @@ var path12 = __toESM(require("path"));
9747
9751
 
9748
9752
  // src/import/parsers.ts
9749
9753
  var path11 = __toESM(require("path"));
9750
- var YAML6 = __toESM(require("yaml"));
9754
+ var YAML7 = __toESM(require("yaml"));
9751
9755
  function detectFormat(filePath, content) {
9752
9756
  const base = path11.basename(filePath);
9753
9757
  const ext = path11.extname(filePath).toLowerCase();
@@ -9771,7 +9775,7 @@ function detectFormat(filePath, content) {
9771
9775
  } catch {
9772
9776
  }
9773
9777
  try {
9774
- const parsed = YAML6.parse(content);
9778
+ const parsed = YAML7.parse(content);
9775
9779
  if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
9776
9780
  return "yaml";
9777
9781
  }
@@ -9852,7 +9856,7 @@ function parseJson(content) {
9852
9856
  function parseYaml(content) {
9853
9857
  let parsed;
9854
9858
  try {
9855
- parsed = YAML6.parse(content);
9859
+ parsed = YAML7.parse(content);
9856
9860
  } catch (err) {
9857
9861
  throw new Error(`Invalid YAML: ${err.message}`);
9858
9862
  }
@@ -9886,7 +9890,7 @@ function parseYaml(content) {
9886
9890
  }
9887
9891
  return { pairs, format: "yaml", skipped, warnings };
9888
9892
  }
9889
- function parse7(content, format, filePath) {
9893
+ function parse8(content, format, filePath) {
9890
9894
  const resolved = format === "auto" ? detectFormat(filePath ?? "", content) : format;
9891
9895
  switch (resolved) {
9892
9896
  case "dotenv":
@@ -9919,7 +9923,7 @@ var ImportRunner = class {
9919
9923
  repoRoot,
9920
9924
  manifest.file_pattern.replace("{namespace}", ns).replace("{environment}", env)
9921
9925
  );
9922
- const parsed = parse7(content, options.format ?? "auto", sourcePath ?? "");
9926
+ const parsed = parse8(content, options.format ?? "auto", sourcePath ?? "");
9923
9927
  let candidates = Object.entries(parsed.pairs);
9924
9928
  if (options.prefix) {
9925
9929
  const prefix = options.prefix;
@@ -9973,9 +9977,9 @@ var ImportRunner = class {
9973
9977
  };
9974
9978
 
9975
9979
  // src/recipients/index.ts
9976
- var fs11 = __toESM(require("fs"));
9980
+ var fs12 = __toESM(require("fs"));
9977
9981
  var path13 = __toESM(require("path"));
9978
- var YAML7 = __toESM(require("yaml"));
9982
+ var YAML8 = __toESM(require("yaml"));
9979
9983
  function parseRecipientEntry(entry) {
9980
9984
  if (typeof entry === "string") {
9981
9985
  return { key: entry };
@@ -9998,12 +10002,12 @@ function toRecipient(entry) {
9998
10002
  }
9999
10003
  function readManifestYaml(repoRoot) {
10000
10004
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
10001
- const raw = fs11.readFileSync(manifestPath, "utf-8");
10002
- return YAML7.parse(raw);
10005
+ const raw = fs12.readFileSync(manifestPath, "utf-8");
10006
+ return YAML8.parse(raw);
10003
10007
  }
10004
10008
  function writeManifestYaml(repoRoot, doc) {
10005
10009
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
10006
- fs11.writeFileSync(manifestPath, YAML7.stringify(doc), "utf-8");
10010
+ fs12.writeFileSync(manifestPath, YAML8.stringify(doc), "utf-8");
10007
10011
  }
10008
10012
  function getRecipientsArray(doc) {
10009
10013
  const sops = doc.sops;
@@ -10104,7 +10108,7 @@ var RecipientManager = class {
10104
10108
  throw new Error(`Recipient '${keyPreview(normalizedKey)}' is already present.`);
10105
10109
  }
10106
10110
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
10107
- const manifestBackup = fs11.readFileSync(manifestPath, "utf-8");
10111
+ const manifestBackup = fs12.readFileSync(manifestPath, "utf-8");
10108
10112
  const recipients = environment ? ensureEnvironmentRecipientsArray(doc, environment) : ensureRecipientsArray(doc);
10109
10113
  if (label) {
10110
10114
  recipients.push({ key: normalizedKey, label });
@@ -10119,16 +10123,16 @@ var RecipientManager = class {
10119
10123
  const fileBackups = /* @__PURE__ */ new Map();
10120
10124
  for (const cell of cells) {
10121
10125
  try {
10122
- fileBackups.set(cell.filePath, fs11.readFileSync(cell.filePath, "utf-8"));
10126
+ fileBackups.set(cell.filePath, fs12.readFileSync(cell.filePath, "utf-8"));
10123
10127
  await this.encryption.addRecipient(cell.filePath, normalizedKey);
10124
10128
  reEncryptedFiles.push(cell.filePath);
10125
10129
  } catch {
10126
10130
  failedFiles.push(cell.filePath);
10127
- fs11.writeFileSync(manifestPath, manifestBackup, "utf-8");
10131
+ fs12.writeFileSync(manifestPath, manifestBackup, "utf-8");
10128
10132
  for (const reEncryptedFile of reEncryptedFiles) {
10129
10133
  const backup = fileBackups.get(reEncryptedFile);
10130
10134
  if (backup) {
10131
- fs11.writeFileSync(reEncryptedFile, backup, "utf-8");
10135
+ fs12.writeFileSync(reEncryptedFile, backup, "utf-8");
10132
10136
  }
10133
10137
  }
10134
10138
  const restoredDoc = readManifestYaml(repoRoot);
@@ -10182,7 +10186,7 @@ var RecipientManager = class {
10182
10186
  }
10183
10187
  const removedEntry = parsed[matchIndex];
10184
10188
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
10185
- const manifestBackup = fs11.readFileSync(manifestPath, "utf-8");
10189
+ const manifestBackup = fs12.readFileSync(manifestPath, "utf-8");
10186
10190
  const recipients = environment ? ensureEnvironmentRecipientsArray(doc, environment) : ensureRecipientsArray(doc);
10187
10191
  recipients.splice(matchIndex, 1);
10188
10192
  writeManifestYaml(repoRoot, doc);
@@ -10193,16 +10197,16 @@ var RecipientManager = class {
10193
10197
  const fileBackups = /* @__PURE__ */ new Map();
10194
10198
  for (const cell of cells) {
10195
10199
  try {
10196
- fileBackups.set(cell.filePath, fs11.readFileSync(cell.filePath, "utf-8"));
10200
+ fileBackups.set(cell.filePath, fs12.readFileSync(cell.filePath, "utf-8"));
10197
10201
  await this.encryption.removeRecipient(cell.filePath, trimmedKey);
10198
10202
  reEncryptedFiles.push(cell.filePath);
10199
10203
  } catch {
10200
10204
  failedFiles.push(cell.filePath);
10201
- fs11.writeFileSync(manifestPath, manifestBackup, "utf-8");
10205
+ fs12.writeFileSync(manifestPath, manifestBackup, "utf-8");
10202
10206
  for (const reEncryptedFile of reEncryptedFiles) {
10203
10207
  const backup = fileBackups.get(reEncryptedFile);
10204
10208
  if (backup) {
10205
- fs11.writeFileSync(reEncryptedFile, backup, "utf-8");
10209
+ fs12.writeFileSync(reEncryptedFile, backup, "utf-8");
10206
10210
  }
10207
10211
  }
10208
10212
  const restoredDoc = readManifestYaml(repoRoot);
@@ -10236,9 +10240,9 @@ var RecipientManager = class {
10236
10240
  };
10237
10241
 
10238
10242
  // src/recipients/requests.ts
10239
- var fs12 = __toESM(require("fs"));
10243
+ var fs13 = __toESM(require("fs"));
10240
10244
  var path14 = __toESM(require("path"));
10241
- var YAML8 = __toESM(require("yaml"));
10245
+ var YAML9 = __toESM(require("yaml"));
10242
10246
  var REQUESTS_FILENAME = ".clef-requests.yaml";
10243
10247
  var HEADER_COMMENT2 = "# Pending recipient access requests. Approve with: clef recipients approve <label>\n";
10244
10248
  function requestsFilePath(repoRoot) {
@@ -10247,9 +10251,9 @@ function requestsFilePath(repoRoot) {
10247
10251
  function loadRequests(repoRoot) {
10248
10252
  const filePath = requestsFilePath(repoRoot);
10249
10253
  try {
10250
- if (!fs12.existsSync(filePath)) return [];
10251
- const content = fs12.readFileSync(filePath, "utf-8");
10252
- const parsed = YAML8.parse(content);
10254
+ if (!fs13.existsSync(filePath)) return [];
10255
+ const content = fs13.readFileSync(filePath, "utf-8");
10256
+ const parsed = YAML9.parse(content);
10253
10257
  if (!parsed || !Array.isArray(parsed.requests)) return [];
10254
10258
  return parsed.requests.map((r) => ({
10255
10259
  key: r.key,
@@ -10265,7 +10269,7 @@ function saveRequests(repoRoot, requests) {
10265
10269
  const filePath = requestsFilePath(repoRoot);
10266
10270
  if (requests.length === 0) {
10267
10271
  try {
10268
- fs12.unlinkSync(filePath);
10272
+ fs13.unlinkSync(filePath);
10269
10273
  } catch {
10270
10274
  }
10271
10275
  return;
@@ -10281,7 +10285,7 @@ function saveRequests(repoRoot, requests) {
10281
10285
  return raw;
10282
10286
  })
10283
10287
  };
10284
- fs12.writeFileSync(filePath, HEADER_COMMENT2 + YAML8.stringify(data), "utf-8");
10288
+ fs13.writeFileSync(filePath, HEADER_COMMENT2 + YAML9.stringify(data), "utf-8");
10285
10289
  }
10286
10290
  function upsertRequest(repoRoot, key, label, environment) {
10287
10291
  const requests = loadRequests(repoRoot);
@@ -10317,9 +10321,7 @@ function findInList(requests, identifier) {
10317
10321
  }
10318
10322
 
10319
10323
  // src/drift/detector.ts
10320
- var fs13 = __toESM(require("fs"));
10321
10324
  var path15 = __toESM(require("path"));
10322
- var YAML9 = __toESM(require("yaml"));
10323
10325
  var DriftDetector = class {
10324
10326
  parser = new ManifestParser();
10325
10327
  matrix = new MatrixManager();
@@ -10347,45 +10349,30 @@ var DriftDetector = class {
10347
10349
  }
10348
10350
  const issues = [];
10349
10351
  let namespacesClean = 0;
10352
+ const remoteEnvSet = new Set(remoteEnvNames);
10353
+ const sharedEnvSet = new Set(localEnvNames.filter((e) => remoteEnvSet.has(e)));
10350
10354
  for (const ns of sharedNamespaces) {
10351
- const keyEnvs = /* @__PURE__ */ new Map();
10352
- const allEnvs = /* @__PURE__ */ new Set();
10353
- const localNsCells = localCells.filter((c) => c.namespace === ns);
10354
- for (const cell of localNsCells) {
10355
- const keys = this.readKeysFromFile(cell.filePath);
10356
- if (keys === null) continue;
10357
- allEnvs.add(cell.environment);
10358
- for (const key of keys) {
10359
- if (!keyEnvs.has(key)) keyEnvs.set(key, /* @__PURE__ */ new Set());
10360
- keyEnvs.get(key).add(cell.environment);
10361
- }
10362
- }
10363
- const remoteNsCells = remoteCells.filter((c) => c.namespace === ns);
10364
- for (const cell of remoteNsCells) {
10365
- const keys = this.readKeysFromFile(cell.filePath);
10366
- if (keys === null) continue;
10367
- allEnvs.add(cell.environment);
10368
- for (const key of keys) {
10369
- if (!keyEnvs.has(key)) keyEnvs.set(key, /* @__PURE__ */ new Set());
10370
- keyEnvs.get(key).add(cell.environment);
10371
- }
10372
- }
10373
- const envList = [...allEnvs];
10355
+ const localKeyEnvs = this.collectKeyEnvs(localCells, ns, sharedEnvSet);
10356
+ const remoteKeyEnvs = this.collectKeyEnvs(remoteCells, ns, sharedEnvSet);
10374
10357
  let nsClean = true;
10375
- for (const [key, envSet] of keyEnvs) {
10376
- const missingFrom = envList.filter((e) => !envSet.has(e));
10377
- if (missingFrom.length > 0) {
10378
- nsClean = false;
10379
- const presentIn = [...envSet].sort();
10380
- issues.push({
10381
- namespace: ns,
10382
- key,
10383
- presentIn,
10384
- missingFrom: missingFrom.sort(),
10385
- message: `Key '${key}' in namespace '${ns}' exists in [${presentIn.join(", ")}] but is missing from [${missingFrom.sort().join(", ")}]`
10386
- });
10358
+ const reportDrift = (sourceMap, targetMap, direction) => {
10359
+ for (const [key, sourceEnvs] of sourceMap) {
10360
+ const targetEnvs = targetMap.get(key);
10361
+ const missingFrom = [...sourceEnvs].filter((e) => !targetEnvs?.has(e)).sort();
10362
+ if (missingFrom.length > 0) {
10363
+ nsClean = false;
10364
+ issues.push({
10365
+ namespace: ns,
10366
+ key,
10367
+ presentIn: [...sourceEnvs].sort(),
10368
+ missingFrom,
10369
+ message: `Key '${key}' in namespace '${ns}' exists in ${direction} [${[...sourceEnvs].sort().join(", ")}] but is missing from [${missingFrom.join(", ")}]`
10370
+ });
10371
+ }
10387
10372
  }
10388
- }
10373
+ };
10374
+ reportDrift(remoteKeyEnvs, localKeyEnvs, "remote");
10375
+ reportDrift(localKeyEnvs, remoteKeyEnvs, "local");
10389
10376
  if (nsClean) namespacesClean++;
10390
10377
  }
10391
10378
  return {
@@ -10396,29 +10383,23 @@ var DriftDetector = class {
10396
10383
  remoteEnvironments: remoteEnvNames
10397
10384
  };
10398
10385
  }
10399
- /**
10400
- * Read top-level key names from an encrypted SOPS YAML file,
10401
- * filtering out the `sops` metadata key.
10402
- *
10403
- * @returns Array of key names, or `null` if the file cannot be read.
10404
- */
10405
- readKeysFromFile(filePath) {
10406
- try {
10407
- if (!fs13.existsSync(filePath)) return null;
10408
- const raw = fs13.readFileSync(filePath, "utf-8");
10409
- const parsed = YAML9.parse(raw);
10410
- if (parsed === null || parsed === void 0 || typeof parsed !== "object") return null;
10411
- return Object.keys(parsed).filter((k) => k !== "sops");
10412
- } catch {
10413
- return null;
10386
+ collectKeyEnvs(cells, ns, sharedEnvSet) {
10387
+ const keyEnvs = /* @__PURE__ */ new Map();
10388
+ for (const cell of cells) {
10389
+ if (cell.namespace !== ns || !sharedEnvSet.has(cell.environment)) continue;
10390
+ const keys = readSopsKeyNames(cell.filePath);
10391
+ if (keys === null) continue;
10392
+ for (const key of keys) {
10393
+ if (!keyEnvs.has(key)) keyEnvs.set(key, /* @__PURE__ */ new Set());
10394
+ keyEnvs.get(key).add(cell.environment);
10395
+ }
10414
10396
  }
10397
+ return keyEnvs;
10415
10398
  }
10416
10399
  };
10417
10400
 
10418
10401
  // src/report/generator.ts
10419
- var fs14 = __toESM(require("fs"));
10420
10402
  var path16 = __toESM(require("path"));
10421
- var YAML10 = __toESM(require("yaml"));
10422
10403
 
10423
10404
  // src/report/sanitizer.ts
10424
10405
  var ReportSanitizer = class {
@@ -10723,15 +10704,7 @@ var ReportGenerator = class {
10723
10704
  };
10724
10705
  }
10725
10706
  readKeyCount(filePath) {
10726
- try {
10727
- if (!fs14.existsSync(filePath)) return 0;
10728
- const raw = fs14.readFileSync(filePath, "utf-8");
10729
- const parsed = YAML10.parse(raw);
10730
- if (parsed === null || parsed === void 0 || typeof parsed !== "object") return 0;
10731
- return Object.keys(parsed).filter((k) => k !== "sops").length;
10732
- } catch {
10733
- return 0;
10734
- }
10707
+ return readSopsKeyNames(filePath)?.length ?? 0;
10735
10708
  }
10736
10709
  async buildPolicy(manifest, repoRoot) {
10737
10710
  try {
@@ -11060,10 +11033,10 @@ var SopsMergeDriver = class {
11060
11033
  };
11061
11034
 
11062
11035
  // src/service-identity/manager.ts
11063
- var fs15 = __toESM(require("fs"));
11036
+ var fs14 = __toESM(require("fs"));
11064
11037
  var os = __toESM(require("os"));
11065
11038
  var path17 = __toESM(require("path"));
11066
- var YAML11 = __toESM(require("yaml"));
11039
+ var YAML10 = __toESM(require("yaml"));
11067
11040
  var PartialRotationError = class extends Error {
11068
11041
  constructor(message, rotatedKeys) {
11069
11042
  super(message);
@@ -11115,8 +11088,8 @@ var ServiceIdentityManager = class {
11115
11088
  };
11116
11089
  await this.registerRecipients(definition, manifest, repoRoot);
11117
11090
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11118
- const raw = fs15.readFileSync(manifestPath, "utf-8");
11119
- const doc = YAML11.parse(raw);
11091
+ const raw = fs14.readFileSync(manifestPath, "utf-8");
11092
+ const doc = YAML10.parse(raw);
11120
11093
  if (!Array.isArray(doc.service_identities)) {
11121
11094
  doc.service_identities = [];
11122
11095
  }
@@ -11128,11 +11101,11 @@ var ServiceIdentityManager = class {
11128
11101
  });
11129
11102
  const tmpCreate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11130
11103
  try {
11131
- fs15.writeFileSync(tmpCreate, YAML11.stringify(doc), "utf-8");
11132
- fs15.renameSync(tmpCreate, manifestPath);
11104
+ fs14.writeFileSync(tmpCreate, YAML10.stringify(doc), "utf-8");
11105
+ fs14.renameSync(tmpCreate, manifestPath);
11133
11106
  } finally {
11134
11107
  try {
11135
- fs15.unlinkSync(tmpCreate);
11108
+ fs14.unlinkSync(tmpCreate);
11136
11109
  } catch {
11137
11110
  }
11138
11111
  }
@@ -11171,8 +11144,8 @@ var ServiceIdentityManager = class {
11171
11144
  }
11172
11145
  }
11173
11146
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11174
- const raw = fs15.readFileSync(manifestPath, "utf-8");
11175
- const doc = YAML11.parse(raw);
11147
+ const raw = fs14.readFileSync(manifestPath, "utf-8");
11148
+ const doc = YAML10.parse(raw);
11176
11149
  const identities = doc.service_identities;
11177
11150
  if (Array.isArray(identities)) {
11178
11151
  doc.service_identities = identities.filter(
@@ -11181,11 +11154,11 @@ var ServiceIdentityManager = class {
11181
11154
  }
11182
11155
  const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11183
11156
  try {
11184
- fs15.writeFileSync(tmp, YAML11.stringify(doc), "utf-8");
11185
- fs15.renameSync(tmp, manifestPath);
11157
+ fs14.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
11158
+ fs14.renameSync(tmp, manifestPath);
11186
11159
  } finally {
11187
11160
  try {
11188
- fs15.unlinkSync(tmp);
11161
+ fs14.unlinkSync(tmp);
11189
11162
  } catch {
11190
11163
  }
11191
11164
  }
@@ -11201,8 +11174,8 @@ var ServiceIdentityManager = class {
11201
11174
  throw new Error(`Service identity '${name}' not found.`);
11202
11175
  }
11203
11176
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11204
- const raw = fs15.readFileSync(manifestPath, "utf-8");
11205
- const doc = YAML11.parse(raw);
11177
+ const raw = fs14.readFileSync(manifestPath, "utf-8");
11178
+ const doc = YAML10.parse(raw);
11206
11179
  const identities = doc.service_identities;
11207
11180
  const siDoc = identities.find((si) => si.name === name);
11208
11181
  const envs = siDoc.environments;
@@ -11229,11 +11202,11 @@ var ServiceIdentityManager = class {
11229
11202
  }
11230
11203
  const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11231
11204
  try {
11232
- fs15.writeFileSync(tmp, YAML11.stringify(doc), "utf-8");
11233
- fs15.renameSync(tmp, manifestPath);
11205
+ fs14.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
11206
+ fs14.renameSync(tmp, manifestPath);
11234
11207
  } finally {
11235
11208
  try {
11236
- fs15.unlinkSync(tmp);
11209
+ fs14.unlinkSync(tmp);
11237
11210
  } catch {
11238
11211
  }
11239
11212
  }
@@ -11270,8 +11243,8 @@ var ServiceIdentityManager = class {
11270
11243
  throw new Error(`Service identity '${name}' not found.`);
11271
11244
  }
11272
11245
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11273
- const raw = fs15.readFileSync(manifestPath, "utf-8");
11274
- const doc = YAML11.parse(raw);
11246
+ const raw = fs14.readFileSync(manifestPath, "utf-8");
11247
+ const doc = YAML10.parse(raw);
11275
11248
  const identities = doc.service_identities;
11276
11249
  const siDoc = identities.find((si) => si.name === name);
11277
11250
  const envs = siDoc.environments;
@@ -11326,11 +11299,11 @@ var ServiceIdentityManager = class {
11326
11299
  }
11327
11300
  const tmpRotate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11328
11301
  try {
11329
- fs15.writeFileSync(tmpRotate, YAML11.stringify(doc), "utf-8");
11330
- fs15.renameSync(tmpRotate, manifestPath);
11302
+ fs14.writeFileSync(tmpRotate, YAML10.stringify(doc), "utf-8");
11303
+ fs14.renameSync(tmpRotate, manifestPath);
11331
11304
  } finally {
11332
11305
  try {
11333
- fs15.unlinkSync(tmpRotate);
11306
+ fs14.unlinkSync(tmpRotate);
11334
11307
  } catch {
11335
11308
  }
11336
11309
  }
@@ -11451,7 +11424,7 @@ async function resolveIdentitySecrets(identityName, environment, manifest, repoR
11451
11424
  }
11452
11425
 
11453
11426
  // src/artifact/packer.ts
11454
- var fs16 = __toESM(require("fs"));
11427
+ var fs15 = __toESM(require("fs"));
11455
11428
  var path18 = __toESM(require("path"));
11456
11429
  var crypto4 = __toESM(require("crypto"));
11457
11430
 
@@ -11459,7 +11432,7 @@ var crypto4 = __toESM(require("crypto"));
11459
11432
  var crypto3 = __toESM(require("crypto"));
11460
11433
  function buildSigningPayload(artifact) {
11461
11434
  const fields = [
11462
- "clef-sig-v1",
11435
+ "clef-sig-v2",
11463
11436
  String(artifact.version),
11464
11437
  artifact.identity,
11465
11438
  artifact.environment,
@@ -11471,7 +11444,9 @@ function buildSigningPayload(artifact) {
11471
11444
  artifact.envelope?.provider ?? "",
11472
11445
  artifact.envelope?.keyId ?? "",
11473
11446
  artifact.envelope?.wrappedKey ?? "",
11474
- artifact.envelope?.algorithm ?? ""
11447
+ artifact.envelope?.algorithm ?? "",
11448
+ artifact.envelope?.iv ?? "",
11449
+ artifact.envelope?.authTag ?? ""
11475
11450
  ];
11476
11451
  return Buffer.from(fields.join("\n"), "utf-8");
11477
11452
  }
@@ -11565,37 +11540,41 @@ var ArtifactPacker = class {
11565
11540
  if (!this.kms) {
11566
11541
  throw new Error("KMS provider required for envelope encryption but none was provided.");
11567
11542
  }
11568
- const { generateIdentity, identityToRecipient, Encrypter } = await Promise.resolve().then(() => __toESM(require_age_encryption()));
11569
- const ephemeralPrivateKey = await generateIdentity();
11570
- const ephemeralPublicKey = await identityToRecipient(ephemeralPrivateKey);
11543
+ const dek = crypto4.randomBytes(32);
11544
+ const iv = crypto4.randomBytes(12);
11571
11545
  try {
11572
- const e = new Encrypter();
11573
- e.addRecipient(ephemeralPublicKey);
11574
- const encrypted = await e.encrypt(plaintext);
11575
- ciphertext = Buffer.from(encrypted).toString("base64");
11576
- } catch {
11577
- throw new Error("Failed to age-encrypt artifact with ephemeral key.");
11546
+ const cipher = crypto4.createCipheriv("aes-256-gcm", dek, iv);
11547
+ const ciphertextBuf = Buffer.concat([
11548
+ cipher.update(Buffer.from(plaintext, "utf-8")),
11549
+ cipher.final()
11550
+ ]);
11551
+ const authTag = cipher.getAuthTag();
11552
+ ciphertext = ciphertextBuf.toString("base64");
11553
+ const kmsConfig = resolved.envConfig.kms;
11554
+ const wrapped = await this.kms.wrap(kmsConfig.keyId, dek);
11555
+ const revision = `${Date.now()}-${crypto4.randomBytes(4).toString("hex")}`;
11556
+ const ciphertextHash = crypto4.createHash("sha256").update(ciphertext).digest("hex");
11557
+ artifact = {
11558
+ version: 1,
11559
+ identity: config.identity,
11560
+ environment: config.environment,
11561
+ packedAt: (/* @__PURE__ */ new Date()).toISOString(),
11562
+ revision,
11563
+ ciphertextHash,
11564
+ ciphertext,
11565
+ keys: Object.keys(resolved.values),
11566
+ envelope: {
11567
+ provider: kmsConfig.provider,
11568
+ keyId: kmsConfig.keyId,
11569
+ wrappedKey: wrapped.wrappedKey.toString("base64"),
11570
+ algorithm: wrapped.algorithm,
11571
+ iv: iv.toString("base64"),
11572
+ authTag: authTag.toString("base64")
11573
+ }
11574
+ };
11575
+ } finally {
11576
+ dek.fill(0);
11578
11577
  }
11579
- const kmsConfig = resolved.envConfig.kms;
11580
- const wrapped = await this.kms.wrap(kmsConfig.keyId, Buffer.from(ephemeralPrivateKey));
11581
- const revision = `${Date.now()}-${crypto4.randomBytes(4).toString("hex")}`;
11582
- const ciphertextHash = crypto4.createHash("sha256").update(ciphertext).digest("hex");
11583
- artifact = {
11584
- version: 1,
11585
- identity: config.identity,
11586
- environment: config.environment,
11587
- packedAt: (/* @__PURE__ */ new Date()).toISOString(),
11588
- revision,
11589
- ciphertextHash,
11590
- ciphertext,
11591
- keys: Object.keys(resolved.values),
11592
- envelope: {
11593
- provider: kmsConfig.provider,
11594
- keyId: kmsConfig.keyId,
11595
- wrappedKey: wrapped.wrappedKey.toString("base64"),
11596
- algorithm: wrapped.algorithm
11597
- }
11598
- };
11599
11578
  } else {
11600
11579
  try {
11601
11580
  const { Encrypter } = await Promise.resolve().then(() => __toESM(require_age_encryption()));
@@ -11603,8 +11582,10 @@ var ArtifactPacker = class {
11603
11582
  e.addRecipient(resolved.recipient);
11604
11583
  const encrypted = await e.encrypt(plaintext);
11605
11584
  ciphertext = Buffer.from(encrypted).toString("base64");
11606
- } catch {
11607
- throw new Error("Failed to age-encrypt artifact. Check recipient key.");
11585
+ } catch (err) {
11586
+ throw new Error(
11587
+ `Failed to age-encrypt artifact: ${err instanceof Error ? err.message : String(err)}`
11588
+ );
11608
11589
  }
11609
11590
  const revision = `${Date.now()}-${crypto4.randomBytes(4).toString("hex")}`;
11610
11591
  const ciphertextHash = crypto4.createHash("sha256").update(ciphertext).digest("hex");
@@ -11620,8 +11601,8 @@ var ArtifactPacker = class {
11620
11601
  };
11621
11602
  }
11622
11603
  const outputDir = path18.dirname(config.outputPath);
11623
- if (!fs16.existsSync(outputDir)) {
11624
- fs16.mkdirSync(outputDir, { recursive: true });
11604
+ if (!fs15.existsSync(outputDir)) {
11605
+ fs15.mkdirSync(outputDir, { recursive: true });
11625
11606
  }
11626
11607
  if (config.ttl && config.ttl > 0) {
11627
11608
  artifact.expiresAt = new Date(Date.now() + config.ttl * 1e3).toISOString();
@@ -11640,8 +11621,8 @@ var ArtifactPacker = class {
11640
11621
  }
11641
11622
  const json = JSON.stringify(artifact, null, 2);
11642
11623
  const tmpOutput = `${config.outputPath}.tmp.${process.pid}`;
11643
- fs16.writeFileSync(tmpOutput, json, "utf-8");
11644
- fs16.renameSync(tmpOutput, config.outputPath);
11624
+ fs15.writeFileSync(tmpOutput, json, "utf-8");
11625
+ fs15.renameSync(tmpOutput, config.outputPath);
11645
11626
  return {
11646
11627
  outputPath: config.outputPath,
11647
11628
  namespaceCount: resolved.identity.namespaces.length,
@@ -11651,6 +11632,9 @@ var ArtifactPacker = class {
11651
11632
  };
11652
11633
  }
11653
11634
  };
11635
+
11636
+ // src/kms/types.ts
11637
+ var VALID_KMS_PROVIDERS = ["aws", "gcp", "azure"];
11654
11638
  // Annotate the CommonJS export names for ESM import in node:
11655
11639
  0 && (module.exports = {
11656
11640
  ArtifactPacker,
@@ -11689,6 +11673,7 @@ var ArtifactPacker = class {
11689
11673
  SopsMergeDriver,
11690
11674
  SopsMissingError,
11691
11675
  SopsVersionError,
11676
+ VALID_KMS_PROVIDERS,
11692
11677
  assertSops,
11693
11678
  buildSigningPayload,
11694
11679
  checkAll,