@coffre/vault 0.7.0 → 0.8.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.
@@ -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-CwsWOH6j.js";
1
+ import { a as KekUnavailableError, c as awsKms, d as Kek, f as People, i as KekProvider, l as checkpointMessage, m as derivedSigningKey, n as AwsKmsOptions, o as SecretContext, p as VaultConfig, r as KekBadClaimError, s as WrappedDek, t as AwsCredentials, u as verifyCheckpoint } from "./index-BkHJR9Gt.js";
2
2
  import { AdmitInput, EndReferencesInput, ReferenceInput, RemoveInput, RewrapInput, SetAccessInput, SetSettingsInput, 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";
@@ -63,4 +63,4 @@ export declare class VaultEntrypoint extends WorkerEntrypoint implements Vault {
63
63
  /** The vault Worker's default export, `VaultEntrypoint`, configured from its `env`. */
64
64
  export declare function vault<Env>(configure_: (env: Env) => WorkersVaultConfig): typeof VaultEntrypoint;
65
65
  //#endregion
66
- export { type AwsCredentials, type AwsKmsOptions, type Kek, KekBadClaimError, type KekProvider, KekUnavailableError, type PostgresDatabase, type SecretContext, type Vault, type VaultConfig, type WrappedDek, awsKms, checkpointMessage, derivedSigningKey, postgres, verifyCheckpoint };
66
+ export { type AwsCredentials, type AwsKmsOptions, type Kek, KekBadClaimError, type KekProvider, KekUnavailableError, type People, type PostgresDatabase, type SecretContext, type Vault, type VaultConfig, type WrappedDek, awsKms, checkpointMessage, derivedSigningKey, postgres, verifyCheckpoint };
@@ -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-CQJS3OZZ.js";
2
- import { n as prepareVault, t as openVault } from "./vault-jbn0I41Z.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-BcttALFQ.js";
2
+ import { n as prepareVault, t as openVault } from "./vault-lBbyZ3kQ.js";
3
3
  import { createDatabase } from "@coffre/db";
4
4
  import { HyperdrivePool, postgres } from "@coffre/db/hyperdrive";
5
5
  import { WorkerEntrypoint } from "cloudflare:workers";
@@ -2,6 +2,15 @@ import { checkpointMessage as checkpointMessage$1, verifyCheckpoint } from "@cof
2
2
  import { AwsCredentials, AwsKmsOptions, KekBadClaimError as KekBadClaimError$1, KekProvider, KekProvider as KekProvider$1, KekRegistry, KekUnavailableError as KekUnavailableError$1, WrappedDek, awsKms } from "@coffre/core/kek";
3
3
  import { SecretContext } from "@coffre/core/envelope";
4
4
  //#region src/config.d.ts
5
+ /**
6
+ * What a person's reads open (docs/design/credential-proxy.md): `values`,
7
+ * as any member's; or `stand-ins`, where a person, on any credential of
8
+ * theirs (a session, a personal token, an MCP connection), opens values
9
+ * only for coffre to use for them, never to be handed back: the credential
10
+ * proxy injects them, and the deployment's `expose` list names the few it
11
+ * may pass on. Service accounts read values either way.
12
+ */
13
+ type People = 'values' | 'stand-ins';
5
14
  /**
6
15
  * A key-encryption key: 32 random bytes, base64, and the id envelopes record
7
16
  * it under; or one a key service holds, such as `awsKms({ keyArn, … })`.
@@ -41,6 +50,8 @@ type VaultConfig = {
41
50
  count: number;
42
51
  windowMinutes: number;
43
52
  };
53
+ /** What a person's reads open (`People`): `values` unless set. */
54
+ people?: People;
44
55
  } & Owners;
45
56
  /**
46
57
  * At least one email, each an Owner of the whole instance: made one when
@@ -69,4 +80,4 @@ type Owners = {
69
80
  /** The signing key a local KEK stands for, when the vault is given none: HKDF-SHA-256 of the KEK. */
70
81
  declare function derivedSigningKey(kek: Uint8Array): Buffer;
71
82
  //#endregion
72
- export { KekUnavailableError$1 as a, awsKms as c, Kek as d, VaultConfig as f, KekProvider$1 as i, checkpointMessage$1 as l, AwsKmsOptions as n, SecretContext as o, derivedSigningKey as p, KekBadClaimError$1 as r, WrappedDek as s, AwsCredentials as t, verifyCheckpoint as u };
83
+ export { KekUnavailableError$1 as a, awsKms as c, Kek as d, People as f, KekProvider$1 as i, checkpointMessage$1 as l, derivedSigningKey as m, AwsKmsOptions as n, SecretContext as o, VaultConfig as p, KekBadClaimError$1 as r, WrappedDek as s, AwsCredentials as t, verifyCheckpoint as u };
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-CwsWOH6j.js";
1
+ import { a as KekUnavailableError, c as awsKms, d as Kek, f as People, i as KekProvider, l as checkpointMessage, m as derivedSigningKey, n as AwsKmsOptions, o as SecretContext, p as VaultConfig, r as KekBadClaimError, s as WrappedDek, t as AwsCredentials, u as verifyCheckpoint } from "./index-BkHJR9Gt.js";
2
2
  export type * from "@coffre/core/vault";
3
- export { type AwsCredentials, type AwsKmsOptions, type Kek, KekBadClaimError, type KekProvider, KekUnavailableError, type SecretContext, type VaultConfig, type WrappedDek, awsKms, checkpointMessage, derivedSigningKey, verifyCheckpoint };
3
+ export { type AwsCredentials, type AwsKmsOptions, type Kek, KekBadClaimError, type KekProvider, KekUnavailableError, type People, 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-CQJS3OZZ.js";
1
+ import { a as verifyCheckpoint, i as checkpointMessage, n as KekUnavailableError, o as derivedSigningKey, r as awsKms, t as KekBadClaimError } from "./src-BcttALFQ.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-CwsWOH6j.js";
1
+ import { a as KekUnavailableError, c as awsKms, d as Kek, f as People, i as KekProvider, l as checkpointMessage, m as derivedSigningKey, n as AwsKmsOptions, o as SecretContext, p as VaultConfig, r as KekBadClaimError, s as WrappedDek, t as AwsCredentials, u as verifyCheckpoint } from "./index-BkHJR9Gt.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";
@@ -45,4 +45,4 @@ export declare function serveVault(config: NodeVaultConfig & {
45
45
  /** The vault that `serveVault` runs, from the server's side of its socket. */
46
46
  export declare function connectVault(socket: string): Vault;
47
47
  //#endregion
48
- export { type AwsCredentials, type AwsKmsOptions, type Kek, KekBadClaimError, type KekProvider, KekUnavailableError, type LocalVault, type SecretContext, type Vault, type VaultConfig, type VaultOptions, type WrappedDek, awsKms, checkpointMessage, derivedSigningKey, verifyCheckpoint };
48
+ export { type AwsCredentials, type AwsKmsOptions, type Kek, KekBadClaimError, type KekProvider, KekUnavailableError, type LocalVault, type People, type SecretContext, type Vault, type VaultConfig, type VaultOptions, type WrappedDek, awsKms, checkpointMessage, derivedSigningKey, verifyCheckpoint };
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-CQJS3OZZ.js";
2
- import { n as prepareVault, t as openVault } from "./vault-jbn0I41Z.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-BcttALFQ.js";
2
+ import { n as prepareVault, t as openVault } from "./vault-lBbyZ3kQ.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";
@@ -57,7 +57,8 @@ function resolveVaultConfig(config) {
57
57
  keks: new KekRegistry(current, previous),
58
58
  owners: checkOwners(config.owners ?? config.bootstrapOwners ?? config.rootAdmins ?? []),
59
59
  signingKeys: signingKeys(config.signingKey, keks),
60
- bulkLimit: config.bulkLimit === void 0 ? DEFAULT_BULK_LIMIT : checkBulkLimit(config.bulkLimit)
60
+ bulkLimit: config.bulkLimit === void 0 ? DEFAULT_BULK_LIMIT : checkBulkLimit(config.bulkLimit),
61
+ people: checkPeople(config.people ?? "values")
61
62
  };
62
63
  }
63
64
  /**
@@ -80,6 +81,10 @@ function signingKeys(given, keks) {
80
81
  }
81
82
  return [signing, ...derived].filter((key, i, all) => all.findIndex((other) => other.equals(key)) === i);
82
83
  }
84
+ function checkPeople(people) {
85
+ if (people !== "values" && people !== "stand-ins") throw new Error(`people is 'values' or 'stand-ins'; got ${JSON.stringify(people)}`);
86
+ return people;
87
+ }
83
88
  function checkBulkLimit({ count, windowMinutes }) {
84
89
  if (!Number.isInteger(count) || count < 1 || !(windowMinutes > 0)) throw new Error("bulkLimit needs a whole count of at least 1 and a window above 0 minutes");
85
90
  return {
@@ -1,4 +1,4 @@
1
- import { ACCESS_ACTIONS, checkpointMessage, checkpointVerifier, describeAccessFault } from "@coffre/core/vault";
1
+ import { ACCESS_ACTIONS, HANDED_BACK, checkpointMessage, checkpointVerifier, describeAccessFault } from "@coffre/core/vault";
2
2
  import { DEK_BYTES, KEY_CHECK, KEY_CHECK_CONTEXT, KEY_CHECK_VALUE, KekBadClaimError, KekCancelledError, KekUnavailableError, LocalKekProvider } from "@coffre/core/kek";
3
3
  import { createHmac, hkdfSync, randomUUID, timingSafeEqual } from "node:crypto";
4
4
  import { tablesOf } from "@coffre/db";
@@ -1083,6 +1083,7 @@ const MESSAGES = {
1083
1083
  deleted: "this project or environment was deleted",
1084
1084
  ended: "the reference read through was ended",
1085
1085
  personal: "a personal config's values are its owner's alone",
1086
+ stand_ins: "people use this instance's values through stand-ins, never as values: `coffre run` gives your app them; a service account reads values",
1086
1087
  not_allowed: "not allowed to change this",
1087
1088
  last_owner: "the last Owner of the whole instance stays one: make someone else an Owner first",
1088
1089
  invalid: "not something the rules allow",
@@ -1762,6 +1763,7 @@ var VaultService = class {
1762
1763
  });
1763
1764
  if (call.vet !== void 0 && !codes.some((code) => code !== null)) (await call.vet(d.tx)).forEach((code, i) => codes[i] = code);
1764
1765
  let first = codes.find((code) => code !== null) ?? null;
1766
+ if (first === null && action === "secret.read" && this.#config.people === "stand-ins" && principal.startsWith("user:") && HANDED_BACK.includes(call.input.purpose)) first = "stand_ins";
1765
1767
  if (first === null && action === "secret.read" && await this.#overBulkLimit(d, principal, secrets.length)) first = "bulk_limit";
1766
1768
  if (first !== null) throw new Refused(refusal(first, MESSAGES[first]), secrets.map((secret, i) => entry(secret, "deny", codes[i] ?? first)));
1767
1769
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/vault",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
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.7.0",
40
- "@coffre/db": "0.7.0"
39
+ "@coffre/core": "0.8.1",
40
+ "@coffre/db": "0.8.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "26.1.1",
package/src/config.ts CHANGED
@@ -28,8 +28,20 @@ export type ResolvedVaultConfig = {
28
28
  */
29
29
  signingKeys: readonly Uint8Array[];
30
30
  bulkLimit: BulkLimit;
31
+ /** `values` when left out. */
32
+ people?: People;
31
33
  };
32
34
 
35
+ /**
36
+ * What a person's reads open (docs/design/credential-proxy.md): `values`,
37
+ * as any member's; or `stand-ins`, where a person, on any credential of
38
+ * theirs (a session, a personal token, an MCP connection), opens values
39
+ * only for coffre to use for them, never to be handed back: the credential
40
+ * proxy injects them, and the deployment's `expose` list names the few it
41
+ * may pass on. Service accounts read values either way.
42
+ */
43
+ export type People = 'values' | 'stand-ins';
44
+
33
45
  /**
34
46
  * A key-encryption key: 32 random bytes, base64, and the id envelopes record
35
47
  * it under; or one a key service holds, such as `awsKms({ keyArn, … })`.
@@ -64,6 +76,8 @@ export type VaultConfig = {
64
76
  signingKey?: string;
65
77
  /** At most `count` data keys unwrapped per principal in any `windowMinutes`; 1000 in 15 unless set. */
66
78
  bulkLimit?: { count: number; windowMinutes: number };
79
+ /** What a person's reads open (`People`): `values` unless set. */
80
+ people?: People;
67
81
  } & Owners;
68
82
 
69
83
  /**
@@ -142,6 +156,7 @@ export function resolveVaultConfig(config: VaultConfig): ResolvedVaultConfig {
142
156
  owners: checkOwners(config.owners ?? config.bootstrapOwners ?? config.rootAdmins ?? []),
143
157
  signingKeys: signingKeys(config.signingKey, keks),
144
158
  bulkLimit: config.bulkLimit === undefined ? DEFAULT_BULK_LIMIT : checkBulkLimit(config.bulkLimit),
159
+ people: checkPeople(config.people ?? 'values'),
145
160
  };
146
161
  }
147
162
 
@@ -169,6 +184,11 @@ function signingKeys(given: string | undefined, keks: { provider: KekProvider; s
169
184
  return [signing, ...derived].filter((key, i, all) => all.findIndex((other) => other.equals(key)) === i);
170
185
  }
171
186
 
187
+ function checkPeople(people: People): People {
188
+ if (people !== 'values' && people !== 'stand-ins') throw new Error(`people is 'values' or 'stand-ins'; got ${JSON.stringify(people)}`);
189
+ return people;
190
+ }
191
+
172
192
  function checkBulkLimit({ count, windowMinutes }: { count: number; windowMinutes: number }): BulkLimit {
173
193
  if (!Number.isInteger(count) || count < 1 || !(windowMinutes > 0)) {
174
194
  throw new Error('bulkLimit needs a whole count of at least 1 and a window above 0 minutes');
package/src/index.ts CHANGED
@@ -3,6 +3,6 @@ export { checkpointMessage, verifyCheckpoint } from '@coffre/core/vault';
3
3
  export type { SecretContext } from '@coffre/core/envelope';
4
4
  export { awsKms, KekBadClaimError, KekUnavailableError } from '@coffre/core/kek';
5
5
  export type { AwsCredentials, AwsKmsOptions, KekProvider, WrappedDek } from '@coffre/core/kek';
6
- export type { Kek, VaultConfig } from './config.ts';
6
+ export type { Kek, People, VaultConfig } from './config.ts';
7
7
  /** The signing key a local KEK stands for, for tools that check the vault's records from outside. */
8
8
  export { derivedSigningKey } from './config.ts';
package/src/vault.ts CHANGED
@@ -46,6 +46,7 @@ import {
46
46
  checkpointMessage,
47
47
  checkpointVerifier,
48
48
  describeAccessFault,
49
+ HANDED_BACK,
49
50
  type Access,
50
51
  type AccessChange,
51
52
  type AccessFault,
@@ -60,6 +61,7 @@ import {
60
61
  type LogHead,
61
62
  type LogVerification,
62
63
  type Outcome,
64
+ type Purpose,
63
65
  type ReferenceInput,
64
66
  type Refusal,
65
67
  type RefusalCode,
@@ -281,6 +283,7 @@ const MESSAGES: Record<RefusalCode, string> = {
281
283
  deleted: 'this project or environment was deleted',
282
284
  ended: 'the reference read through was ended',
283
285
  personal: "a personal config's values are its owner's alone",
286
+ stand_ins: "people use this instance's values through stand-ins, never as values: `coffre run` gives your app them; a service account reads values",
284
287
  not_allowed: 'not allowed to change this',
285
288
  last_owner: 'the last Owner of the whole instance stays one: make someone else an Owner first',
286
289
  invalid: 'not something the rules allow',
@@ -1054,6 +1057,8 @@ class VaultService implements Vault {
1054
1057
  (await call.vet(d.tx)).forEach((code, i) => (codes[i] = code));
1055
1058
  }
1056
1059
  let first = codes.find((code) => code !== null) ?? null;
1060
+ // A person's values are only ever used for them, where people get stand-ins: by whatever credential of theirs asks.
1061
+ if (first === null && action === 'secret.read' && this.#config.people === 'stand-ins' && principal.startsWith('user:') && HANDED_BACK.includes(call.input.purpose as Purpose)) first = 'stand_ins';
1057
1062
  if (first === null && action === 'secret.read' && (await this.#overBulkLimit(d, principal, secrets.length))) first = 'bulk_limit';
1058
1063
  if (first !== null) {
1059
1064
  throw new Refused(