@coffre/vault 0.1.2 → 0.1.3

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/README.md CHANGED
@@ -10,7 +10,7 @@ import { postgres, vault } from '@coffre/vault/cloudflare';
10
10
 
11
11
  export default vault((env) => ({
12
12
  database: postgres(env.VAULT_HYPERDRIVE), // coffre_vault_runtime, caching disabled
13
- kek: { id: env.KEK_ID, key: env.KEK }, // or awsKms({ keyArn, credentials }), with a signingKey
13
+ kek: { id: env.VAULT_KEY_ID, key: env.VAULT_KEY }, // or awsKms({ keyArn, credentials }), with a signingKey
14
14
  rootAdmins: env.ROOT_ADMINS.split(','),
15
15
  }));
16
16
  ```
@@ -1,4 +1,4 @@
1
- import { a as KekUnavailableError, c as awsKms, d as Kek, f as VaultConfig, i as KekProvider, l as checkpointMessage, n as AwsKmsOptions, o as SecretContext, p as derivedSigningKey, r as KekBadClaimError, s as WrappedDek, t as AwsCredentials, u as verifyCheckpoint } from "./index-CapZlhbz.js";
1
+ import { a as KekUnavailableError, c as awsKms, d as Kek, f as VaultConfig, i as KekProvider, l as checkpointMessage, n as AwsKmsOptions, o as SecretContext, p as derivedSigningKey, r as KekBadClaimError, s as WrappedDek, t as AwsCredentials, u as verifyCheckpoint } from "./index-DPxyTJNN.js";
2
2
  import { AdmitInput, RemoveInput, RewrapInput, SetAccessInput, UnwrapInput, Vault, VerifyLogInput, WrapInput } from "@coffre/core/vault";
3
3
  import { PostgresDatabase, PostgresDatabase as PostgresDatabase$1, postgres } from "@coffre/db/hyperdrive";
4
4
  import { WorkerEntrypoint } from "cloudflare:workers";
@@ -1,5 +1,5 @@
1
- import { a as verifyCheckpoint, i as checkpointMessage, n as KekUnavailableError, o as derivedSigningKey, r as awsKms, s as resolveVaultConfig, t as KekBadClaimError } from "./src-D1C9S6VS.js";
2
- import { n as prepareVault, t as openVault } from "./vault-D5ZahBN1.js";
1
+ import { a as verifyCheckpoint, i as checkpointMessage, n as KekUnavailableError, o as derivedSigningKey, r as awsKms, s as resolveVaultConfig, t as KekBadClaimError } from "./src-DHuqw-IB.js";
2
+ import { n as prepareVault, t as openVault } from "./vault-pexzy7Z-.js";
3
3
  import { createDatabase } from "@coffre/db";
4
4
  import { HyperdrivePool, postgres } from "@coffre/db/hyperdrive";
5
5
  import { WorkerEntrypoint } from "cloudflare:workers";
@@ -16,7 +16,7 @@ type Kek = {
16
16
  *
17
17
  * {
18
18
  * kek: awsKms({ keyArn: env.KMS_KEY_ARN, credentials: { … } }),
19
- * previousKeks: [{ id: 'kek-2025-01', key: env.KEK_2025_01 }],
19
+ * previousKeks: [{ id: 'vault-2025-01-10-k7q2xm', key: env.OLD_VAULT_KEY }],
20
20
  * rootAdmins: ['admin@acme.example'],
21
21
  * signingKey: env.SIGNING_KEY, // required with a key service; derived from a local KEK otherwise
22
22
  * }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { a as KekUnavailableError, c as awsKms, d as Kek, f as VaultConfig, i as KekProvider, l as checkpointMessage, n as AwsKmsOptions, o as SecretContext, p as derivedSigningKey, r as KekBadClaimError, s as WrappedDek, t as AwsCredentials, u as verifyCheckpoint } from "./index-CapZlhbz.js";
1
+ import { a as KekUnavailableError, c as awsKms, d as Kek, f as VaultConfig, i as KekProvider, l as checkpointMessage, n as AwsKmsOptions, o as SecretContext, p as derivedSigningKey, r as KekBadClaimError, s as WrappedDek, t as AwsCredentials, u as verifyCheckpoint } from "./index-DPxyTJNN.js";
2
2
  export type * from "@coffre/core/vault";
3
3
  export { type AwsCredentials, type AwsKmsOptions, type Kek, KekBadClaimError, type KekProvider, KekUnavailableError, type SecretContext, type VaultConfig, type WrappedDek, awsKms, checkpointMessage, derivedSigningKey, verifyCheckpoint };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as verifyCheckpoint, i as checkpointMessage, n as KekUnavailableError, o as derivedSigningKey, r as awsKms, t as KekBadClaimError } from "./src-D1C9S6VS.js";
1
+ import { a as verifyCheckpoint, i as checkpointMessage, n as KekUnavailableError, o as derivedSigningKey, r as awsKms, t as KekBadClaimError } from "./src-DHuqw-IB.js";
2
2
  export { KekBadClaimError, KekUnavailableError, awsKms, checkpointMessage, derivedSigningKey, verifyCheckpoint };
package/dist/node.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as KekUnavailableError, c as awsKms, d as Kek, f as VaultConfig, i as KekProvider, l as checkpointMessage, n as AwsKmsOptions, o as SecretContext, p as derivedSigningKey, r as KekBadClaimError, s as WrappedDek, t as AwsCredentials, u as verifyCheckpoint } from "./index-CapZlhbz.js";
1
+ import { a as KekUnavailableError, c as awsKms, d as Kek, f as VaultConfig, i as KekProvider, l as checkpointMessage, n as AwsKmsOptions, o as SecretContext, p as derivedSigningKey, r as KekBadClaimError, s as WrappedDek, t as AwsCredentials, u as verifyCheckpoint } from "./index-DPxyTJNN.js";
2
2
  import { Vault, Vault as Vault$1 } from "@coffre/core/vault";
3
3
  import { Database } from "@coffre/db";
4
4
  import "@coffre/core/audit";
package/dist/node.js CHANGED
@@ -1,5 +1,5 @@
1
- import { a as verifyCheckpoint, i as checkpointMessage, n as KekUnavailableError, o as derivedSigningKey, r as awsKms, s as resolveVaultConfig, t as KekBadClaimError } from "./src-D1C9S6VS.js";
2
- import { n as prepareVault, t as openVault } from "./vault-D5ZahBN1.js";
1
+ import { a as verifyCheckpoint, i as checkpointMessage, n as KekUnavailableError, o as derivedSigningKey, r as awsKms, s as resolveVaultConfig, t as KekBadClaimError } from "./src-DHuqw-IB.js";
2
+ import { n as prepareVault, t as openVault } from "./vault-pexzy7Z-.js";
3
3
  import { chmodSync, existsSync, lstatSync, rmSync } from "node:fs";
4
4
  import { Agent, createServer, request } from "node:http";
5
5
  import { openDatabase } from "@coffre/db/connect";
@@ -30,17 +30,17 @@ function derivedSigningKey(kek) {
30
30
  function kek(entry) {
31
31
  if (!("wrap" in entry)) {
32
32
  const { id, key } = entry;
33
- if (!KEK_ID.test(id)) throw new Error(`KEK id "${id}" must be 1-64 letters, digits, dots, dashes or underscores`);
34
- const raw = key32(key, `KEK ${id}`);
33
+ if (!KEK_ID.test(id)) throw new Error(`the vault key's ID "${id}" (kek.id) must be 1-64 letters, digits, dots, dashes or underscores`);
34
+ const raw = key32(key, `the vault key ${id}`);
35
35
  return {
36
36
  provider: new LocalKekProvider(raw, id),
37
37
  signingKey: derivedSigningKey(raw)
38
38
  };
