@clef-sh/core 0.1.6-beta.22 → 0.1.7-beta.43

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
@@ -156,7 +156,7 @@ var require_age_encryption = __commonJS({
156
156
  Object.assign(hashC, info);
157
157
  return Object.freeze(hashC);
158
158
  }
159
- function randomBytes3(bytesLength = 32) {
159
+ function randomBytes4(bytesLength = 32) {
160
160
  const cr = typeof globalThis === "object" ? globalThis.crypto : null;
161
161
  if (typeof cr?.getRandomValues !== "function")
162
162
  throw new Error("crypto.getRandomValues must be defined");
@@ -608,7 +608,7 @@ var require_age_encryption = __commonJS({
608
608
  };
609
609
  }
610
610
  // @__NO_SIDE_EFFECTS__
611
- function join15(separator = "") {
611
+ function join16(separator = "") {
612
612
  astr("join", separator);
613
613
  return {
614
614
  encode: (from) => {
@@ -738,9 +738,9 @@ var require_age_encryption = __commonJS({
738
738
  decode(s) {
739
739
  return decodeBase64Builtin(s, false);
740
740
  }
741
- } : /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ padding(6), /* @__PURE__ */ join15(""));
742
- var base64nopad = /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ join15(""));
743
- var BECH_ALPHABET = /* @__PURE__ */ chain(/* @__PURE__ */ alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), /* @__PURE__ */ join15(""));
741
+ } : /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ padding(6), /* @__PURE__ */ join16(""));
742
+ var base64nopad = /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ join16(""));
743
+ var BECH_ALPHABET = /* @__PURE__ */ chain(/* @__PURE__ */ alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), /* @__PURE__ */ join16(""));
744
744
  var POLYMOD_GENERATORS = [996825010, 642813549, 513874426, 1027748829, 705979059];
745
745
  function bech32Polymod(pre) {
746
746
  const b = pre >> 25;
@@ -4339,7 +4339,7 @@ var require_age_encryption = __commonJS({
4339
4339
  Object.assign(hashC, info);
4340
4340
  return Object.freeze(hashC);
4341
4341
  }
4342
- function randomBytes4(bytesLength = 32) {
4342
+ function randomBytes42(bytesLength = 32) {
4343
4343
  const cr = typeof globalThis === "object" ? globalThis.crypto : null;
4344
4344
  if (typeof cr?.getRandomValues !== "function")
4345
4345
  throw new Error("crypto.getRandomValues must be defined");
@@ -5007,7 +5007,7 @@ var require_age_encryption = __commonJS({
5007
5007
  return values;
5008
5008
  };
5009
5009
  };
5010
- var randomBytes5 = randomBytes4;
5010
+ var randomBytes5 = randomBytes42;
5011
5011
  function equalBytes2(a, b) {
5012
5012
  if (a.length !== b.length)
5013
5013
  return false;
@@ -5944,12 +5944,12 @@ var require_age_encryption = __commonJS({
5944
5944
  return generateX25519Identity();
5945
5945
  }
5946
5946
  function generateX25519Identity() {
5947
- const scalar = randomBytes3(32);
5947
+ const scalar = randomBytes4(32);
5948
5948
  const identity = bech32.encodeFromBytes("AGE-SECRET-KEY-", scalar).toUpperCase();
5949
5949
  return Promise.resolve(identity);
5950
5950
  }
5951
5951
  function generateHybridIdentity() {
5952
- const scalar = randomBytes3(32);
5952
+ const scalar = randomBytes4(32);
5953
5953
  const identity = bech32.encodeFromBytes("AGE-SECRET-KEY-PQ-", scalar).toUpperCase();
5954
5954
  return Promise.resolve(identity);
5955
5955
  }
@@ -6157,7 +6157,7 @@ var require_age_encryption = __commonJS({
6157
6157
  this.recipient = res.bytes;
6158
6158
  }
6159
6159
  async wrapFileKey(fileKey) {
6160
- const ephemeral = randomBytes3(32);
6160
+ const ephemeral = randomBytes4(32);
6161
6161
  const share = await scalarMultBase(ephemeral);
6162
6162
  const secret = await scalarMult(ephemeral, this.recipient);
6163
6163
  const salt = new Uint8Array(share.length + this.recipient.length);
@@ -6218,7 +6218,7 @@ var require_age_encryption = __commonJS({
6218
6218
  this.logN = logN;
6219
6219
  }
6220
6220
  wrapFileKey(fileKey) {
6221
- const salt = randomBytes3(16);
6221
+ const salt = randomBytes4(16);
6222
6222
  const label2 = "age-encryption.org/v1/scrypt";
6223
6223
  const labelAndSalt = new Uint8Array(label2.length + 16);
6224
6224
  labelAndSalt.set(new TextEncoder().encode(label2));
@@ -6553,7 +6553,7 @@ var require_age_encryption = __commonJS({
6553
6553
  rp: { name: "", id: options.rpId },
6554
6554
  user: {
6555
6555
  name: options.keyName,
6556
- id: domBuffer2(randomBytes3(8)),
6556
+ id: domBuffer2(randomBytes4(8)),
6557
6557
  // avoid overwriting existing keys
6558
6558
  displayName: ""
6559
6559
  },
@@ -6623,7 +6623,7 @@ var require_age_encryption = __commonJS({
6623
6623
  transports: this.transports,
6624
6624
  type: "public-key"
6625
6625
  }] : [],
6626
- challenge: domBuffer2(randomBytes3(16)),
6626
+ challenge: domBuffer2(randomBytes4(16)),
6627
6627
  extensions: { prf: { eval: prfInputs(nonce) } },
6628
6628
  userVerification: "required",
6629
6629
  // prf requires UV
@@ -6642,7 +6642,7 @@ var require_age_encryption = __commonJS({
6642
6642
  * Implements {@link Recipient.wrapFileKey}.
6643
6643
  */
6644
6644
  async wrapFileKey(fileKey) {
6645
- const nonce = randomBytes3(16);
6645
+ const nonce = randomBytes4(16);
6646
6646
  const results = await this.getCredential(nonce);
6647
6647
  const key = deriveKey(results);
6648
6648
  return [new Stanza([label, base64nopad.encode(nonce)], encryptFileKey(fileKey, key))];
@@ -6765,7 +6765,7 @@ var require_age_encryption = __commonJS({
6765
6765
  }
6766
6766
  }
6767
6767
  async encrypt(file) {
6768
- const fileKey = randomBytes3(16);
6768
+ const fileKey = randomBytes4(16);
6769
6769
  const stanzas = [];
6770
6770
  let recipients = this.recipients;
6771
6771
  if (this.passphrase !== null) {
@@ -6778,7 +6778,7 @@ var require_age_encryption = __commonJS({
6778
6778
  const hmacKey = hkdf(sha256, fileKey, void 0, labelHeader, 32);
6779
6779
  const mac = hmac(sha256, hmacKey, encodeHeaderNoMAC(stanzas));
6780
6780
  const header = encodeHeader(stanzas, mac);
6781
- const nonce = randomBytes3(16);
6781
+ const nonce = randomBytes4(16);
6782
6782
  const labelPayload = new TextEncoder().encode("payload");
6783
6783
  const streamKey = hkdf(sha256, fileKey, nonce, labelPayload, 32);
6784
6784
  const encrypter = encryptSTREAM(streamKey);
@@ -6919,6 +6919,8 @@ __export(src_exports, {
6919
6919
  CLEF_REPORT_SCHEMA_VERSION: () => CLEF_REPORT_SCHEMA_VERSION,
6920
6920
  CLEF_SUPPORTED_EXTENSIONS: () => CLEF_SUPPORTED_EXTENSIONS,
6921
6921
  ClefError: () => ClefError,
6922
+ CloudApiError: () => CloudApiError,
6923
+ CloudClient: () => CloudClient,
6922
6924
  ConsumptionClient: () => ConsumptionClient,
6923
6925
  DiffEngine: () => DiffEngine,
6924
6926
  DriftDetector: () => DriftDetector,
@@ -6930,10 +6932,12 @@ __export(src_exports, {
6930
6932
  ManifestValidationError: () => ManifestValidationError,
6931
6933
  MatrixManager: () => MatrixManager,
6932
6934
  PartialRotationError: () => PartialRotationError,
6935
+ REQUESTS_FILENAME: () => REQUESTS_FILENAME,
6933
6936
  REQUIREMENTS: () => REQUIREMENTS,
6934
6937
  RecipientManager: () => RecipientManager,
6935
6938
  ReportGenerator: () => ReportGenerator,
6936
6939
  ReportSanitizer: () => ReportSanitizer,
6940
+ ReportTransformer: () => ReportTransformer,
6937
6941
  ScanRunner: () => ScanRunner,
6938
6942
  SchemaLoadError: () => SchemaLoadError,
6939
6943
  SchemaValidator: () => SchemaValidator,
@@ -6948,17 +6952,21 @@ __export(src_exports, {
6948
6952
  assertSops: () => assertSops,
6949
6953
  checkAll: () => checkAll,
6950
6954
  checkDependency: () => checkDependency,
6955
+ collectCIContext: () => collectCIContext,
6951
6956
  deriveAgePublicKey: () => deriveAgePublicKey,
6952
6957
  detectFormat: () => detectFormat,
6958
+ findRequest: () => findRequest,
6953
6959
  formatAgeKeyFile: () => formatAgeKeyFile,
6954
6960
  generateAgeIdentity: () => generateAgeIdentity,
6955
6961
  generateRandomValue: () => generateRandomValue,
6956
6962
  getPendingKeys: () => getPendingKeys,
6957
6963
  isHighEntropy: () => isHighEntropy,
6964
+ isKmsEnvelope: () => isKmsEnvelope,
6958
6965
  isPending: () => isPending,
6959
6966
  keyPreview: () => keyPreview,
6960
6967
  loadIgnoreRules: () => loadIgnoreRules,
6961
6968
  loadMetadata: () => loadMetadata,
6969
+ loadRequests: () => loadRequests,
6962
6970
  markPending: () => markPending,
6963
6971
  markPendingWithRetry: () => markPendingWithRetry,
6964
6972
  markResolved: () => markResolved,
@@ -6970,15 +6978,19 @@ __export(src_exports, {
6970
6978
  parseJson: () => parseJson,
6971
6979
  parseYaml: () => parseYaml,
6972
6980
  redactValue: () => redactValue,
6981
+ removeAccessRequest: () => removeRequest,
6982
+ requestsFilePath: () => requestsFilePath,
6973
6983
  resetSopsResolution: () => resetSopsResolution,
6974
6984
  resolveBackendConfig: () => resolveBackendConfig,
6975
6985
  resolveIdentitySecrets: () => resolveIdentitySecrets,
6976
6986
  resolveRecipientsForEnvironment: () => resolveRecipientsForEnvironment,
6977
6987
  resolveSopsPath: () => resolveSopsPath,
6978
6988
  saveMetadata: () => saveMetadata,
6989
+ saveRequests: () => saveRequests,
6979
6990
  shannonEntropy: () => shannonEntropy,
6980
6991
  shouldIgnoreFile: () => shouldIgnoreFile,
6981
6992
  shouldIgnoreMatch: () => shouldIgnoreMatch,
6993
+ upsertRequest: () => upsertRequest,
6982
6994
  validateAgePublicKey: () => validateAgePublicKey
6983
6995
  });
6984
6996
  module.exports = __toCommonJS(src_exports);
@@ -6992,6 +7004,7 @@ function resolveBackendConfig(manifest, environment) {
6992
7004
  backend: manifest.sops.default_backend,
6993
7005
  aws_kms_arn: manifest.sops.aws_kms_arn,
6994
7006
  gcp_kms_resource_id: manifest.sops.gcp_kms_resource_id,
7007
+ azure_kv_url: manifest.sops.azure_kv_url,
6995
7008
  pgp_fingerprint: manifest.sops.pgp_fingerprint
6996
7009
  };
6997
7010
  }
@@ -7080,7 +7093,17 @@ Then run clef doctor to verify your setup.`
7080
7093
  this.name = "SopsVersionError";
7081
7094
  }
7082
7095
  };
7096
+ function isKmsEnvelope(cfg) {
7097
+ return cfg.kms !== void 0;
7098
+ }
7083
7099
  var CLEF_REPORT_SCHEMA_VERSION = 1;
7100
+ var CloudApiError = class extends ClefError {
7101
+ constructor(message, statusCode, fix) {
7102
+ super(message, fix);
7103
+ this.statusCode = statusCode;
7104
+ this.name = "CloudApiError";
7105
+ }
7106
+ };
7084
7107
 
7085
7108
  // src/manifest/parser.ts
7086
7109
  var fs = __toESM(require("fs"));
@@ -7122,14 +7145,15 @@ function keyPreview(key) {
7122
7145
 
7123
7146
  // src/manifest/parser.ts
7124
7147
  var CLEF_MANIFEST_FILENAME = "clef.yaml";
7125
- var VALID_BACKENDS = ["age", "awskms", "gcpkms", "pgp"];
7148
+ var VALID_BACKENDS = ["age", "awskms", "gcpkms", "azurekv", "pgp"];
7126
7149
  var VALID_TOP_LEVEL_KEYS = [
7127
7150
  "version",
7128
7151
  "environments",
7129
7152
  "namespaces",
7130
7153
  "sops",
7131
7154
  "file_pattern",
7132
- "service_identities"
7155
+ "service_identities",
7156
+ "cloud"
7133
7157
  ];
7134
7158
  var ENV_NAME_PATTERN = /^[a-z][a-z0-9_-]*$/;
7135
7159
  var FILE_PATTERN_REQUIRED_TOKENS = ["{namespace}", "{environment}"];
@@ -7269,6 +7293,12 @@ var ManifestParser = class {
7269
7293
  "environments"
7270
7294
  );
7271
7295
  }
7296
+ if (backend === "azurekv" && typeof sopsOverride.azure_kv_url !== "string") {
7297
+ throw new ManifestValidationError(
7298
+ `Environment '${envObj.name}' uses 'azurekv' backend but is missing 'azure_kv_url'.`,
7299
+ "environments"
7300
+ );
7301
+ }
7272
7302
  if (backend === "pgp" && typeof sopsOverride.pgp_fingerprint !== "string") {
7273
7303
  throw new ManifestValidationError(
7274
7304
  `Environment '${envObj.name}' uses 'pgp' backend but is missing 'pgp_fingerprint'.`,
@@ -7279,6 +7309,7 @@ var ManifestParser = class {
7279
7309
  backend,
7280
7310
  ...typeof sopsOverride.aws_kms_arn === "string" ? { aws_kms_arn: sopsOverride.aws_kms_arn } : {},
7281
7311
  ...typeof sopsOverride.gcp_kms_resource_id === "string" ? { gcp_kms_resource_id: sopsOverride.gcp_kms_resource_id } : {},
7312
+ ...typeof sopsOverride.azure_kv_url === "string" ? { azure_kv_url: sopsOverride.azure_kv_url } : {},
7282
7313
  ...typeof sopsOverride.pgp_fingerprint === "string" ? { pgp_fingerprint: sopsOverride.pgp_fingerprint } : {}
7283
7314
  };
7284
7315
  }
@@ -7405,7 +7436,7 @@ var ManifestParser = class {
7405
7436
  const sopsObj = obj.sops;
7406
7437
  if (!sopsObj.default_backend || typeof sopsObj.default_backend !== "string") {
7407
7438
  throw new ManifestValidationError(
7408
- "Field 'sops.default_backend' is required and must be one of: age, awskms, gcpkms, pgp.",
7439
+ "Field 'sops.default_backend' is required and must be one of: age, awskms, gcpkms, azurekv, pgp.",
7409
7440
  "sops.default_backend"
7410
7441
  );
7411
7442
  }
@@ -7419,6 +7450,7 @@ var ManifestParser = class {
7419
7450
  default_backend: sopsObj.default_backend,
7420
7451
  ...typeof sopsObj.aws_kms_arn === "string" ? { aws_kms_arn: sopsObj.aws_kms_arn } : {},
7421
7452
  ...typeof sopsObj.gcp_kms_resource_id === "string" ? { gcp_kms_resource_id: sopsObj.gcp_kms_resource_id } : {},
7453
+ ...typeof sopsObj.azure_kv_url === "string" ? { azure_kv_url: sopsObj.azure_kv_url } : {},
7422
7454
  ...typeof sopsObj.pgp_fingerprint === "string" ? { pgp_fingerprint: sopsObj.pgp_fingerprint } : {}
7423
7455
  };
7424
7456
  for (const env of environments) {
@@ -7520,25 +7552,69 @@ var ManifestParser = class {
7520
7552
  }
7521
7553
  if (typeof envVal !== "object" || envVal === null) {
7522
7554
  throw new ManifestValidationError(
7523
- `Service identity '${siName}' environment '${envName}' must be an object with 'recipient'.`,
7555
+ `Service identity '${siName}' environment '${envName}' must be an object with 'recipient' or 'kms'.`,
7524
7556
  "service_identities"
7525
7557
  );
7526
7558
  }
7527
7559
  const envObj = envVal;
7528
- if (!envObj.recipient || typeof envObj.recipient !== "string") {
7560
+ const hasRecipient = envObj.recipient !== void 0;
7561
+ const hasKms = envObj.kms !== void 0;
7562
+ if (hasRecipient && hasKms) {
7529
7563
  throw new ManifestValidationError(
7530
- `Service identity '${siName}' environment '${envName}' is missing a 'recipient' string.`,
7564
+ `Service identity '${siName}' environment '${envName}': 'recipient' and 'kms' are mutually exclusive.`,
7531
7565
  "service_identities"
7532
7566
  );
7533
7567
  }
7534
- const recipientValidation = validateAgePublicKey(envObj.recipient);
7535
- if (!recipientValidation.valid) {
7568
+ if (!hasRecipient && !hasKms) {
7536
7569
  throw new ManifestValidationError(
7537
- `Service identity '${siName}' environment '${envName}': ${recipientValidation.error}`,
7570
+ `Service identity '${siName}' environment '${envName}' must have either 'recipient' or 'kms'.`,
7538
7571
  "service_identities"
7539
7572
  );
7540
7573
  }
7541
- parsedEnvs[envName] = { recipient: recipientValidation.key };
7574
+ if (hasRecipient) {
7575
+ if (typeof envObj.recipient !== "string") {
7576
+ throw new ManifestValidationError(
7577
+ `Service identity '${siName}' environment '${envName}': 'recipient' must be a string.`,
7578
+ "service_identities"
7579
+ );
7580
+ }
7581
+ const recipientValidation = validateAgePublicKey(envObj.recipient);
7582
+ if (!recipientValidation.valid) {
7583
+ throw new ManifestValidationError(
7584
+ `Service identity '${siName}' environment '${envName}': ${recipientValidation.error}`,
7585
+ "service_identities"
7586
+ );
7587
+ }
7588
+ parsedEnvs[envName] = { recipient: recipientValidation.key };
7589
+ } else {
7590
+ const kmsObj = envObj.kms;
7591
+ if (typeof kmsObj !== "object" || kmsObj === null) {
7592
+ throw new ManifestValidationError(
7593
+ `Service identity '${siName}' environment '${envName}': 'kms' must be an object.`,
7594
+ "service_identities"
7595
+ );
7596
+ }
7597
+ const validProviders = ["aws", "gcp", "azure"];
7598
+ if (!kmsObj.provider || !validProviders.includes(kmsObj.provider)) {
7599
+ throw new ManifestValidationError(
7600
+ `Service identity '${siName}' environment '${envName}': kms.provider must be one of: ${validProviders.join(", ")}.`,
7601
+ "service_identities"
7602
+ );
7603
+ }
7604
+ if (!kmsObj.keyId || typeof kmsObj.keyId !== "string") {
7605
+ throw new ManifestValidationError(
7606
+ `Service identity '${siName}' environment '${envName}': kms.keyId must be a non-empty string.`,
7607
+ "service_identities"
7608
+ );
7609
+ }
7610
+ parsedEnvs[envName] = {
7611
+ kms: {
7612
+ provider: kmsObj.provider,
7613
+ keyId: kmsObj.keyId,
7614
+ region: typeof kmsObj.region === "string" ? kmsObj.region : void 0
7615
+ }
7616
+ };
7617
+ }
7542
7618
  }
7543
7619
  return {
7544
7620
  name: siName,
@@ -7558,13 +7634,28 @@ var ManifestParser = class {
7558
7634
  siNames.add(si.name);
7559
7635
  }
7560
7636
  }
7637
+ let cloud;
7638
+ if (obj.cloud !== void 0) {
7639
+ if (typeof obj.cloud !== "object" || obj.cloud === null || Array.isArray(obj.cloud)) {
7640
+ throw new ManifestValidationError("Field 'cloud' must be an object.", "cloud");
7641
+ }
7642
+ const cloudObj = obj.cloud;
7643
+ if (typeof cloudObj.integrationId !== "string" || cloudObj.integrationId.length === 0) {
7644
+ throw new ManifestValidationError(
7645
+ "Field 'cloud.integrationId' is required and must be a non-empty string.",
7646
+ "cloud"
7647
+ );
7648
+ }
7649
+ cloud = { integrationId: cloudObj.integrationId };
7650
+ }
7561
7651
  return {
7562
7652
  version: 1,
7563
7653
  environments,
7564
7654
  namespaces,
7565
7655
  sops: sopsConfig,
7566
7656
  file_pattern: obj.file_pattern,
7567
- ...serviceIdentities ? { service_identities: serviceIdentities } : {}
7657
+ ...serviceIdentities ? { service_identities: serviceIdentities } : {},
7658
+ ...cloud ? { cloud } : {}
7568
7659
  };
7569
7660
  }
7570
7661
  /**
@@ -9227,6 +9318,8 @@ var SopsClient = class {
9227
9318
  if (sops.kms && Array.isArray(sops.kms) && sops.kms.length > 0) return "awskms";
9228
9319
  if (sops.gcp_kms && Array.isArray(sops.gcp_kms) && sops.gcp_kms.length > 0)
9229
9320
  return "gcpkms";
9321
+ if (sops.azure_kv && Array.isArray(sops.azure_kv) && sops.azure_kv.length > 0)
9322
+ return "azurekv";
9230
9323
  if (sops.pgp && Array.isArray(sops.pgp) && sops.pgp.length > 0) return "pgp";
9231
9324
  return "age";
9232
9325
  }
@@ -9244,6 +9337,14 @@ var SopsClient = class {
9244
9337
  const entries = sops.gcp_kms;
9245
9338
  return entries?.map((e) => String(e.resource_id ?? "")) ?? [];
9246
9339
  }
9340
+ case "azurekv": {
9341
+ const entries = sops.azure_kv;
9342
+ return entries?.map((e) => {
9343
+ const vaultUrl = String(e.vaultUrl ?? e.vault_url ?? "");
9344
+ const name = String(e.name ?? e.key ?? "");
9345
+ return vaultUrl && name ? `${vaultUrl}/keys/${name}` : vaultUrl || name;
9346
+ }) ?? [];
9347
+ }
9247
9348
  case "pgp": {
9248
9349
  const entries = sops.pgp;
9249
9350
  return entries?.map((e) => String(e.fp ?? "")) ?? [];
@@ -9256,6 +9357,7 @@ var SopsClient = class {
9256
9357
  backend: manifest.sops.default_backend,
9257
9358
  aws_kms_arn: manifest.sops.aws_kms_arn,
9258
9359
  gcp_kms_resource_id: manifest.sops.gcp_kms_resource_id,
9360
+ azure_kv_url: manifest.sops.azure_kv_url,
9259
9361
  pgp_fingerprint: manifest.sops.pgp_fingerprint
9260
9362
  };
9261
9363
  switch (config.backend) {
@@ -9271,6 +9373,11 @@ var SopsClient = class {
9271
9373
  args.push("--gcp-kms", config.gcp_kms_resource_id);
9272
9374
  }
9273
9375
  break;
9376
+ case "azurekv":
9377
+ if (config.azure_kv_url) {
9378
+ args.push("--azure-kv", config.azure_kv_url);
9379
+ }
9380
+ break;
9274
9381
  case "pgp":
9275
9382
  if (config.pgp_fingerprint) {
9276
9383
  args.push("--pgp", config.pgp_fingerprint);
@@ -9488,7 +9595,7 @@ var LintRunner = class {
9488
9595
  /**
9489
9596
  * Lint service identity configurations for drift issues.
9490
9597
  */
9491
- async lintServiceIdentities(identities, manifest, _repoRoot, existingCells) {
9598
+ async lintServiceIdentities(identities, manifest, repoRoot, existingCells) {
9492
9599
  const issues = [];
9493
9600
  const declaredEnvNames = new Set(manifest.environments.map((e) => e.name));
9494
9601
  const declaredNsNames = new Set(manifest.namespaces.map((ns) => ns.name));
@@ -9516,6 +9623,7 @@ var LintRunner = class {
9516
9623
  for (const cell of existingCells) {
9517
9624
  const envConfig = si.environments[cell.environment];
9518
9625
  if (!envConfig) continue;
9626
+ if (!envConfig.recipient) continue;
9519
9627
  if (si.namespaces.includes(cell.namespace)) {
9520
9628
  try {
9521
9629
  const metadata = await this.sopsClient.getMetadata(cell.filePath);
@@ -10106,10 +10214,91 @@ var RecipientManager = class {
10106
10214
  }
10107
10215
  };
10108
10216
 
10109
- // src/drift/detector.ts
10217
+ // src/recipients/requests.ts
10110
10218
  var fs12 = __toESM(require("fs"));
10111
10219
  var path14 = __toESM(require("path"));
10112
10220
  var YAML7 = __toESM(require("yaml"));
10221
+ var REQUESTS_FILENAME = ".clef-requests.yaml";
10222
+ var HEADER_COMMENT2 = "# Pending recipient access requests. Approve with: clef recipients approve <label>\n";
10223
+ function requestsFilePath(repoRoot) {
10224
+ return path14.join(repoRoot, REQUESTS_FILENAME);
10225
+ }
10226
+ function loadRequests(repoRoot) {
10227
+ const filePath = requestsFilePath(repoRoot);
10228
+ try {
10229
+ if (!fs12.existsSync(filePath)) return [];
10230
+ const content = fs12.readFileSync(filePath, "utf-8");
10231
+ const parsed = YAML7.parse(content);
10232
+ if (!parsed || !Array.isArray(parsed.requests)) return [];
10233
+ return parsed.requests.map((r) => ({
10234
+ key: r.key,
10235
+ label: r.label,
10236
+ requestedAt: new Date(r.requested_at),
10237
+ environment: r.environment
10238
+ }));
10239
+ } catch {
10240
+ return [];
10241
+ }
10242
+ }
10243
+ function saveRequests(repoRoot, requests) {
10244
+ const filePath = requestsFilePath(repoRoot);
10245
+ if (requests.length === 0) {
10246
+ try {
10247
+ fs12.unlinkSync(filePath);
10248
+ } catch {
10249
+ }
10250
+ return;
10251
+ }
10252
+ const data = {
10253
+ requests: requests.map((r) => {
10254
+ const raw = {
10255
+ key: r.key,
10256
+ label: r.label,
10257
+ requested_at: r.requestedAt.toISOString()
10258
+ };
10259
+ if (r.environment) raw.environment = r.environment;
10260
+ return raw;
10261
+ })
10262
+ };
10263
+ fs12.writeFileSync(filePath, HEADER_COMMENT2 + YAML7.stringify(data), "utf-8");
10264
+ }
10265
+ function upsertRequest(repoRoot, key, label, environment) {
10266
+ const requests = loadRequests(repoRoot);
10267
+ const now = /* @__PURE__ */ new Date();
10268
+ const request = { key, label, requestedAt: now, environment };
10269
+ const existingIndex = requests.findIndex((r) => r.key === key);
10270
+ if (existingIndex >= 0) {
10271
+ requests[existingIndex] = request;
10272
+ } else {
10273
+ requests.push(request);
10274
+ }
10275
+ saveRequests(repoRoot, requests);
10276
+ return request;
10277
+ }
10278
+ function removeRequest(repoRoot, identifier) {
10279
+ const requests = loadRequests(repoRoot);
10280
+ const match = findInList(requests, identifier);
10281
+ if (!match) return null;
10282
+ const filtered = requests.filter((r) => r.key !== match.key);
10283
+ saveRequests(repoRoot, filtered);
10284
+ return match;
10285
+ }
10286
+ function findRequest(repoRoot, identifier) {
10287
+ const requests = loadRequests(repoRoot);
10288
+ return findInList(requests, identifier);
10289
+ }
10290
+ function findInList(requests, identifier) {
10291
+ const lower = identifier.toLowerCase();
10292
+ const byLabel = requests.find((r) => r.label.toLowerCase() === lower);
10293
+ if (byLabel) return byLabel;
10294
+ const byKey = requests.find((r) => r.key === identifier);
10295
+ return byKey ?? null;
10296
+ }
10297
+
10298
+ // src/drift/detector.ts
10299
+ var fs13 = __toESM(require("fs"));
10300
+ var path15 = __toESM(require("path"));
10301
+ var YAML8 = __toESM(require("yaml"));
10113
10302
  var DriftDetector = class {
10114
10303
  parser = new ManifestParser();
10115
10304
  matrix = new MatrixManager();
@@ -10122,8 +10311,8 @@ var DriftDetector = class {
10122
10311
  * @returns Drift result with any issues found.
10123
10312
  */
10124
10313
  detect(localRoot, remoteRoot, namespaceFilter) {
10125
- const localManifest = this.parser.parse(path14.join(localRoot, CLEF_MANIFEST_FILENAME));
10126
- const remoteManifest = this.parser.parse(path14.join(remoteRoot, CLEF_MANIFEST_FILENAME));
10314
+ const localManifest = this.parser.parse(path15.join(localRoot, CLEF_MANIFEST_FILENAME));
10315
+ const remoteManifest = this.parser.parse(path15.join(remoteRoot, CLEF_MANIFEST_FILENAME));
10127
10316
  const localCells = this.matrix.resolveMatrix(localManifest, localRoot);
10128
10317
  const remoteCells = this.matrix.resolveMatrix(remoteManifest, remoteRoot);
10129
10318
  const localEnvNames = localManifest.environments.map((e) => e.name);
@@ -10194,9 +10383,9 @@ var DriftDetector = class {
10194
10383
  */
10195
10384
  readKeysFromFile(filePath) {
10196
10385
  try {
10197
- if (!fs12.existsSync(filePath)) return null;
10198
- const raw = fs12.readFileSync(filePath, "utf-8");
10199
- const parsed = YAML7.parse(raw);
10386
+ if (!fs13.existsSync(filePath)) return null;
10387
+ const raw = fs13.readFileSync(filePath, "utf-8");
10388
+ const parsed = YAML8.parse(raw);
10200
10389
  if (parsed === null || parsed === void 0 || typeof parsed !== "object") return null;
10201
10390
  return Object.keys(parsed).filter((k) => k !== "sops");
10202
10391
  } catch {
@@ -10206,9 +10395,9 @@ var DriftDetector = class {
10206
10395
  };
10207
10396
 
10208
10397
  // src/report/generator.ts
10209
- var fs13 = __toESM(require("fs"));
10210
- var path15 = __toESM(require("path"));
10211
- var YAML8 = __toESM(require("yaml"));
10398
+ var fs14 = __toESM(require("fs"));
10399
+ var path16 = __toESM(require("path"));
10400
+ var YAML9 = __toESM(require("yaml"));
10212
10401
 
10213
10402
  // src/report/sanitizer.ts
10214
10403
  var ReportSanitizer = class {
@@ -10364,7 +10553,7 @@ var ReportGenerator = class {
10364
10553
  let manifest = null;
10365
10554
  try {
10366
10555
  const parser = new ManifestParser();
10367
- manifest = parser.parse(path15.join(repoRoot, "clef.yaml"));
10556
+ manifest = parser.parse(path16.join(repoRoot, "clef.yaml"));
10368
10557
  } catch {
10369
10558
  const emptyManifest = {
10370
10559
  manifestVersion: 0,
@@ -10514,9 +10703,9 @@ var ReportGenerator = class {
10514
10703
  }
10515
10704
  readKeyCount(filePath) {
10516
10705
  try {
10517
- if (!fs13.existsSync(filePath)) return 0;
10518
- const raw = fs13.readFileSync(filePath, "utf-8");
10519
- const parsed = YAML8.parse(raw);
10706
+ if (!fs14.existsSync(filePath)) return 0;
10707
+ const raw = fs14.readFileSync(filePath, "utf-8");
10708
+ const parsed = YAML9.parse(raw);
10520
10709
  if (parsed === null || parsed === void 0 || typeof parsed !== "object") return 0;
10521
10710
  return Object.keys(parsed).filter((k) => k !== "sops").length;
10522
10711
  } catch {
@@ -10569,6 +10758,202 @@ var ReportGenerator = class {
10569
10758
  }
10570
10759
  };
10571
10760
 
10761
+ // src/report/transformer.ts
10762
+ var ReportTransformer = class {
10763
+ transform(report) {
10764
+ const summary = this.buildSummary(report);
10765
+ const drift = this.buildDrift(report);
10766
+ const policyResults = this.buildPolicyResults(report.policy.issues);
10767
+ return {
10768
+ commitSha: report.repoIdentity.commitSha,
10769
+ branch: report.repoIdentity.branch,
10770
+ commitTimestamp: new Date(report.repoIdentity.commitTimestamp).getTime(),
10771
+ cliVersion: report.repoIdentity.clefVersion,
10772
+ summary,
10773
+ drift,
10774
+ policyResults
10775
+ };
10776
+ }
10777
+ buildSummary(report) {
10778
+ const namespaces = [...new Set(report.matrix.map((c) => c.namespace))];
10779
+ const environments = [...new Set(report.matrix.map((c) => c.environment))];
10780
+ const cells = report.matrix.map((cell) => this.buildCell(cell, report.policy.issues));
10781
+ const violations = report.policy.issues.filter((i) => i.severity === "error").length;
10782
+ return {
10783
+ filesScanned: report.matrix.length,
10784
+ namespaces,
10785
+ environments,
10786
+ cells,
10787
+ violations,
10788
+ passed: violations === 0
10789
+ };
10790
+ }
10791
+ buildCell(cell, issues) {
10792
+ const healthStatus = this.computeHealthStatus(cell, issues);
10793
+ const description = this.describeCell(cell, healthStatus);
10794
+ return {
10795
+ namespace: cell.namespace,
10796
+ environment: cell.environment,
10797
+ healthStatus,
10798
+ description
10799
+ };
10800
+ }
10801
+ computeHealthStatus(cell, issues) {
10802
+ if (!cell.exists) return "unknown";
10803
+ const cellIssues = issues.filter(
10804
+ (i) => i.namespace === cell.namespace && i.environment === cell.environment || i.file !== void 0 && i.file.includes(cell.namespace) && i.file.includes(cell.environment)
10805
+ );
10806
+ if (cellIssues.some((i) => i.severity === "error")) return "critical";
10807
+ if (cellIssues.some((i) => i.severity === "warning") || cell.pendingCount > 0) return "warning";
10808
+ return "healthy";
10809
+ }
10810
+ describeCell(cell, status) {
10811
+ switch (status) {
10812
+ case "unknown":
10813
+ return "File does not exist";
10814
+ case "critical":
10815
+ return "Has error-severity policy issues";
10816
+ case "warning":
10817
+ return cell.pendingCount > 0 ? `${cell.pendingCount} pending key(s) awaiting values` : "Has warning-severity policy issues";
10818
+ case "healthy":
10819
+ return `${cell.keyCount} key(s), no issues`;
10820
+ }
10821
+ }
10822
+ buildDrift(report) {
10823
+ const namespaces = [...new Set(report.matrix.map((c) => c.namespace))];
10824
+ const driftIssues = report.policy.issues.filter((i) => i.category === "drift");
10825
+ return namespaces.map((namespace) => {
10826
+ const nsIssues = driftIssues.filter((i) => i.namespace === namespace);
10827
+ const totalDrift = nsIssues.reduce((sum, i) => sum + (i.driftCount ?? 1), 0);
10828
+ return {
10829
+ namespace,
10830
+ isDrifted: totalDrift > 0,
10831
+ driftCount: totalDrift
10832
+ };
10833
+ });
10834
+ }
10835
+ buildPolicyResults(issues) {
10836
+ return issues.map((issue) => ({
10837
+ ruleId: `${issue.category}/${issue.severity}`,
10838
+ ruleName: issue.category,
10839
+ passed: issue.severity !== "error",
10840
+ severity: issue.severity,
10841
+ message: issue.message,
10842
+ ...issue.namespace || issue.environment ? {
10843
+ scope: {
10844
+ ...issue.namespace ? { namespace: issue.namespace } : {},
10845
+ ...issue.environment ? { environment: issue.environment } : {}
10846
+ }
10847
+ } : {}
10848
+ }));
10849
+ }
10850
+ };
10851
+
10852
+ // src/report/cloud-client.ts
10853
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
10854
+ var CloudClient = class {
10855
+ retryDelayMs;
10856
+ constructor(options) {
10857
+ this.retryDelayMs = options?.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
10858
+ }
10859
+ async fetchIntegration(apiUrl, apiKey, integrationId) {
10860
+ const url = `${apiUrl}/api/v1/integrations/${encodeURIComponent(integrationId)}`;
10861
+ return this.request("GET", url, apiKey);
10862
+ }
10863
+ async submitReport(apiUrl, apiKey, report) {
10864
+ const url = `${apiUrl}/api/v1/reports`;
10865
+ return this.request("POST", url, apiKey, report);
10866
+ }
10867
+ async submitBatchReports(apiUrl, apiKey, batch) {
10868
+ const url = `${apiUrl}/api/v1/reports/batch`;
10869
+ return this.request("POST", url, apiKey, batch);
10870
+ }
10871
+ async request(method, url, apiKey, body) {
10872
+ const headers = {
10873
+ Authorization: `Bearer ${apiKey}`,
10874
+ "Content-Type": "application/json"
10875
+ };
10876
+ const init = {
10877
+ method,
10878
+ headers,
10879
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
10880
+ };
10881
+ let response;
10882
+ try {
10883
+ response = await fetch(url, init);
10884
+ } catch {
10885
+ await this.delay(this.retryDelayMs);
10886
+ try {
10887
+ response = await fetch(url, init);
10888
+ } catch (retryErr) {
10889
+ throw new CloudApiError(
10890
+ `Network error contacting Clef Pro: ${retryErr.message}`,
10891
+ 0,
10892
+ "Check your network connection and CLEF_API_URL."
10893
+ );
10894
+ }
10895
+ }
10896
+ if (response.ok) {
10897
+ return await response.json();
10898
+ }
10899
+ if (response.status >= 500 && response.status < 600) {
10900
+ await this.delay(this.retryDelayMs);
10901
+ const retryResponse = await fetch(url, init);
10902
+ if (retryResponse.ok) {
10903
+ return await retryResponse.json();
10904
+ }
10905
+ throw this.buildError(retryResponse);
10906
+ }
10907
+ throw this.buildError(response);
10908
+ }
10909
+ buildError(response) {
10910
+ const hint = response.status === 401 || response.status === 403 ? "Check your API token (--api-token or CLEF_API_TOKEN)." : response.status === 404 ? "Check your cloud.integrationId in clef.yaml." : void 0;
10911
+ return new CloudApiError(
10912
+ `Clef Pro API returned ${response.status} ${response.statusText}`,
10913
+ response.status,
10914
+ hint
10915
+ );
10916
+ }
10917
+ delay(ms) {
10918
+ return new Promise((resolve) => setTimeout(resolve, ms));
10919
+ }
10920
+ };
10921
+
10922
+ // src/report/ci-context.ts
10923
+ function collectCIContext() {
10924
+ const env = process.env;
10925
+ if (env.GITHUB_ACTIONS) {
10926
+ const serverUrl = env.GITHUB_SERVER_URL ?? "https://github.com";
10927
+ const repo = env.GITHUB_REPOSITORY ?? "";
10928
+ const runId = env.GITHUB_RUN_ID ?? "";
10929
+ const pipelineUrl = repo && runId ? `${serverUrl}/${repo}/actions/runs/${runId}` : void 0;
10930
+ return {
10931
+ provider: "github-actions",
10932
+ pipelineUrl,
10933
+ trigger: env.GITHUB_EVENT_NAME
10934
+ };
10935
+ }
10936
+ if (env.GITLAB_CI) {
10937
+ return {
10938
+ provider: "gitlab-ci",
10939
+ pipelineUrl: env.CI_PIPELINE_URL,
10940
+ trigger: env.CI_PIPELINE_SOURCE
10941
+ };
10942
+ }
10943
+ if (env.CIRCLECI) {
10944
+ return {
10945
+ provider: "circleci",
10946
+ pipelineUrl: env.CIRCLE_BUILD_URL
10947
+ };
10948
+ }
10949
+ if (env.CI) {
10950
+ return {
10951
+ provider: "unknown"
10952
+ };
10953
+ }
10954
+ return void 0;
10955
+ }
10956
+
10572
10957
  // src/merge/driver.ts
10573
10958
  var SopsMergeDriver = class {
10574
10959
  constructor(sopsClient) {
@@ -10654,10 +11039,10 @@ var SopsMergeDriver = class {
10654
11039
  };
10655
11040
 
10656
11041
  // src/service-identity/manager.ts
10657
- var fs14 = __toESM(require("fs"));
11042
+ var fs15 = __toESM(require("fs"));
10658
11043
  var os = __toESM(require("os"));
10659
- var path16 = __toESM(require("path"));
10660
- var YAML9 = __toESM(require("yaml"));
11044
+ var path17 = __toESM(require("path"));
11045
+ var YAML10 = __toESM(require("yaml"));
10661
11046
  var PartialRotationError = class extends Error {
10662
11047
  constructor(message, rotatedKeys) {
10663
11048
  super(message);
@@ -10671,12 +11056,15 @@ var ServiceIdentityManager = class {
10671
11056
  this.matrixManager = matrixManager;
10672
11057
  }
10673
11058
  /**
10674
- * Create a new service identity with per-environment age key pairs.
10675
- * Generates keys, updates the manifest, and registers public keys as SOPS recipients.
11059
+ * Create a new service identity with per-environment age key pairs or KMS envelope config.
11060
+ * For age-only: generates keys, updates the manifest, and registers public keys as SOPS recipients.
11061
+ * For KMS: stores KMS config in manifest, no age keys generated.
10676
11062
  *
10677
- * @returns The created identity definition and the per-environment private keys (printed once).
11063
+ * @param kmsEnvConfigs - Optional per-environment KMS config. When provided, those envs use
11064
+ * KMS envelope encryption instead of generating age keys.
11065
+ * @returns The created identity definition and the per-environment private keys (empty for KMS envs).
10678
11066
  */
10679
- async create(name, namespaces, description, manifest, repoRoot) {
11067
+ async create(name, namespaces, description, manifest, repoRoot, kmsEnvConfigs) {
10680
11068
  if (manifest.service_identities?.some((si) => si.name === name)) {
10681
11069
  throw new Error(`Service identity '${name}' already exists.`);
10682
11070
  }
@@ -10689,9 +11077,14 @@ var ServiceIdentityManager = class {
10689
11077
  const environments = {};
10690
11078
  const privateKeys = {};
10691
11079
  for (const env of manifest.environments) {
10692
- const identity = await generateAgeIdentity();
10693
- environments[env.name] = { recipient: identity.publicKey };
10694
- privateKeys[env.name] = identity.privateKey;
11080
+ const kmsConfig = kmsEnvConfigs?.[env.name];
11081
+ if (kmsConfig) {
11082
+ environments[env.name] = { kms: kmsConfig };
11083
+ } else {
11084
+ const identity = await generateAgeIdentity();
11085
+ environments[env.name] = { recipient: identity.publicKey };
11086
+ privateKeys[env.name] = identity.privateKey;
11087
+ }
10695
11088
  }
10696
11089
  const definition = {
10697
11090
  name,
@@ -10700,9 +11093,9 @@ var ServiceIdentityManager = class {
10700
11093
  environments
10701
11094
  };
10702
11095
  await this.registerRecipients(definition, manifest, repoRoot);
10703
- const manifestPath = path16.join(repoRoot, CLEF_MANIFEST_FILENAME);
10704
- const raw = fs14.readFileSync(manifestPath, "utf-8");
10705
- const doc = YAML9.parse(raw);
11096
+ const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11097
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
11098
+ const doc = YAML10.parse(raw);
10706
11099
  if (!Array.isArray(doc.service_identities)) {
10707
11100
  doc.service_identities = [];
10708
11101
  }
@@ -10712,9 +11105,16 @@ var ServiceIdentityManager = class {
10712
11105
  namespaces,
10713
11106
  environments
10714
11107
  });
10715
- const tmpCreate = path16.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
10716
- fs14.writeFileSync(tmpCreate, YAML9.stringify(doc), "utf-8");
10717
- fs14.renameSync(tmpCreate, manifestPath);
11108
+ const tmpCreate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11109
+ try {
11110
+ fs15.writeFileSync(tmpCreate, YAML10.stringify(doc), "utf-8");
11111
+ fs15.renameSync(tmpCreate, manifestPath);
11112
+ } finally {
11113
+ try {
11114
+ fs15.unlinkSync(tmpCreate);
11115
+ } catch {
11116
+ }
11117
+ }
10718
11118
  return { identity: definition, privateKeys };
10719
11119
  }
10720
11120
  /**
@@ -10729,6 +11129,55 @@ var ServiceIdentityManager = class {
10729
11129
  get(manifest, name) {
10730
11130
  return manifest.service_identities?.find((si) => si.name === name);
10731
11131
  }
11132
+ /**
11133
+ * Update environment backends on an existing service identity.
11134
+ * Switches age → KMS (removes old recipient) or updates KMS config.
11135
+ * Returns new private keys for any environments switched from KMS → age.
11136
+ */
11137
+ async updateEnvironments(name, kmsEnvConfigs, manifest, repoRoot) {
11138
+ const identity = this.get(manifest, name);
11139
+ if (!identity) {
11140
+ throw new Error(`Service identity '${name}' not found.`);
11141
+ }
11142
+ const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11143
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
11144
+ const doc = YAML10.parse(raw);
11145
+ const identities = doc.service_identities;
11146
+ const siDoc = identities.find((si) => si.name === name);
11147
+ const envs = siDoc.environments;
11148
+ const cells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists);
11149
+ const privateKeys = {};
11150
+ for (const [envName, kmsConfig] of Object.entries(kmsEnvConfigs)) {
11151
+ const oldConfig = identity.environments[envName];
11152
+ if (!oldConfig) {
11153
+ throw new Error(`Environment '${envName}' not found on identity '${name}'.`);
11154
+ }
11155
+ if (oldConfig.recipient) {
11156
+ const scopedCells = cells.filter(
11157
+ (c) => identity.namespaces.includes(c.namespace) && c.environment === envName
11158
+ );
11159
+ for (const cell of scopedCells) {
11160
+ try {
11161
+ await this.encryption.removeRecipient(cell.filePath, oldConfig.recipient);
11162
+ } catch {
11163
+ }
11164
+ }
11165
+ }
11166
+ envs[envName] = { kms: kmsConfig };
11167
+ identity.environments[envName] = { kms: kmsConfig };
11168
+ }
11169
+ const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11170
+ try {
11171
+ fs15.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
11172
+ fs15.renameSync(tmp, manifestPath);
11173
+ } finally {
11174
+ try {
11175
+ fs15.unlinkSync(tmp);
11176
+ } catch {
11177
+ }
11178
+ }
11179
+ return { privateKeys };
11180
+ }
10732
11181
  /**
10733
11182
  * Register a service identity's public keys as SOPS recipients on scoped matrix files.
10734
11183
  */
@@ -10738,9 +11187,15 @@ var ServiceIdentityManager = class {
10738
11187
  if (!identity.namespaces.includes(cell.namespace)) continue;
10739
11188
  const envConfig = identity.environments[cell.environment];
10740
11189
  if (!envConfig) continue;
11190
+ if (isKmsEnvelope(envConfig)) continue;
11191
+ if (!envConfig.recipient) continue;
10741
11192
  try {
10742
11193
  await this.encryption.addRecipient(cell.filePath, envConfig.recipient);
10743
- } catch {
11194
+ } catch (err) {
11195
+ const message = err instanceof Error ? err.message : String(err);
11196
+ if (!message.includes("already")) {
11197
+ throw err;
11198
+ }
10744
11199
  }
10745
11200
  }
10746
11201
  }
@@ -10753,9 +11208,9 @@ var ServiceIdentityManager = class {
10753
11208
  if (!identity) {
10754
11209
  throw new Error(`Service identity '${name}' not found.`);
10755
11210
  }
10756
- const manifestPath = path16.join(repoRoot, CLEF_MANIFEST_FILENAME);
10757
- const raw = fs14.readFileSync(manifestPath, "utf-8");
10758
- const doc = YAML9.parse(raw);
11211
+ const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11212
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
11213
+ const doc = YAML10.parse(raw);
10759
11214
  const identities = doc.service_identities;
10760
11215
  const siDoc = identities.find((si) => si.name === name);
10761
11216
  const envs = siDoc.environments;
@@ -10764,7 +11219,12 @@ var ServiceIdentityManager = class {
10764
11219
  const cells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists);
10765
11220
  try {
10766
11221
  for (const envName of envsToRotate) {
10767
- const oldRecipient = identity.environments[envName]?.recipient;
11222
+ const envConfig = identity.environments[envName];
11223
+ if (!envConfig) {
11224
+ throw new Error(`Environment '${envName}' not found on identity '${name}'.`);
11225
+ }
11226
+ if (isKmsEnvelope(envConfig)) continue;
11227
+ const oldRecipient = envConfig.recipient;
10768
11228
  if (!oldRecipient) {
10769
11229
  throw new Error(`Environment '${envName}' not found on identity '${name}'.`);
10770
11230
  }
@@ -10803,9 +11263,16 @@ var ServiceIdentityManager = class {
10803
11263
  }
10804
11264
  throw err;
10805
11265
  }
10806
- const tmpRotate = path16.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
10807
- fs14.writeFileSync(tmpRotate, YAML9.stringify(doc), "utf-8");
10808
- fs14.renameSync(tmpRotate, manifestPath);
11266
+ const tmpRotate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11267
+ try {
11268
+ fs15.writeFileSync(tmpRotate, YAML10.stringify(doc), "utf-8");
11269
+ fs15.renameSync(tmpRotate, manifestPath);
11270
+ } finally {
11271
+ try {
11272
+ fs15.unlinkSync(tmpRotate);
11273
+ } catch {
11274
+ }
11275
+ }
10809
11276
  return newPrivateKeys;
10810
11277
  }
10811
11278
  /**
@@ -10842,6 +11309,8 @@ var ServiceIdentityManager = class {
10842
11309
  for (const cell of cells) {
10843
11310
  const envConfig = si.environments[cell.environment];
10844
11311
  if (!envConfig) continue;
11312
+ if (isKmsEnvelope(envConfig)) continue;
11313
+ if (!envConfig.recipient) continue;
10845
11314
  if (si.namespaces.includes(cell.namespace)) {
10846
11315
  try {
10847
11316
  const metadata = await this.encryption.getMetadata(cell.filePath);
@@ -10915,18 +11384,20 @@ async function resolveIdentitySecrets(identityName, environment, manifest, repoR
10915
11384
  return {
10916
11385
  values: allValues,
10917
11386
  identity,
10918
- recipient: envConfig.recipient
11387
+ recipient: envConfig.recipient,
11388
+ envConfig
10919
11389
  };
10920
11390
  }
10921
11391
 
10922
11392
  // src/artifact/packer.ts
10923
- var fs15 = __toESM(require("fs"));
10924
- var path17 = __toESM(require("path"));
11393
+ var fs16 = __toESM(require("fs"));
11394
+ var path18 = __toESM(require("path"));
10925
11395
  var crypto3 = __toESM(require("crypto"));
10926
11396
  var ArtifactPacker = class {
10927
- constructor(encryption, matrixManager) {
11397
+ constructor(encryption, matrixManager, kms) {
10928
11398
  this.encryption = encryption;
10929
11399
  this.matrixManager = matrixManager;
11400
+ this.kms = kms;
10930
11401
  }
10931
11402
  /**
10932
11403
  * Pack an artifact: decrypt scoped SOPS files, age-encrypt the merged
@@ -10943,38 +11414,82 @@ var ArtifactPacker = class {
10943
11414
  );
10944
11415
  const plaintext = JSON.stringify(resolved.values);
10945
11416
  let ciphertext;
10946
- try {
10947
- const { Encrypter } = await Promise.resolve().then(() => __toESM(require_age_encryption()));
10948
- const e = new Encrypter();
10949
- e.addRecipient(resolved.recipient);
10950
- ciphertext = await e.encrypt(plaintext);
10951
- } catch {
10952
- throw new Error("Failed to age-encrypt artifact. Check recipient key.");
11417
+ let artifact;
11418
+ if (isKmsEnvelope(resolved.envConfig)) {
11419
+ if (!this.kms) {
11420
+ throw new Error("KMS provider required for envelope encryption but none was provided.");
11421
+ }
11422
+ const { generateIdentity, identityToRecipient, Encrypter } = await Promise.resolve().then(() => __toESM(require_age_encryption()));
11423
+ const ephemeralPrivateKey = await generateIdentity();
11424
+ const ephemeralPublicKey = await identityToRecipient(ephemeralPrivateKey);
11425
+ try {
11426
+ const e = new Encrypter();
11427
+ e.addRecipient(ephemeralPublicKey);
11428
+ const encrypted = await e.encrypt(plaintext);
11429
+ ciphertext = typeof encrypted === "string" ? encrypted : Buffer.from(encrypted).toString("base64");
11430
+ } catch {
11431
+ throw new Error("Failed to age-encrypt artifact with ephemeral key.");
11432
+ }
11433
+ const kmsConfig = resolved.envConfig.kms;
11434
+ const wrapped = await this.kms.wrap(kmsConfig.keyId, Buffer.from(ephemeralPrivateKey));
11435
+ const revision = `${Date.now()}-${crypto3.randomBytes(4).toString("hex")}`;
11436
+ const ciphertextHash = crypto3.createHash("sha256").update(ciphertext).digest("hex");
11437
+ artifact = {
11438
+ version: 1,
11439
+ identity: config.identity,
11440
+ environment: config.environment,
11441
+ packedAt: (/* @__PURE__ */ new Date()).toISOString(),
11442
+ revision,
11443
+ ciphertextHash,
11444
+ ciphertext,
11445
+ keys: Object.keys(resolved.values),
11446
+ envelope: {
11447
+ provider: kmsConfig.provider,
11448
+ keyId: kmsConfig.keyId,
11449
+ wrappedKey: wrapped.wrappedKey.toString("base64"),
11450
+ algorithm: wrapped.algorithm
11451
+ }
11452
+ };
11453
+ } else {
11454
+ try {
11455
+ const { Encrypter } = await Promise.resolve().then(() => __toESM(require_age_encryption()));
11456
+ const e = new Encrypter();
11457
+ e.addRecipient(resolved.recipient);
11458
+ const encrypted = await e.encrypt(plaintext);
11459
+ ciphertext = typeof encrypted === "string" ? encrypted : Buffer.from(encrypted).toString("base64");
11460
+ } catch {
11461
+ throw new Error("Failed to age-encrypt artifact. Check recipient key.");
11462
+ }
11463
+ const revision = `${Date.now()}-${crypto3.randomBytes(4).toString("hex")}`;
11464
+ const ciphertextHash = crypto3.createHash("sha256").update(ciphertext).digest("hex");
11465
+ artifact = {
11466
+ version: 1,
11467
+ identity: config.identity,
11468
+ environment: config.environment,
11469
+ packedAt: (/* @__PURE__ */ new Date()).toISOString(),
11470
+ revision,
11471
+ ciphertextHash,
11472
+ ciphertext,
11473
+ keys: Object.keys(resolved.values)
11474
+ };
10953
11475
  }
10954
- const revision = Date.now().toString();
10955
- const ciphertextHash = crypto3.createHash("sha256").update(ciphertext).digest("hex");
10956
- const artifact = {
10957
- version: 1,
10958
- identity: config.identity,
10959
- environment: config.environment,
10960
- packedAt: (/* @__PURE__ */ new Date()).toISOString(),
10961
- revision,
10962
- ciphertextHash,
10963
- ciphertext,
10964
- keys: Object.keys(resolved.values)
10965
- };
10966
- const outputDir = path17.dirname(config.outputPath);
10967
- if (!fs15.existsSync(outputDir)) {
10968
- fs15.mkdirSync(outputDir, { recursive: true });
11476
+ const outputDir = path18.dirname(config.outputPath);
11477
+ if (!fs16.existsSync(outputDir)) {
11478
+ fs16.mkdirSync(outputDir, { recursive: true });
11479
+ }
11480
+ if (config.ttl && config.ttl > 0) {
11481
+ artifact.expiresAt = new Date(Date.now() + config.ttl * 1e3).toISOString();
10969
11482
  }
10970
11483
  const json = JSON.stringify(artifact, null, 2);
10971
- fs15.writeFileSync(config.outputPath, json, "utf-8");
11484
+ const tmpOutput = `${config.outputPath}.tmp.${process.pid}`;
11485
+ fs16.writeFileSync(tmpOutput, json, "utf-8");
11486
+ fs16.renameSync(tmpOutput, config.outputPath);
10972
11487
  return {
10973
11488
  outputPath: config.outputPath,
10974
11489
  namespaceCount: resolved.identity.namespaces.length,
10975
11490
  keyCount: Object.keys(resolved.values).length,
10976
11491
  artifactSize: Buffer.byteLength(json, "utf-8"),
10977
- revision
11492
+ revision: artifact.revision
10978
11493
  };
10979
11494
  }
10980
11495
  };
@@ -10986,6 +11501,8 @@ var ArtifactPacker = class {
10986
11501
  CLEF_REPORT_SCHEMA_VERSION,
10987
11502
  CLEF_SUPPORTED_EXTENSIONS,
10988
11503
  ClefError,
11504
+ CloudApiError,
11505
+ CloudClient,
10989
11506
  ConsumptionClient,
10990
11507
  DiffEngine,
10991
11508
  DriftDetector,
@@ -10997,10 +11514,12 @@ var ArtifactPacker = class {
10997
11514
  ManifestValidationError,
10998
11515
  MatrixManager,
10999
11516
  PartialRotationError,
11517
+ REQUESTS_FILENAME,
11000
11518
  REQUIREMENTS,
11001
11519
  RecipientManager,
11002
11520
  ReportGenerator,
11003
11521
  ReportSanitizer,
11522
+ ReportTransformer,
11004
11523
  ScanRunner,
11005
11524
  SchemaLoadError,
11006
11525
  SchemaValidator,
@@ -11015,17 +11534,21 @@ var ArtifactPacker = class {
11015
11534
  assertSops,
11016
11535
  checkAll,
11017
11536
  checkDependency,
11537
+ collectCIContext,
11018
11538
  deriveAgePublicKey,
11019
11539
  detectFormat,
11540
+ findRequest,
11020
11541
  formatAgeKeyFile,
11021
11542
  generateAgeIdentity,
11022
11543
  generateRandomValue,
11023
11544
  getPendingKeys,
11024
11545
  isHighEntropy,
11546
+ isKmsEnvelope,
11025
11547
  isPending,
11026
11548
  keyPreview,
11027
11549
  loadIgnoreRules,
11028
11550
  loadMetadata,
11551
+ loadRequests,
11029
11552
  markPending,
11030
11553
  markPendingWithRetry,
11031
11554
  markResolved,
@@ -11037,15 +11560,19 @@ var ArtifactPacker = class {
11037
11560
  parseJson,
11038
11561
  parseYaml,
11039
11562
  redactValue,
11563
+ removeAccessRequest,
11564
+ requestsFilePath,
11040
11565
  resetSopsResolution,
11041
11566
  resolveBackendConfig,
11042
11567
  resolveIdentitySecrets,
11043
11568
  resolveRecipientsForEnvironment,
11044
11569
  resolveSopsPath,
11045
11570
  saveMetadata,
11571
+ saveRequests,
11046
11572
  shannonEntropy,
11047
11573
  shouldIgnoreFile,
11048
11574
  shouldIgnoreMatch,
11575
+ upsertRequest,
11049
11576
  validateAgePublicKey
11050
11577
  });
11051
11578
  /*! Bundled license information: