@stigmer/temporal-codecs 3.13.0 → 3.14.0

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.
@@ -40,11 +40,29 @@ export interface EncryptionKey {
40
40
  /** 32-byte AES-256 key. */
41
41
  readonly key: Buffer;
42
42
  }
43
+ /**
44
+ * Resolves decrypt-key material for a key id absent from the static
45
+ * config (C4, stigmer-cloud 20260827.09: the cloud server decodes
46
+ * desktop-runner histories written under server-managed per-identity
47
+ * `rpk_` keys, whose material lives in a database, not the environment).
48
+ * Returns undefined when the id is unknown — the codec then fails closed
49
+ * with its pinned unknown-key-id error. Implementations own their lookup
50
+ * and unsealing; the codec caches every RESOLVED key for the process
51
+ * lifetime (key material per id is immutable — rotation mints a new id),
52
+ * and never caches misses (the key may be minted moments later).
53
+ */
54
+ export type PayloadKeyResolver = (keyId: string) => Promise<Buffer | undefined>;
43
55
  export interface PayloadEncryptionConfig {
44
56
  /** Key used to encrypt outgoing payloads (and decrypt its own). */
45
57
  readonly primary: EncryptionKey;
46
58
  /** Decrypt-only key accepted during rotation windows. */
47
59
  readonly secondary?: EncryptionKey;
60
+ /**
61
+ * Decrypt-only fallback consulted when a payload names a key id the
62
+ * static pair does not cover ({@link PayloadKeyResolver}). Encode never
63
+ * consults it — outgoing payloads are always written under `primary`.
64
+ */
65
+ readonly resolveKey?: PayloadKeyResolver;
48
66
  }
49
67
  /**
50
68
  * Reads one secret VALUE by its env-var name. Consumers inject their own
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/encryption/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,yDAAyD;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AASD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,YAAY,EACxB,SAAS,CAAC,EAAE,oBAAoB,GAC/B,uBAAuB,GAAG,SAAS,CAuCrC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/encryption/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;AAEhF,MAAM,WAAW,uBAAuB;IACtC,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,yDAAyD;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AASD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,YAAY,EACxB,SAAS,CAAC,EAAE,oBAAoB,GAC/B,uBAAuB,GAAG,SAAS,CA6CrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/encryption/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/encryption/config.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,14 @@
28
28
  * fails closed: unknown key id, missing key id, and ciphertext tampering
29
29
  * all throw rather than surfacing bogus payloads.
30
30
  *
31
+ * Key ids outside the static primary/secondary pair may be resolved
32
+ * through the config's optional resolveKey seam (C4: the cloud server
33
+ * decodes desktop-runner histories under database-resident `rpk_` keys).
34
+ * Resolution is decode-only, resolved keys are cached for the process
35
+ * lifetime (an id's material is immutable — rotation mints a new id),
36
+ * misses are never cached, and an unresolvable id keeps the pinned
37
+ * fail-closed throw.
38
+ *
31
39
  * Moved from backend/services/runner/src/encryption/payload-codec.ts when
32
40
  * the codecs became @stigmer/temporal-codecs (one home for the
33
41
  * cross-language envelope contract; the TS server is the second consumer).
@@ -42,5 +50,11 @@ export declare class EncryptionPayloadCodec implements PayloadCodec {
42
50
  decode(payloads: Payload[]): Promise<Payload[]>;
43
51
  private encodePayload;
44
52
  private decodePayload;
53
+ /**
54
+ * The static map first; on a miss, the config's resolver (when present),
55
+ * caching the RESOLVED key only — a miss stays unresolved so a key
56
+ * minted after this process booted is found on the next decode.
57
+ */
58
+ private decryptKeyFor;
45
59
  }
46
60
  //# sourceMappingURL=payload-codec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"payload-codec.d.ts","sourceRoot":"","sources":["../../src/encryption/payload-codec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQhE,OAAO,KAAK,EAAiB,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAY1E,qBAAa,sBAAuB,YAAW,YAAY;IAG7C,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsB;gBAEzB,MAAM,EAAE,uBAAuB;IAOtD,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAI/C,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAIrD,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,aAAa;CAuBtB"}
1
+ {"version":3,"file":"payload-codec.d.ts","sourceRoot":"","sources":["../../src/encryption/payload-codec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQhE,OAAO,KAAK,EAAiB,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAY1E,qBAAa,sBAAuB,YAAW,YAAY;IAG7C,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsB;gBAEzB,MAAM,EAAE,uBAAuB;IAStD,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAI/C,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAIrD,OAAO,CAAC,aAAa;YAkBP,aAAa;IAwB3B;;;;OAIG;YACW,aAAa;CAc5B"}
@@ -28,6 +28,14 @@
28
28
  * fails closed: unknown key id, missing key id, and ciphertext tampering
29
29
  * all throw rather than surfacing bogus payloads.
30
30
  *
31
+ * Key ids outside the static primary/secondary pair may be resolved
32
+ * through the config's optional resolveKey seam (C4: the cloud server
33
+ * decodes desktop-runner histories under database-resident `rpk_` keys).
34
+ * Resolution is decode-only, resolved keys are cached for the process
35
+ * lifetime (an id's material is immutable — rotation mints a new id),
36
+ * misses are never cached, and an unresolvable id keeps the pinned
37
+ * fail-closed throw.
38
+ *
31
39
  * Moved from backend/services/runner/src/encryption/payload-codec.ts when
32
40
  * the codecs became @stigmer/temporal-codecs (one home for the
33
41
  * cross-language envelope contract; the TS server is the second consumer).
@@ -52,7 +60,9 @@ export class EncryptionPayloadCodec {
52
60
  decryptKeysById;
53
61
  constructor(config) {
54
62
  this.config = config;
55
- this.decryptKeysById = new Map([[config.primary.keyId, config.primary.key]]);
63
+ this.decryptKeysById = new Map([
64
+ [config.primary.keyId, config.primary.key],
65
+ ]);
56
66
  if (config.secondary) {
57
67
  this.decryptKeysById.set(config.secondary.keyId, config.secondary.key);
58
68
  }
@@ -61,7 +71,7 @@ export class EncryptionPayloadCodec {
61
71
  return payloads.map((p) => this.encodePayload(p));
62
72
  }
63
73
  async decode(payloads) {
64
- return payloads.map((p) => this.decodePayload(p));
74
+ return Promise.all(payloads.map((p) => this.decodePayload(p)));
65
75
  }
66
76
  encodePayload(payload) {
67
77
  // Data-less payloads (binary/null from void results) stay as-is:
@@ -79,7 +89,7 @@ export class EncryptionPayloadCodec {
79
89
  data: encrypt(plaintext, this.config.primary),
80
90
  };
81
91
  }
82
- decodePayload(payload) {
92
+ async decodePayload(payload) {
83
93
  if (!isEncryptedPayload(payload)) {
84
94
  return payload;
85
95
  }
@@ -88,7 +98,7 @@ export class EncryptionPayloadCodec {
88
98
  throw new Error("Encrypted payload is missing its encryption-key-id metadata — refusing to decode");
89
99
  }
90
100
  const keyId = Buffer.from(keyIdBytes).toString("utf-8");
91
- const key = this.decryptKeysById.get(keyId);
101
+ const key = await this.decryptKeyFor(keyId);
92
102
  if (!key) {
93
103
  throw new Error(`Encrypted payload uses unknown key id '${keyId}' — configure it as the ` +
94
104
  `primary or secondary payload encryption key (rotation window?)`);
@@ -96,6 +106,25 @@ export class EncryptionPayloadCodec {
96
106
  const plaintext = decrypt(payload.data, key, keyId);
97
107
  return temporal.api.common.v1.Payload.decode(plaintext);
98
108
  }
109
+ /**
110
+ * The static map first; on a miss, the config's resolver (when present),
111
+ * caching the RESOLVED key only — a miss stays unresolved so a key
112
+ * minted after this process booted is found on the next decode.
113
+ */
114
+ async decryptKeyFor(keyId) {
115
+ const known = this.decryptKeysById.get(keyId);
116
+ if (known !== undefined) {
117
+ return known;
118
+ }
119
+ if (this.config.resolveKey === undefined) {
120
+ return undefined;
121
+ }
122
+ const resolved = await this.config.resolveKey(keyId);
123
+ if (resolved !== undefined) {
124
+ this.decryptKeysById.set(keyId, resolved);
125
+ }
126
+ return resolved;
127
+ }
99
128
  }
100
129
  function isEncryptedPayload(payload) {
101
130
  const encoding = payload.metadata?.[ENCODING_METADATA_KEY];
@@ -1 +1 @@
1
- {"version":3,"file":"payload-codec.js","sourceRoot":"","sources":["../../src/encryption/payload-codec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"payload-codec.js","sourceRoot":"","sources":["../../src/encryption/payload-codec.ts"],"names":[],"mappings":"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"}
package/index.d.ts CHANGED
@@ -20,7 +20,7 @@
20
20
  */
21
21
  export { EncryptionPayloadCodec } from "./encryption/payload-codec.js";
22
22
  export { loadPayloadEncryptionConfig } from "./encryption/config.js";
23
- export type { BootstrapKeyMaterial, EncryptionKey, PayloadEncryptionConfig, SecretReader, } from "./encryption/config.js";
23
+ export type { BootstrapKeyMaterial, EncryptionKey, PayloadEncryptionConfig, PayloadKeyResolver, SecretReader, } from "./encryption/config.js";
24
24
  export { ClaimcheckPayloadCodec } from "./claimcheck/payload-codec.js";
25
25
  export { loadClaimcheckConfig } from "./claimcheck/config.js";
26
26
  export type { ClaimcheckConfig } from "./claimcheck/config.js";
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AACrE,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,uBAAuB,EACvB,YAAY,GACb,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AACrE,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,uBAAuB,EACvB,kBAAkB,EAClB,YAAY,GACb,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAQrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AASrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/temporal-codecs",
3
- "version": "3.13.0",
3
+ "version": "3.14.0",
4
4
  "description": "Temporal payload codecs shared by Stigmer's TypeScript Temporal processes — AES-256-GCM payload encryption and claim-check offloading, the one home for the cross-language envelope contract",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -23,7 +23,9 @@ import { makeInMemoryClaimcheckStorage } from "../__test-utils__/fake-claimcheck
23
23
 
24
24
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
25
 
26
- function makeKeyConfig(overrides: Partial<PayloadEncryptionConfig> = {}): PayloadEncryptionConfig {
26
+ function makeKeyConfig(
27
+ overrides: Partial<PayloadEncryptionConfig> = {},
28
+ ): PayloadEncryptionConfig {
27
29
  return {
28
30
  primary: { keyId: "test-key-1", key: randomBytes(32) },
29
31
  ...overrides,
@@ -121,7 +123,92 @@ describe("EncryptionPayloadCodec", () => {
121
123
  secondary: oldKey,
122
124
  });
123
125
  const [decoded] = await rotatedReader.decode([encoded]);
124
- expect(JSON.parse(Buffer.from(decoded.data!).toString())).toEqual({ rotated: true });
126
+ expect(JSON.parse(Buffer.from(decoded.data!).toString())).toEqual({
127
+ rotated: true,
128
+ });
129
+ });
130
+
131
+ // The resolveKey seam (C4 Stage 2): decrypt-only fallback for key ids
132
+ // outside the static pair — the cloud server's database-resident rpk_
133
+ // keys. Pinned properties: resolved keys decode, resolved material is
134
+ // cached (one lookup per id per process), misses are NOT cached, an
135
+ // unresolvable id keeps the fail-closed unknown-key-id throw, and
136
+ // encode never consults the resolver.
137
+ describe("resolveKey fallback", () => {
138
+ it("decodes a payload under a resolver-supplied key and caches the material", async () => {
139
+ const runnerKey = { keyId: "rpk_abc123", key: randomBytes(32) };
140
+ const writer = new EncryptionPayloadCodec({ primary: runnerKey });
141
+ const [encoded] = await writer.encode([
142
+ makeJsonPayload({ desktop: true }),
143
+ ]);
144
+
145
+ const lookups: string[] = [];
146
+ const reader = new EncryptionPayloadCodec({
147
+ ...makeKeyConfig(),
148
+ resolveKey: async (keyId) => {
149
+ lookups.push(keyId);
150
+ return keyId === runnerKey.keyId ? runnerKey.key : undefined;
151
+ },
152
+ });
153
+
154
+ const [first] = await reader.decode([encoded]);
155
+ expect(JSON.parse(Buffer.from(first.data!).toString())).toEqual({
156
+ desktop: true,
157
+ });
158
+
159
+ const [second] = await reader.decode([encoded]);
160
+ expect(JSON.parse(Buffer.from(second.data!).toString())).toEqual({
161
+ desktop: true,
162
+ });
163
+ expect(lookups).toEqual(["rpk_abc123"]);
164
+ });
165
+
166
+ it("fails closed when the resolver does not know the key id, and retries on the next decode", async () => {
167
+ const runnerKey = { keyId: "rpk_late", key: randomBytes(32) };
168
+ const writer = new EncryptionPayloadCodec({ primary: runnerKey });
169
+ const [encoded] = await writer.encode([makeJsonPayload({ a: 1 })]);
170
+
171
+ let known = false;
172
+ const lookups: string[] = [];
173
+ const reader = new EncryptionPayloadCodec({
174
+ ...makeKeyConfig(),
175
+ resolveKey: async (keyId) => {
176
+ lookups.push(keyId);
177
+ return known && keyId === runnerKey.keyId ? runnerKey.key : undefined;
178
+ },
179
+ });
180
+
181
+ await expect(reader.decode([encoded])).rejects.toThrow(
182
+ /unknown key id 'rpk_late'/,
183
+ );
184
+
185
+ // The miss was not cached: once the key exists (minted after this
186
+ // process booted), the next decode finds it.
187
+ known = true;
188
+ const [decoded] = await reader.decode([encoded]);
189
+ expect(JSON.parse(Buffer.from(decoded.data!).toString())).toEqual({
190
+ a: 1,
191
+ });
192
+ expect(lookups).toEqual(["rpk_late", "rpk_late"]);
193
+ });
194
+
195
+ it("never consults the resolver for the static keys or on encode", async () => {
196
+ const lookups: string[] = [];
197
+ const codec = new EncryptionPayloadCodec({
198
+ ...makeKeyConfig(),
199
+ resolveKey: async (keyId) => {
200
+ lookups.push(keyId);
201
+ return undefined;
202
+ },
203
+ });
204
+
205
+ const [encoded] = await codec.encode([makeJsonPayload({ b: 2 })]);
206
+ const [decoded] = await codec.decode([encoded]);
207
+ expect(JSON.parse(Buffer.from(decoded.data!).toString())).toEqual({
208
+ b: 2,
209
+ });
210
+ expect(lookups).toEqual([]);
211
+ });
125
212
  });
126
213
  });
127
214
 
@@ -177,9 +264,11 @@ describe("loadPayloadEncryptionConfig", () => {
177
264
  });
178
265
 
179
266
  it("loads primary and secondary keys", () => {
180
- process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY = randomBytes(32).toString("base64");
267
+ process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY =
268
+ randomBytes(32).toString("base64");
181
269
  process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY_ID = "k2";
182
- process.env.STIGMER_PAYLOAD_ENCRYPTION_SECONDARY_KEY = randomBytes(32).toString("base64");
270
+ process.env.STIGMER_PAYLOAD_ENCRYPTION_SECONDARY_KEY =
271
+ randomBytes(32).toString("base64");
183
272
  process.env.STIGMER_PAYLOAD_ENCRYPTION_SECONDARY_KEY_ID = "k1";
184
273
 
185
274
  const config = loadPayloadEncryptionConfig(readEnv);
@@ -189,16 +278,20 @@ describe("loadPayloadEncryptionConfig", () => {
189
278
  });
190
279
 
191
280
  it("fails the boot when a key is set without an id", () => {
192
- process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY = randomBytes(32).toString("base64");
281
+ process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY =
282
+ randomBytes(32).toString("base64");
193
283
  expect(() => loadPayloadEncryptionConfig(readEnv)).toThrow(
194
284
  /STIGMER_PAYLOAD_ENCRYPTION_KEY_ID is required/,
195
285
  );
196
286
  });
197
287
 
198
288
  it("fails the boot on a key of the wrong length", () => {
199
- process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY = randomBytes(16).toString("base64");
289
+ process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY =
290
+ randomBytes(16).toString("base64");
200
291
  process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY_ID = "k1";
201
- expect(() => loadPayloadEncryptionConfig(readEnv)).toThrow(/must decode to 32 bytes/);
292
+ expect(() => loadPayloadEncryptionConfig(readEnv)).toThrow(
293
+ /must decode to 32 bytes/,
294
+ );
202
295
  });
203
296
 
204
297
  // Server-managed key material from getRunnerBootstrapConfig (stigmer#398):
@@ -220,7 +313,8 @@ describe("loadPayloadEncryptionConfig", () => {
220
313
  });
221
314
 
222
315
  it("env-configured key wins over bootstrap material", () => {
223
- process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY = randomBytes(32).toString("base64");
316
+ process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY =
317
+ randomBytes(32).toString("base64");
224
318
  process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY_ID = "env-key";
225
319
 
226
320
  const config = loadPayloadEncryptionConfig(readEnv, bootstrapKeys());
@@ -230,7 +324,9 @@ describe("loadPayloadEncryptionConfig", () => {
230
324
 
231
325
  it("fails the boot on a bootstrap key without its id (server contract violation)", () => {
232
326
  expect(() =>
233
- loadPayloadEncryptionConfig(readEnv, { key: randomBytes(32).toString("base64") }),
327
+ loadPayloadEncryptionConfig(readEnv, {
328
+ key: randomBytes(32).toString("base64"),
329
+ }),
234
330
  ).toThrow(/payload_encryption_key_id/);
235
331
  });
236
332
 
@@ -264,7 +360,10 @@ describe("cross-language conformance fixture", () => {
264
360
  // lockstep.
265
361
  it("decrypts the committed fixture to the expected payload", async () => {
266
362
  const fixture = JSON.parse(
267
- readFileSync(join(__dirname, "fixtures", "encrypted-payload-fixture.json"), "utf-8"),
363
+ readFileSync(
364
+ join(__dirname, "fixtures", "encrypted-payload-fixture.json"),
365
+ "utf-8",
366
+ ),
268
367
  );
269
368
 
270
369
  const codec = new EncryptionPayloadCodec({
@@ -276,9 +375,9 @@ describe("cross-language conformance fixture", () => {
276
375
 
277
376
  const encrypted: Payload = {
278
377
  metadata: Object.fromEntries(
279
- Object.entries(fixture.encrypted.metadataBase64 as Record<string, string>).map(
280
- ([k, v]) => [k, Buffer.from(v, "base64")],
281
- ),
378
+ Object.entries(
379
+ fixture.encrypted.metadataBase64 as Record<string, string>,
380
+ ).map(([k, v]) => [k, Buffer.from(v, "base64")]),
282
381
  ),
283
382
  data: Buffer.from(fixture.encrypted.dataBase64, "base64"),
284
383
  };
@@ -42,11 +42,30 @@ export interface EncryptionKey {
42
42
  readonly key: Buffer;
43
43
  }
44
44
 
45
+ /**
46
+ * Resolves decrypt-key material for a key id absent from the static
47
+ * config (C4, stigmer-cloud 20260827.09: the cloud server decodes
48
+ * desktop-runner histories written under server-managed per-identity
49
+ * `rpk_` keys, whose material lives in a database, not the environment).
50
+ * Returns undefined when the id is unknown — the codec then fails closed
51
+ * with its pinned unknown-key-id error. Implementations own their lookup
52
+ * and unsealing; the codec caches every RESOLVED key for the process
53
+ * lifetime (key material per id is immutable — rotation mints a new id),
54
+ * and never caches misses (the key may be minted moments later).
55
+ */
56
+ export type PayloadKeyResolver = (keyId: string) => Promise<Buffer | undefined>;
57
+
45
58
  export interface PayloadEncryptionConfig {
46
59
  /** Key used to encrypt outgoing payloads (and decrypt its own). */
47
60
  readonly primary: EncryptionKey;
48
61
  /** Decrypt-only key accepted during rotation windows. */
49
62
  readonly secondary?: EncryptionKey;
63
+ /**
64
+ * Decrypt-only fallback consulted when a payload names a key id the
65
+ * static pair does not cover ({@link PayloadKeyResolver}). Encode never
66
+ * consults it — outgoing payloads are always written under `primary`.
67
+ */
68
+ readonly resolveKey?: PayloadKeyResolver;
50
69
  }
51
70
 
52
71
  /**
@@ -114,7 +133,10 @@ export function loadPayloadEncryptionConfig(
114
133
 
115
134
  if (bootstrap?.key) {
116
135
  const primary: EncryptionKey = {
117
- keyId: requireBootstrapKeyId(bootstrap.keyId, "payload_encryption_key_id"),
136
+ keyId: requireBootstrapKeyId(
137
+ bootstrap.keyId,
138
+ "payload_encryption_key_id",
139
+ ),
118
140
  key: parseKey(bootstrap.key, "bootstrap payload_encryption_key"),
119
141
  };
120
142
 
@@ -124,7 +146,10 @@ export function loadPayloadEncryptionConfig(
124
146
  bootstrap.secondaryKeyId,
125
147
  "payload_encryption_secondary_key_id",
126
148
  ),
127
- key: parseKey(bootstrap.secondaryKey, "bootstrap payload_encryption_secondary_key"),
149
+ key: parseKey(
150
+ bootstrap.secondaryKey,
151
+ "bootstrap payload_encryption_secondary_key",
152
+ ),
128
153
  }
129
154
  : undefined;
130
155
 
@@ -147,7 +172,10 @@ function requireKeyId(envName: string): string {
147
172
  return keyId;
148
173
  }
149
174
 
150
- function requireBootstrapKeyId(keyId: string | undefined, fieldName: string): string {
175
+ function requireBootstrapKeyId(
176
+ keyId: string | undefined,
177
+ fieldName: string,
178
+ ): string {
151
179
  if (!keyId) {
152
180
  throw new Error(
153
181
  `Runner bootstrap returned a payload encryption key without its ${fieldName} — ` +
@@ -162,7 +190,9 @@ function parseKey(rawBase64: string, envName: string): Buffer {
162
190
  try {
163
191
  key = Buffer.from(rawBase64, "base64");
164
192
  } catch {
165
- throw new Error(`Payload encryption misconfigured: ${envName} is not valid base64`);
193
+ throw new Error(
194
+ `Payload encryption misconfigured: ${envName} is not valid base64`,
195
+ );
166
196
  }
167
197
  if (key.length !== AES_256_KEY_BYTES) {
168
198
  throw new Error(
@@ -28,6 +28,14 @@
28
28
  * fails closed: unknown key id, missing key id, and ciphertext tampering
29
29
  * all throw rather than surfacing bogus payloads.
30
30
  *
31
+ * Key ids outside the static primary/secondary pair may be resolved
32
+ * through the config's optional resolveKey seam (C4: the cloud server
33
+ * decodes desktop-runner histories under database-resident `rpk_` keys).
34
+ * Resolution is decode-only, resolved keys are cached for the process
35
+ * lifetime (an id's material is immutable — rotation mints a new id),
36
+ * misses are never cached, and an unresolvable id keeps the pinned
37
+ * fail-closed throw.
38
+ *
31
39
  * Moved from backend/services/runner/src/encryption/payload-codec.ts when
32
40
  * the codecs became @stigmer/temporal-codecs (one home for the
33
41
  * cross-language envelope contract; the TS server is the second consumer).
@@ -58,7 +66,9 @@ export class EncryptionPayloadCodec implements PayloadCodec {
58
66
  private readonly decryptKeysById: Map<string, Buffer>;
59
67
 
60
68
  constructor(private readonly config: PayloadEncryptionConfig) {
61
- this.decryptKeysById = new Map([[config.primary.keyId, config.primary.key]]);
69
+ this.decryptKeysById = new Map([
70
+ [config.primary.keyId, config.primary.key],
71
+ ]);
62
72
  if (config.secondary) {
63
73
  this.decryptKeysById.set(config.secondary.keyId, config.secondary.key);
64
74
  }
@@ -69,7 +79,7 @@ export class EncryptionPayloadCodec implements PayloadCodec {
69
79
  }
70
80
 
71
81
  async decode(payloads: Payload[]): Promise<Payload[]> {
72
- return payloads.map((p) => this.decodePayload(p));
82
+ return Promise.all(payloads.map((p) => this.decodePayload(p)));
73
83
  }
74
84
 
75
85
  private encodePayload(payload: Payload): Payload {
@@ -90,7 +100,7 @@ export class EncryptionPayloadCodec implements PayloadCodec {
90
100
  };
91
101
  }
92
102
 
93
- private decodePayload(payload: Payload): Payload {
103
+ private async decodePayload(payload: Payload): Promise<Payload> {
94
104
  if (!isEncryptedPayload(payload)) {
95
105
  return payload;
96
106
  }
@@ -102,7 +112,7 @@ export class EncryptionPayloadCodec implements PayloadCodec {
102
112
  );
103
113
  }
104
114
  const keyId = Buffer.from(keyIdBytes).toString("utf-8");
105
- const key = this.decryptKeysById.get(keyId);
115
+ const key = await this.decryptKeyFor(keyId);
106
116
  if (!key) {
107
117
  throw new Error(
108
118
  `Encrypted payload uses unknown key id '${keyId}' — configure it as the ` +
@@ -113,6 +123,26 @@ export class EncryptionPayloadCodec implements PayloadCodec {
113
123
  const plaintext = decrypt(payload.data!, key, keyId);
114
124
  return temporal.api.common.v1.Payload.decode(plaintext);
115
125
  }
126
+
127
+ /**
128
+ * The static map first; on a miss, the config's resolver (when present),
129
+ * caching the RESOLVED key only — a miss stays unresolved so a key
130
+ * minted after this process booted is found on the next decode.
131
+ */
132
+ private async decryptKeyFor(keyId: string): Promise<Buffer | undefined> {
133
+ const known = this.decryptKeysById.get(keyId);
134
+ if (known !== undefined) {
135
+ return known;
136
+ }
137
+ if (this.config.resolveKey === undefined) {
138
+ return undefined;
139
+ }
140
+ const resolved = await this.config.resolveKey(keyId);
141
+ if (resolved !== undefined) {
142
+ this.decryptKeysById.set(keyId, resolved);
143
+ }
144
+ return resolved;
145
+ }
116
146
  }
117
147
 
118
148
  function isEncryptedPayload(payload: Payload): boolean {
package/src/index.ts CHANGED
@@ -25,6 +25,7 @@ export type {
25
25
  BootstrapKeyMaterial,
26
26
  EncryptionKey,
27
27
  PayloadEncryptionConfig,
28
+ PayloadKeyResolver,
28
29
  SecretReader,
29
30
  } from "./encryption/config.js";
30
31