vaultkeeper 0.7.0 → 0.7.1

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.cjs CHANGED
@@ -1391,13 +1391,19 @@ var DpapiBackend = class {
1391
1391
  const entryPath = getEntryPath2(storageDir, id);
1392
1392
  const script = [
1393
1393
  "Add-Type -AssemblyName System.Security",
1394
- `$bytes = [System.Text.Encoding]::UTF8.GetBytes(${JSON.stringify(secret)})`,
1394
+ "$b64 = [Console]::In.ReadToEnd()",
1395
+ "$bytes = [System.Convert]::FromBase64String($b64.Trim())",
1395
1396
  "$entropy = $null",
1396
1397
  "$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
1397
1398
  "$encrypted = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $entropy, $scope)",
1398
1399
  `[System.IO.File]::WriteAllBytes(${JSON.stringify(entryPath)}, $encrypted)`
1399
1400
  ].join("; ");
1400
- await execCommand("powershell", ["-NoProfile", "-Command", script]);
1401
+ const secretBuf = Buffer.from(secret, "utf8");
1402
+ const stdinPayload = secretBuf.toString("base64");
1403
+ secretBuf.fill(0);
1404
+ await execCommand("powershell", ["-NoProfile", "-Command", script], {
1405
+ stdin: stdinPayload
1406
+ });
1401
1407
  }