39
39
  }
40
40
  const { provider, keyId, keyVersion } = entry;
41
- if (typeof provider !== "string" || !KEK_PROVIDER.test(provider)) throw new Error(`a KEK provider's name must be 1-32 lowercase letters, digits or dashes; got "${String(provider)}"`);
42
- if (typeof keyId !== "string" || !KEK_NAME.test(keyId) || typeof keyVersion !== "string" || !KEK_NAME.test(keyVersion)) throw new Error(`KEK ${provider}: keyId and keyVersion must be 1-255 visible ASCII characters`);
43
- if (typeof entry.wrap !== "function" || typeof entry.unwrap !== "function") throw new Error(`KEK ${provider}:${keyId} needs wrap() and unwrap()`);
41
+ if (typeof provider !== "string" || !KEK_PROVIDER.test(provider)) throw new Error(`a key service's name (kek.provider) must be 1-32 lowercase letters, digits or dashes; got "${String(provider)}"`);
42
+ if (typeof keyId !== "string" || !KEK_NAME.test(keyId) || typeof keyVersion !== "string" || !KEK_NAME.test(keyVersion)) throw new Error(`vault key ${provider}: keyId and keyVersion must be 1-255 visible ASCII characters`);
43
+ if (typeof entry.wrap !== "function" || typeof entry.unwrap !== "function") throw new Error(`vault key ${provider}:${keyId} needs wrap() and unwrap()`);
44
44
  return {
45
45
  provider: entry,
46
46
  signingKey: null
@@ -52,7 +52,7 @@ function resolveVaultConfig(config) {
52
52
  const [current, ...previous] = keks.map((entry) => entry.provider);
53
53
  const refs = [current, ...previous].map(({ provider, keyId }) => `${provider}:${keyId}`);
54
54
  const twice = refs.find((ref, i) => refs.indexOf(ref) !== i);
55
- if (twice !== void 0) throw new Error(`two KEKs share an id: ${twice}`);
55
+ if (twice !== void 0) throw new Error(`two vault keys share an id: ${twice}`);
56
56
  return {
57
57
  keks: new KekRegistry(current, previous),
58
58
  rootAdmins: checkRootAdmins(config.rootAdmins),
@@ -76,7 +76,7 @@ function signingKeys(given, keks) {
76
76
  else if (keks[0].signingKey !== null) signing = keks[0].signingKey;
77
77
  else {
78
78
  const { provider, keyId } = keks[0].provider;
79
- throw new Error(`signingKey is required with the ${provider} KEK ${keyId}: the vault derives its signing key only from a KEK it holds, and a key service never hands its key over. Set signingKey to 32 random bytes, base64 (openssl rand -base64 32), and keep it with the KEK.`);
79
+ throw new Error(`signingKey is required with the ${provider} vault key ${keyId}: the vault derives its signing key only from a vault key it holds, and a key service never hands its key over. Set signingKey to 32 random bytes, base64 (openssl rand -base64 32), and keep it with the vault key.`);
80
80
  }
81
81
  return [signing, ...derived].filter((key, i, all) => all.findIndex((other) => other.equals(key)) === i);
82
82
  }
@@ -377,7 +377,7 @@ function forward(entries, keys, current) {
377
377
  */
378
378
  function withCause(reason) {
379
379
  if (!/^written under vault:\S+, a key this verifier does not hold$/.test(reason)) return reason;
380
- return `${reason}: either it is forged, or the vault wrote it under another KEK or signing key, which must stay configured: a KEK that was replaced stays in previousKeks`;
380
+ return `${reason}: either it is forged, or the vault wrote it under another vault key or signing key, which must stay configured: a vault key that was replaced stays in the vault's config, in previousKeks`;
381
381
  }
382
382
  /**
383
383
  * Whether the log still holds `head` where it was: not rewritten, nor cut
@@ -890,7 +890,7 @@ const MESSAGES = {
890
890
  root_admin: "root admins are set in the vault configuration",
891
891
  invalid: "not something the rules allow",
892
892
  log_broken: "the vault log does not hold from the last checkpoint",
893
- wrong_kek: "this vault's KEK does not open the data it holds",
893
+ wrong_kek: "this vault's key does not open the data it holds",
894
894
  tampered: "this member's record failed the vault's integrity check"
895
895
  };
896
896
  /**
@@ -1140,7 +1140,7 @@ var VaultService = class {
1140
1140
  if (attempt < 3 && error instanceof Retry) continue;
1141
1141
  throw error;
1142
1142
  }
1143
- prepared.superseded = first === void 0 ? `the vault's entries are under ${newest.keyId}, a key this vault does not hold: it was given the wrong KEK or signing key, or a KEK it replaced is missing from previousKeks` : `the log moved on from this vault's key, ${current}, to ${newest.keyId}: a vault given a newer KEK replaced it, and a replaced key writes nothing more`;
1143
+ prepared.superseded = first === void 0 ? `the vault's entries are under ${newest.keyId}, a key this vault does not hold: it was given the wrong vault key or signing key, or a vault key it replaced is missing from previousKeks in its config` : `the log moved on from this vault's key, ${current}, to ${newest.keyId}: a vault given a newer vault key replaced it, and a replaced key writes nothing more`;
1144
1144
  return;
1145
1145
  }
1146
1146
  }
@@ -1189,7 +1189,7 @@ var VaultService = class {
1189
1189
  return appendEntries(tx, prepared.logKey, entries, async (locked) => {
1190
1190
  const rotation = await latestVaultEntry(locked, [KEY_ROTATE]);
1191
1191
  if (rotation === void 0 || prepared.logKeys.some((key) => key.keyId === rotation.keyId)) return;
1192
- prepared.superseded = `the log moved on to ${rotation.keyId}, a key this vault does not hold: a vault given a newer KEK replaced it, and a replaced key writes nothing more`;
1192
+ prepared.superseded = `the log moved on to ${rotation.keyId}, a key this vault does not hold: a vault given a newer vault key replaced it, and a replaced key writes nothing more`;
1193
1193
  throw new Error(prepared.superseded);
1194
1194
  });
1195
1195
  }
@@ -1956,7 +1956,7 @@ var VaultService = class {
1956
1956
  throw error;
1957
1957
  }
1958
1958
  };
1959
- const mismatch = `this vault's ${kek.provider} KEK ${kek.keyId} does not open the data it holds: it is not the key that wrapped it`;
1959
+ const mismatch = `the vault key ${kek.keyId}${kek.provider === "local" ? "" : ` (${kek.provider})`}, kek or previousKeks in the vault's config, is not the one that wrapped these values`;
1960
1960
  const check = checks.get(`${kek.provider}:${kek.keyId}`);
1961
1961
  if (check !== void 0) {
1962
1962
  if (!await opens(check, KEY_CHECK_CONTEXT, KEY_CHECK_VALUE)) return mismatch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/vault",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "coffre's vault: keys, grants and members, in the database the app uses, as a Cloudflare Worker or on Node.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,8 +36,8 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "drizzle-orm": "0.45.2",
39
- "@coffre/core": "0.1.2",
40
- "@coffre/db": "0.1.2"
39
+ "@coffre/core": "0.1.3",
40
+ "@coffre/db": "0.1.3"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "26.1.1",
package/src/cloudflare.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * export default vault((env: Env) => ({
10
10
  * database: postgres(env.HYPERDRIVE),
11
- * kek: { id: 'kek-1', key: env.KEK }, // or awsKms({ keyArn, credentials }), with a signingKey
11
+ * kek: { id: env.VAULT_KEY_ID, key: env.VAULT_KEY }, // or awsKms({ keyArn, credentials }), with a signingKey
12
12
  * rootAdmins: ['admin@acme.example'],
13
13
  * }));
14
14
  *
package/src/config.ts CHANGED
@@ -38,7 +38,7 @@ export type Kek = { id: string; key: string } | KekProvider;
38
38
  *
39
39
  * {
40
40
  * kek: awsKms({ keyArn: env.KMS_KEY_ARN, credentials: { … } }),
41
- * previousKeks: [{ id: 'kek-2025-01', key: env.KEK_2025_01 }],
41
+ * previousKeks: [{ id: 'vault-2025-01-10-k7q2xm', key: env.OLD_VAULT_KEY }],
42
42
  * rootAdmins: ['admin@acme.example'],
43
43
  * signingKey: env.SIGNING_KEY, // required with a key service; derived from a local KEK otherwise
44
44
  * }
@@ -87,19 +87,19 @@ export function derivedSigningKey(kek: Uint8Array): Buffer {
87
87
  function kek(entry: Kek): { provider: KekProvider; signingKey: Buffer | null } {
88
88
  if (!('wrap' in entry)) {
89
89
  const { id, key } = entry;
90
- if (!KEK_ID.test(id)) throw new Error(`KEK id "${id}" must be 1-64 letters, digits, dots, dashes or underscores`);
91
- const raw = key32(key, `KEK ${id}`);
90
+ if (!KEK_ID.test(id)) throw new Error(`the vault key's ID "${id}" (kek.id) must be 1-64 letters, digits, dots, dashes or underscores`);
91
+ const raw = key32(key, `the vault key ${id}`);
92
92
  return { provider: new LocalKekProvider(raw, id), signingKey: derivedSigningKey(raw) };
93
93
  }
94
94
  const { provider, keyId, keyVersion } = entry;
95
95
  if (typeof provider !== 'string' || !KEK_PROVIDER.test(provider)) {
96
- throw new Error(`a KEK provider's name must be 1-32 lowercase letters, digits or dashes; got "${String(provider)}"`);
96
+ throw new Error(`a key service's name (kek.provider) must be 1-32 lowercase letters, digits or dashes; got "${String(provider)}"`);
97
97
  }
98
98
  if (typeof keyId !== 'string' || !KEK_NAME.test(keyId) || typeof keyVersion !== 'string' || !KEK_NAME.test(keyVersion)) {
99
- throw new Error(`KEK ${provider}: keyId and keyVersion must be 1-255 visible ASCII characters`);
99
+ throw new Error(`vault key ${provider}: keyId and keyVersion must be 1-255 visible ASCII characters`);
100
100
  }
101
101
  if (typeof entry.wrap !== 'function' || typeof entry.unwrap !== 'function') {
102
- throw new Error(`KEK ${provider}:${keyId} needs wrap() and unwrap()`);
102
+ throw new Error(`vault key ${provider}:${keyId} needs wrap() and unwrap()`);
103
103
  }
104
104
  return { provider: entry, signingKey: null };
105
105
  }
@@ -110,7 +110,7 @@ export function resolveVaultConfig(config: VaultConfig): ResolvedVaultConfig {
110
110
  const [current, ...previous] = keks.map((entry) => entry.provider);
111
111
  const refs = [current, ...previous].map(({ provider, keyId }) => `${provider}:${keyId}`);
112
112
  const twice = refs.find((ref, i) => refs.indexOf(ref) !== i);
113
- if (twice !== undefined) throw new Error(`two KEKs share an id: ${twice}`);
113
+ if (twice !== undefined) throw new Error(`two vault keys share an id: ${twice}`);
114
114
  return {
115
115
  keks: new KekRegistry(current, previous),
116
116
  rootAdmins: checkRootAdmins(config.rootAdmins),
@@ -136,8 +136,8 @@ function signingKeys(given: string | undefined, keks: { provider: KekProvider; s
136
136
  else {
137
137
  const { provider, keyId } = keks[0].provider;
138
138
  throw new Error(
139
- `signingKey is required with the ${provider} KEK ${keyId}: the vault derives its signing key only from a KEK it holds, ` +
140
- 'and a key service never hands its key over. Set signingKey to 32 random bytes, base64 (openssl rand -base64 32), and keep it with the KEK.',
139
+ `signingKey is required with the ${provider} vault key ${keyId}: the vault derives its signing key only from a vault key it holds, ` +
140
+ 'and a key service never hands its key over. Set signingKey to 32 random bytes, base64 (openssl rand -base64 32), and keep it with the vault key.',
141
141
  );
142
142
  }
143
143
  return [signing, ...derived].filter((key, i, all) => all.findIndex((other) => other.equals(key)) === i);
package/src/log.ts CHANGED
@@ -132,7 +132,7 @@ function forward(
132
132
  */
133
133
  function withCause(reason: string): string {
134
134
  if (!/^written under vault:\S+, a key this verifier does not hold$/.test(reason)) return reason;
135
- return `${reason}: either it is forged, or the vault wrote it under another KEK or signing key, which must stay configured: a KEK that was replaced stays in previousKeks`;
135
+ return `${reason}: either it is forged, or the vault wrote it under another vault key or signing key, which must stay configured: a vault key that was replaced stays in the vault's config, in previousKeks`;
136
136
  }
137
137
 
138
138
  /**
package/src/vault.ts CHANGED
@@ -223,7 +223,7 @@ const MESSAGES: Record<RefusalCode, string> = {
223
223
  root_admin: 'root admins are set in the vault configuration',
224
224
  invalid: 'not something the rules allow',
225
225
  log_broken: 'the vault log does not hold from the last checkpoint',
226
- wrong_kek: "this vault's KEK does not open the data it holds",
226
+ wrong_kek: "this vault's key does not open the data it holds",
227
227
  tampered: "this member's record failed the vault's integrity check",
228
228
  };
229
229
 
@@ -531,8 +531,8 @@ class VaultService implements Vault {
531
531
  }
532
532
  prepared.superseded =
533
533
  first === undefined
534
- ? `the vault's entries are under ${newest.keyId}, a key this vault does not hold: it was given the wrong KEK or signing key, or a KEK it replaced is missing from previousKeks`
535
- : `the log moved on from this vault's key, ${current}, to ${newest.keyId}: a vault given a newer KEK replaced it, and a replaced key writes nothing more`;
534
+ ? `the vault's entries are under ${newest.keyId}, a key this vault does not hold: it was given the wrong vault key or signing key, or a vault key it replaced is missing from previousKeks in its config`
535
+ : `the log moved on from this vault's key, ${current}, to ${newest.keyId}: a vault given a newer vault key replaced it, and a replaced key writes nothing more`;
536
536
  return;
537
537
  }
538
538
  }
@@ -581,7 +581,7 @@ class VaultService implements Vault {
581
581
  return appendEntries(tx, prepared.logKey, entries, async (locked) => {
582
582
  const rotation = await store.latestVaultEntry(locked, [KEY_ROTATE]);
583
583
  if (rotation === undefined || prepared.logKeys.some((key) => key.keyId === rotation.keyId)) return;
584
- prepared.superseded = `the log moved on to ${rotation.keyId}, a key this vault does not hold: a vault given a newer KEK replaced it, and a replaced key writes nothing more`;
584
+ prepared.superseded = `the log moved on to ${rotation.keyId}, a key this vault does not hold: a vault given a newer vault key replaced it, and a replaced key writes nothing more`;
585
585
  throw new Error(prepared.superseded);
586
586
  });
587
587
  }
@@ -1359,7 +1359,7 @@ class VaultService implements Vault {
1359
1359
  throw error;
1360
1360
  }
1361
1361
  };
1362
- const mismatch = `this vault's ${kek.provider} KEK ${kek.keyId} does not open the data it holds: it is not the key that wrapped it`;
1362
+ const mismatch = `the vault key ${kek.keyId}${kek.provider === 'local' ? '' : ` (${kek.provider})`}, kek or previousKeks in the vault's config, is not the one that wrapped these values`;
1363
1363
  const check = checks.get(`${kek.provider}:${kek.keyId}`);
1364
1364
  if (check !== undefined) {
1365
1365
  if (!(await opens(check, KEY_CHECK_CONTEXT, KEY_CHECK_VALUE))) return mismatch;