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.js CHANGED
@@ -1366,13 +1366,19 @@ var DpapiBackend = class {
1366
1366
  const entryPath = getEntryPath2(storageDir, id);
1367
1367
  const script = [
1368
1368
  "Add-Type -AssemblyName System.Security",
1369
- `$bytes = [System.Text.Encoding]::UTF8.GetBytes(${JSON.stringify(secret)})`,
1369
+ "$b64 = [Console]::In.ReadToEnd()",
1370
+ "$bytes = [System.Convert]::FromBase64String($b64.Trim())",
1370
1371
  "$entropy = $null",
1371
1372
  "$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
1372
1373
  "$encrypted = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $entropy, $scope)",
1373
1374
  `[System.IO.File]::WriteAllBytes(${JSON.stringify(entryPath)}, $encrypted)`
1374
1375
  ].join("; ");
1375
- await execCommand("powershell", ["-NoProfile", "-Command", script]);
1376
+ const secretBuf = Buffer.from(secret, "utf8");
1377
+ const stdinPayload = secretBuf.toString("base64");
1378
+ secretBuf.fill(0);
1379
+ await execCommand("powershell", ["-NoProfile", "-Command", script], {
1380
+ stdin: stdinPayload
1381
+ });
1376
1382
  }
1377
1383
  async retrieve(id) {
1378
1384
  const storageDir = this.#storageDir;
@@ -2925,11 +2931,16 @@ async function createToken(key, claims, options) {
2925
2931
  function isObject2(value) {
2926
2932
  return typeof value === "object" && value !== null && !Array.isArray(value);
2927
2933
  }
2934
+ function isLeasePresence(value) {
2935
+ if (!isObject2(value)) return false;
2936
+ const { op, at, method, backend } = value;
2937
+ return typeof op === "string" && typeof at === "number" && typeof method === "string" && typeof backend === "string";
2938
+ }
2928
2939
  function parseVaultClaims(raw) {
2929
2940
  if (!isObject2(raw)) {
2930
2941
  return void 0;
2931
2942
  }
2932
- const { jti, exp, iat, sub, exe, use, tid, bkd, val, ref } = raw;
2943
+ const { jti, exp, iat, sub, exe, use, tid, bkd, val, ref, kty, kid, kgen, pres } = raw;
2933
2944
  if (typeof jti !== "string") return void 0;
2934
2945
  if (typeof exp !== "number") return void 0;
2935
2946
  if (typeof iat !== "number") return void 0;
@@ -2937,10 +2948,29 @@ function parseVaultClaims(raw) {
2937
2948
  if (typeof exe !== "string") return void 0;
2938
2949
  if (use !== null && typeof use !== "number") return void 0;
2939
2950
  if (tid !== 1 && tid !== 2 && tid !== 3) return void 0;
2940
- if (typeof bkd !== "string") return void 0;
2941
- if (typeof val !== "string") return void 0;
2951
+ if (bkd !== void 0 && typeof bkd !== "string") return void 0;
2952
+ if (val !== void 0 && typeof val !== "string") return void 0;
2942
2953
  if (typeof ref !== "string") return void 0;
2943
- return { jti, exp, iat, sub, exe, use: use ?? null, tid, bkd, val, ref };
2954
+ if (kty !== void 0 && kty !== "secret" && kty !== "signing-key") return void 0;
2955
+ if (kid !== void 0 && typeof kid !== "string") return void 0;
2956
+ if (kgen !== void 0 && typeof kgen !== "number") return void 0;
2957
+ if (pres !== void 0 && !isLeasePresence(pres)) return void 0;
2958
+ return {
2959
+ jti,
2960
+ exp,
2961
+ iat,
2962
+ sub,
2963
+ exe,
2964
+ use: use ?? null,
2965
+ tid,
2966
+ bkd,
2967
+ val,
2968
+ ref,
2969
+ kty,
2970
+ kid,
2971
+ kgen,
2972
+ pres
2973
+ };
2944
2974
  }
2945
2975
  async function decryptToken(key, jwe) {
2946
2976
  let plaintext;
@@ -3021,15 +3051,44 @@ function validateClaims(claims, usedCount = 0) {
3021
3051
  if (claims.exe.trim() === "") {
3022
3052
  throw new VaultError("Invalid token: exe must not be empty");
3023
3053
  }
3024
- if (claims.bkd.trim() === "") {
3025
- throw new VaultError("Invalid token: bkd must not be empty");
3026
- }
3027
- if (claims.val.trim() === "") {
3028
- throw new VaultError("Invalid token: val must not be empty");
3029
- }
3030
3054
  if (claims.ref.trim() === "") {
3031
3055
  throw new VaultError("Invalid token: ref must not be empty");
3032
3056
  }
3057
+ switch (claims.kty) {
3058
+ case "signing-key": {
3059
+ if (claims.val !== void 0) {
3060
+ throw new VaultError("Invalid token: signing lease must not carry a val");
3061
+ }
3062
+ if (claims.kid === void 0 || claims.kid.trim() === "") {
3063
+ throw new VaultError("Invalid token: kid must not be empty");
3064
+ }
3065
+ if (claims.kgen === void 0) {
3066
+ throw new VaultError("Invalid token: kgen is required for a signing lease");
3067
+ }
3068
+ if (!Number.isSafeInteger(claims.kgen) || claims.kgen < 0) {
3069
+ throw new VaultError("Invalid token: kgen must be a non-negative integer");
3070
+ }
3071
+ break;
3072
+ }
3073
+ case "secret":
3074
+ case void 0: {
3075
+ if (claims.bkd === void 0 || claims.bkd.trim() === "") {
3076
+ throw new VaultError("Invalid token: bkd must not be empty");
3077
+ }
3078
+ if (claims.val === void 0 || claims.val.trim() === "") {
3079
+ throw new VaultError("Invalid token: val must not be empty");
3080
+ }
3081
+ if (claims.kid !== void 0 || claims.kgen !== void 0 || claims.pres !== void 0) {
3082
+ throw new VaultError(
3083
+ "Invalid token: secret claim must not carry signing-lease fields (kid/kgen/pres)"
3084
+ );
3085
+ }
3086
+ break;
3087
+ }
3088
+ default: {
3089
+ throw new VaultError(`Invalid token: unrecognized claim kind kty=${String(claims.kty)}`);
3090
+ }
3091
+ }
3033
3092
  if (claims.iat > claims.exp) {
3034
3093
  throw new VaultError("Invalid token: iat must not be after exp");
3035
3094
  }
@@ -4044,6 +4103,16 @@ var VaultKeeper = class _VaultKeeper {
4044
4103
  "This capability token authorizes a signing key, not a secret \u2014 it cannot be read with getSecret(). Use sign() instead."
4045
4104
  );
4046
4105
  }
4106
+ if (claims.kty === "signing-key") {
4107
+ throw new AuthorizationDeniedError(
4108
+ "This capability token authorizes a signing key, not a secret \u2014 it cannot be read with getSecret(). Use sign() instead."
4109
+ );
4110
+ }
4111
+ if (claims.val === void 0) {
4112
+ throw new AuthorizationDeniedError(
4113
+ "This capability token does not carry a secret value \u2014 it is malformed or was not minted as a secret token."
4114
+ );
4115
+ }
4047
4116
  return createSecretAccessor(claims.val);
4048
4117
  }
4049
4118
  /**
@@ -4386,7 +4455,7 @@ var VaultKeeper = class _VaultKeeper {
4386
4455
  // ---------------------------------------------------------------------------
4387
4456
  static #resolveSecrets(token) {
4388
4457
  if (token instanceof CapabilityToken) {
4389
- return _VaultKeeper.#requireSecretClaims(token).val;
4458
+ return _VaultKeeper.#requireSecretValue(token);
4390
4459
  }
4391
4460
  const result = {};
4392
4461
  for (const [name, t] of Object.entries(token)) {
@@ -4395,7 +4464,7 @@ var VaultKeeper = class _VaultKeeper {
4395
4464
  `Invalid capability token for secret "${name}" \u2014 expected a CapabilityToken from authorize()`
4396
4465
  );
4397
4466
  }
4398
- result[name] = _VaultKeeper.#requireSecretClaims(t).val;
4467
+ result[name] = _VaultKeeper.#requireSecretValue(t);
4399
4468
  }
4400
4469
  return result;
4401
4470
  }
@@ -4403,7 +4472,10 @@ var VaultKeeper = class _VaultKeeper {
4403
4472
  * Resolve a token to its secret claims, rejecting a signing-key token.
4404
4473
  *
4405
4474
  * Defense in depth: a signing-key capability must never be injectable as a
4406
- * secret through `fetch()`/`exec()`.
4475
+ * secret through `fetch()`/`exec()`. This rejects both the in-memory
4476
+ * signing-key token (`isSigningClaims`) and a JWE-based signing-key lease
4477
+ * (`kty: 'signing-key'`) — the two capability shapes are discriminated
4478
+ * differently but neither ever carries a secret value.
4407
4479
  */
4408
4480
  static #requireSecretClaims(token) {
4409
4481
  const claims = validateCapabilityToken(token);
@@ -4412,8 +4484,21 @@ var VaultKeeper = class _VaultKeeper {
4412
4484
  "This capability token authorizes a signing key, not a secret \u2014 it cannot be injected into fetch() or exec()."
4413
4485
  );
4414
4486
  }
4487
+ if (claims.kty === "signing-key") {
4488
+ throw new AuthorizationDeniedError(
4489
+ "This capability token authorizes a signing-key lease, not a secret \u2014 it cannot be injected into fetch() or exec()."
4490
+ );
4491
+ }
4415
4492
  return claims;
4416
4493
  }
4494
+ /** Resolve a token to its secret value, rejecting a claims shape with no `val`. */
4495
+ static #requireSecretValue(token) {
4496
+ const claims = _VaultKeeper.#requireSecretClaims(token);
4497
+ if (claims.val === void 0 || claims.val.trim() === "") {
4498
+ throw new AuthorizationDeniedError("This capability token does not authorize a secret value.");
4499
+ }
4500
+ return claims.val;
4501
+ }
4417
4502
  /**
4418
4503
  * Validate a caller-supplied resource name. `kind` names the resource in the
4419
4504
  * error so a signing-key caller is not told about a "secret".