1402
1408
  async retrieve(id) {
1403
1409
  const storageDir = this.#storageDir;
@@ -2950,11 +2956,16 @@ async function createToken(key, claims, options) {
2950
2956
  function isObject2(value) {
2951
2957
  return typeof value === "object" && value !== null && !Array.isArray(value);
2952
2958
  }
2959
+ function isLeasePresence(value) {
2960
+ if (!isObject2(value)) return false;
2961
+ const { op, at, method, backend } = value;
2962
+ return typeof op === "string" && typeof at === "number" && typeof method === "string" && typeof backend === "string";
2963
+ }
2953
2964
  function parseVaultClaims(raw) {
2954
2965
  if (!isObject2(raw)) {
2955
2966
  return void 0;
2956
2967
  }
2957
- const { jti, exp, iat, sub, exe, use, tid, bkd, val, ref } = raw;
2968
+ const { jti, exp, iat, sub, exe, use, tid, bkd, val, ref, kty, kid, kgen, pres } = raw;
2958
2969
  if (typeof jti !== "string") return void 0;
2959
2970
  if (typeof exp !== "number") return void 0;
2960
2971
  if (typeof iat !== "number") return void 0;
@@ -2962,10 +2973,29 @@ function parseVaultClaims(raw) {
2962
2973
  if (typeof exe !== "string") return void 0;
2963
2974
  if (use !== null && typeof use !== "number") return void 0;
2964
2975
  if (tid !== 1 && tid !== 2 && tid !== 3) return void 0;
2965
- if (typeof bkd !== "string") return void 0;
2966
- if (typeof val !== "string") return void 0;
2976
+ if (bkd !== void 0 && typeof bkd !== "string") return void 0;
2977
+ if (val !== void 0 && typeof val !== "string") return void 0;
2967
2978
  if (typeof ref !== "string") return void 0;
2968
- return { jti, exp, iat, sub, exe, use: use ?? null, tid, bkd, val, ref };
2979
+ if (kty !== void 0 && kty !== "secret" && kty !== "signing-key") return void 0;
2980
+ if (kid !== void 0 && typeof kid !== "string") return void 0;
2981
+ if (kgen !== void 0 && typeof kgen !== "number") return void 0;
2982
+ if (pres !== void 0 && !isLeasePresence(pres)) return void 0;
2983
+ return {
2984
+ jti,
2985
+ exp,
2986
+ iat,
2987
+ sub,
2988
+ exe,
2989
+ use: use ?? null,
2990
+ tid,
2991
+ bkd,
2992
+ val,
2993
+ ref,
2994
+ kty,
2995
+ kid,
2996
+ kgen,
2997
+ pres
2998
+ };
2969
2999
  }
2970
3000
  async function decryptToken(key, jwe) {
2971
3001
  let plaintext;
@@ -3046,15 +3076,44 @@ function validateClaims(claims, usedCount = 0) {
3046
3076
  if (claims.exe.trim() === "") {
3047
3077
  throw new VaultError("Invalid token: exe must not be empty");
3048
3078
  }
3049
- if (claims.bkd.trim() === "") {
3050
- throw new VaultError("Invalid token: bkd must not be empty");
3051
- }
3052
- if (claims.val.trim() === "") {
3053
- throw new VaultError("Invalid token: val must not be empty");
3054
- }
3055
3079
  if (claims.ref.trim() === "") {
3056
3080
  throw new VaultError("Invalid token: ref must not be empty");
3057
3081
  }
3082
+ switch (claims.kty) {
3083
+ case "signing-key": {
3084
+ if (claims.val !== void 0) {
3085
+ throw new VaultError("Invalid token: signing lease must not carry a val");
3086
+ }
3087
+ if (claims.kid === void 0 || claims.kid.trim() === "") {
3088
+ throw new VaultError("Invalid token: kid must not be empty");
3089
+ }
3090
+ if (claims.kgen === void 0) {
3091
+ throw new VaultError("Invalid token: kgen is required for a signing lease");
3092
+ }
3093
+ if (!Number.isSafeInteger(claims.kgen) || claims.kgen < 0) {
3094
+ throw new VaultError("Invalid token: kgen must be a non-negative integer");
3095
+ }
3096
+ break;
3097
+ }
3098
+ case "secret":
3099
+ case void 0: {
3100
+ if (claims.bkd === void 0 || claims.bkd.trim() === "") {
3101
+ throw new VaultError("Invalid token: bkd must not be empty");
3102
+ }
3103
+ if (claims.val === void 0 || claims.val.trim() === "") {
3104
+ throw new VaultError("Invalid token: val must not be empty");
3105
+ }
3106
+ if (claims.kid !== void 0 || claims.kgen !== void 0 || claims.pres !== void 0) {
3107
+ throw new VaultError(
3108
+ "Invalid token: secret claim must not carry signing-lease fields (kid/kgen/pres)"
3109
+ );
3110
+ }
3111
+ break;
3112
+ }
3113
+ default: {
3114
+ throw new VaultError(`Invalid token: unrecognized claim kind kty=${String(claims.kty)}`);
3115
+ }
3116
+ }
3058
3117
  if (claims.iat > claims.exp) {
3059
3118
  throw new VaultError("Invalid token: iat must not be after exp");
3060
3119
  }
@@ -4069,6 +4128,16 @@ var VaultKeeper = class _VaultKeeper {
4069
4128
  "This capability token authorizes a signing key, not a secret \u2014 it cannot be read with getSecret(). Use sign() instead."
4070
4129
  );
4071
4130
  }
4131
+ if (claims.kty === "signing-key") {
4132
+ throw new AuthorizationDeniedError(
4133
+ "This capability token authorizes a signing key, not a secret \u2014 it cannot be read with getSecret(). Use sign() instead."
4134
+ );
4135
+ }
4136
+ if (claims.val === void 0) {
4137
+ throw new AuthorizationDeniedError(
4138
+ "This capability token does not carry a secret value \u2014 it is malformed or was not minted as a secret token."
4139
+ );
4140
+ }
4072
4141
  return createSecretAccessor(claims.val);
4073
4142
  }
4074
4143
  /**
@@ -4411,7 +4480,7 @@ var VaultKeeper = class _VaultKeeper {
4411
4480
  // ---------------------------------------------------------------------------
4412
4481
  static #resolveSecrets(token) {
4413
4482
  if (token instanceof CapabilityToken) {
4414
- return _VaultKeeper.#requireSecretClaims(token).val;
4483
+ return _VaultKeeper.#requireSecretValue(token);
4415
4484
  }
4416
4485
  const result = {};
4417
4486
  for (const [name, t] of Object.entries(token)) {
@@ -4420,7 +4489,7 @@ var VaultKeeper = class _VaultKeeper {
4420
4489
  `Invalid capability token for secret "${name}" \u2014 expected a CapabilityToken from authorize()`
4421
4490
  );
4422
4491
  }
4423
- result[name] = _VaultKeeper.#requireSecretClaims(t).val;
4492
+ result[name] = _VaultKeeper.#requireSecretValue(t);
4424
4493
  }
4425
4494
  return result;
4426
4495
  }
@@ -4428,7 +4497,10 @@ var VaultKeeper = class _VaultKeeper {
4428
4497
  * Resolve a token to its secret claims, rejecting a signing-key token.
4429
4498
  *
4430
4499
  * Defense in depth: a signing-key capability must never be injectable as a
4431
- * secret through `fetch()`/`exec()`.
4500
+ * secret through `fetch()`/`exec()`. This rejects both the in-memory
4501
+ * signing-key token (`isSigningClaims`) and a JWE-based signing-key lease
4502
+ * (`kty: 'signing-key'`) — the two capability shapes are discriminated
4503
+ * differently but neither ever carries a secret value.
4432
4504
  */
4433
4505
  static #requireSecretClaims(token) {
4434
4506
  const claims = validateCapabilityToken(token);
@@ -4437,8 +4509,21 @@ var VaultKeeper = class _VaultKeeper {
4437
4509
  "This capability token authorizes a signing key, not a secret \u2014 it cannot be injected into fetch() or exec()."
4438
4510
  );
4439
4511
  }
4512
+ if (claims.kty === "signing-key") {
4513
+ throw new AuthorizationDeniedError(
4514
+ "This capability token authorizes a signing-key lease, not a secret \u2014 it cannot be injected into fetch() or exec()."
4515
+ );
4516
+ }
4440
4517
  return claims;
4441
4518
  }
4519
+ /** Resolve a token to its secret value, rejecting a claims shape with no `val`. */
4520
+ static #requireSecretValue(token) {
4521
+ const claims = _VaultKeeper.#requireSecretClaims(token);
4522
+ if (claims.val === void 0 || claims.val.trim() === "") {
4523
+ throw new AuthorizationDeniedError("This capability token does not authorize a secret value.");
4524
+ }
4525
+ return claims.val;
4526
+ }
4442
4527
  /**
4443
4528
  * Validate a caller-supplied resource name. `kind` names the resource in the
4444
4529
  * error so a signing-key caller is not told about a "secret".