@stigmer/temporal-codecs 3.29.0 → 3.31.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.
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Temporal connection security: how a Stigmer process authenticates to the
3
+ * Temporal frontend it dials. The server's client and worker connections
4
+ * and the runner's worker connection all read it, so it lives here, in the
5
+ * one library both processes share. Unset means today's plaintext
6
+ * connection, unchanged.
7
+ *
8
+ * The settings (each `_PATH` item has a `_DATA` twin holding the PEM text):
9
+ *
10
+ * STIGMER_TEMPORAL_API_KEY bearer key; implies TLS
11
+ * STIGMER_TEMPORAL_TLS "true" or "1": TLS with the
12
+ * system's trusted roots
13
+ * STIGMER_TEMPORAL_TLS_SERVER_NAME SNI / host-name override
14
+ * STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_PATH the frontend's CA (implies TLS)
15
+ * STIGMER_TEMPORAL_TLS_CLIENT_CERT_PATH mutual TLS: the client's
16
+ * STIGMER_TEMPORAL_TLS_CLIENT_KEY_PATH certificate and key, together
17
+ *
18
+ * WHY Stigmer's own names, not Temporal's `TEMPORAL_API_KEY` and
19
+ * `TEMPORAL_TLS_*`: those belong to the user's own Temporal work. A local
20
+ * Stigmer (`stigmer up`) inherits the user's shell, and agents that build
21
+ * Temporal applications export `TEMPORAL_API_KEY` for their own namespaces.
22
+ * Reading it would present the user's key to Stigmer's own Temporal and,
23
+ * through the runner's secret custody, take it away from the agents that
24
+ * need it. The shape follows Temporal's so an operator recognises it.
25
+ *
26
+ * WHY not Temporal's reader (`@temporalio/envconfig`): at the SDK version
27
+ * every Stigmer package pins (1.16.2), it reads `TEMPORAL_TLS=true` as TLS
28
+ * disabled. Releases before 1.22.0 carry that inversion. It also ignores
29
+ * host-verification settings, and it reads the user's own Temporal profile
30
+ * file by default.
31
+ *
32
+ * Misconfiguration fails the boot, the posture of the payload-encryption
33
+ * config beside this module: an operator who set half a client pair, or
34
+ * the same item twice, believes the connection is authenticated, and a
35
+ * silently weaker connection would be a security failure. An empty value
36
+ * means unset, so a blank line in a deployment's environment turns nothing
37
+ * on.
38
+ *
39
+ * Values are read through an injected {@link SecretReader}, as the
40
+ * encryption keys are: secret custody is the consumer's policy. The runner
41
+ * passes its credential store's reader, so the API key and the client key
42
+ * never live in an environment its agent tools can read.
43
+ */
44
+ import type { SecretReader } from "../encryption/config.js";
45
+ /**
46
+ * TLS settings in the Temporal SDK's own shape (`TLSConfig` in
47
+ * `@temporalio/common`), declared structurally so this library imports
48
+ * nothing internal. An empty object means TLS with the system's roots.
49
+ */
50
+ export interface TemporalTls {
51
+ readonly serverNameOverride?: string;
52
+ readonly serverRootCACertificate?: Uint8Array;
53
+ readonly clientCertPair?: {
54
+ readonly crt: Uint8Array;
55
+ readonly key: Uint8Array;
56
+ };
57
+ }
58
+ /**
59
+ * What a connection needs beyond its address. Both fields are absent for a
60
+ * plaintext connection; spread it into `Connection.connect` or
61
+ * `NativeConnection.connect` as is.
62
+ */
63
+ export interface TemporalConnectionConfig {
64
+ readonly tls?: TemporalTls;
65
+ readonly apiKey?: string;
66
+ }
67
+ export declare const TEMPORAL_API_KEY_ENV = "STIGMER_TEMPORAL_API_KEY";
68
+ export declare const TEMPORAL_TLS_ENV = "STIGMER_TEMPORAL_TLS";
69
+ export declare const TEMPORAL_TLS_SERVER_NAME_ENV = "STIGMER_TEMPORAL_TLS_SERVER_NAME";
70
+ /**
71
+ * The `_DATA` name of the client key: a credential, which the runner takes
72
+ * into custody beside the API key (its `_PATH` twin names a file, not a
73
+ * secret).
74
+ */
75
+ export declare const TEMPORAL_TLS_CLIENT_KEY_DATA_ENV: string;
76
+ /**
77
+ * Every setting name this module reads, exactly. A process that hands its
78
+ * children their own rendered settings ({@link temporalConnectionEnv})
79
+ * removes these from what the child inherits; other `STIGMER_TEMPORAL_*`
80
+ * names (the CLI's own) are not connection settings.
81
+ */
82
+ export declare const TEMPORAL_CONNECTION_ENV_NAMES: readonly string[];
83
+ /**
84
+ * Loads the connection settings.
85
+ *
86
+ * @throws when a setting is contradictory or unreadable: a client
87
+ * certificate without its key (or the reverse), both the `_PATH` and the
88
+ * `_DATA` form of one item, a path that cannot be read, or a
89
+ * `STIGMER_TEMPORAL_TLS` that is neither true nor false.
90
+ */
91
+ export declare function loadTemporalConnectionConfig(read: SecretReader): TemporalConnectionConfig;
92
+ /**
93
+ * Renders loaded settings back as the environment a child runner reads,
94
+ * every PEM item in its `_DATA` form, so that no file has to exist where
95
+ * the child runs. The server's sandbox drivers pass this to the runners
96
+ * they start. Round-trips: loading the rendered environment yields the
97
+ * same settings.
98
+ */
99
+ export declare function temporalConnectionEnv(config: TemporalConnectionConfig): Record<string, string>;
100
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/connection/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,UAAU,CAAC;IAC9C,QAAQ,CAAC,cAAc,CAAC,EAAE;QACxB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;QACzB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;KAC1B,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,oBAAoB,6BAA6B,CAAC;AAC/D,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AACvD,eAAO,MAAM,4BAA4B,qCAAqC,CAAC;AAS/E;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,QAAgC,CAAC;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAK1D,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,YAAY,GACjB,wBAAwB,CA2C1B;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,wBAAwB,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBxB"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Temporal connection security: how a Stigmer process authenticates to the
3
+ * Temporal frontend it dials. The server's client and worker connections
4
+ * and the runner's worker connection all read it, so it lives here, in the
5
+ * one library both processes share. Unset means today's plaintext
6
+ * connection, unchanged.
7
+ *
8
+ * The settings (each `_PATH` item has a `_DATA` twin holding the PEM text):
9
+ *
10
+ * STIGMER_TEMPORAL_API_KEY bearer key; implies TLS
11
+ * STIGMER_TEMPORAL_TLS "true" or "1": TLS with the
12
+ * system's trusted roots
13
+ * STIGMER_TEMPORAL_TLS_SERVER_NAME SNI / host-name override
14
+ * STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_PATH the frontend's CA (implies TLS)
15
+ * STIGMER_TEMPORAL_TLS_CLIENT_CERT_PATH mutual TLS: the client's
16
+ * STIGMER_TEMPORAL_TLS_CLIENT_KEY_PATH certificate and key, together
17
+ *
18
+ * WHY Stigmer's own names, not Temporal's `TEMPORAL_API_KEY` and
19
+ * `TEMPORAL_TLS_*`: those belong to the user's own Temporal work. A local
20
+ * Stigmer (`stigmer up`) inherits the user's shell, and agents that build
21
+ * Temporal applications export `TEMPORAL_API_KEY` for their own namespaces.
22
+ * Reading it would present the user's key to Stigmer's own Temporal and,
23
+ * through the runner's secret custody, take it away from the agents that
24
+ * need it. The shape follows Temporal's so an operator recognises it.
25
+ *
26
+ * WHY not Temporal's reader (`@temporalio/envconfig`): at the SDK version
27
+ * every Stigmer package pins (1.16.2), it reads `TEMPORAL_TLS=true` as TLS
28
+ * disabled. Releases before 1.22.0 carry that inversion. It also ignores
29
+ * host-verification settings, and it reads the user's own Temporal profile
30
+ * file by default.
31
+ *
32
+ * Misconfiguration fails the boot, the posture of the payload-encryption
33
+ * config beside this module: an operator who set half a client pair, or
34
+ * the same item twice, believes the connection is authenticated, and a
35
+ * silently weaker connection would be a security failure. An empty value
36
+ * means unset, so a blank line in a deployment's environment turns nothing
37
+ * on.
38
+ *
39
+ * Values are read through an injected {@link SecretReader}, as the
40
+ * encryption keys are: secret custody is the consumer's policy. The runner
41
+ * passes its credential store's reader, so the API key and the client key
42
+ * never live in an environment its agent tools can read.
43
+ */
44
+ import { readFileSync } from "node:fs";
45
+ export const TEMPORAL_API_KEY_ENV = "STIGMER_TEMPORAL_API_KEY";
46
+ export const TEMPORAL_TLS_ENV = "STIGMER_TEMPORAL_TLS";
47
+ export const TEMPORAL_TLS_SERVER_NAME_ENV = "STIGMER_TEMPORAL_TLS_SERVER_NAME";
48
+ /** The PEM items, each settable as a file path or as its text. */
49
+ const PEM_ITEMS = {
50
+ serverCa: "STIGMER_TEMPORAL_TLS_SERVER_CA_CERT",
51
+ clientCert: "STIGMER_TEMPORAL_TLS_CLIENT_CERT",
52
+ clientKey: "STIGMER_TEMPORAL_TLS_CLIENT_KEY",
53
+ };
54
+ /**
55
+ * The `_DATA` name of the client key: a credential, which the runner takes
56
+ * into custody beside the API key (its `_PATH` twin names a file, not a
57
+ * secret).
58
+ */
59
+ export const TEMPORAL_TLS_CLIENT_KEY_DATA_ENV = `${PEM_ITEMS.clientKey}_DATA`;
60
+ /**
61
+ * Every setting name this module reads, exactly. A process that hands its
62
+ * children their own rendered settings ({@link temporalConnectionEnv})
63
+ * removes these from what the child inherits; other `STIGMER_TEMPORAL_*`
64
+ * names (the CLI's own) are not connection settings.
65
+ */
66
+ export const TEMPORAL_CONNECTION_ENV_NAMES = [
67
+ TEMPORAL_API_KEY_ENV,
68
+ TEMPORAL_TLS_ENV,
69
+ TEMPORAL_TLS_SERVER_NAME_ENV,
70
+ ...Object.values(PEM_ITEMS).flatMap((item) => [`${item}_PATH`, `${item}_DATA`]),
71
+ ];
72
+ /**
73
+ * Loads the connection settings.
74
+ *
75
+ * @throws when a setting is contradictory or unreadable: a client
76
+ * certificate without its key (or the reverse), both the `_PATH` and the
77
+ * `_DATA` form of one item, a path that cannot be read, or a
78
+ * `STIGMER_TEMPORAL_TLS` that is neither true nor false.
79
+ */
80
+ export function loadTemporalConnectionConfig(read) {
81
+ const value = (name) => {
82
+ const raw = read(name);
83
+ return raw === undefined || raw.trim() === "" ? undefined : raw;
84
+ };
85
+ const apiKey = value(TEMPORAL_API_KEY_ENV)?.trim();
86
+ const tlsFlag = parseFlag(value(TEMPORAL_TLS_ENV));
87
+ const serverName = value(TEMPORAL_TLS_SERVER_NAME_ENV)?.trim();
88
+ const serverCa = readPem(PEM_ITEMS.serverCa, value);
89
+ const clientCert = readPem(PEM_ITEMS.clientCert, value);
90
+ const clientKey = readPem(PEM_ITEMS.clientKey, value);
91
+ if ((clientCert === undefined) !== (clientKey === undefined)) {
92
+ throw new Error(`Temporal connection misconfigured: mutual TLS needs both ` +
93
+ `${PEM_ITEMS.clientCert}_* and ${PEM_ITEMS.clientKey}_* — set both or neither`);
94
+ }
95
+ const tlsRequested = tlsFlag === true ||
96
+ apiKey !== undefined ||
97
+ serverName !== undefined ||
98
+ serverCa !== undefined ||
99
+ clientCert !== undefined;
100
+ if (tlsFlag === false && tlsRequested) {
101
+ throw new Error(`Temporal connection misconfigured: ${TEMPORAL_TLS_ENV} is false while ` +
102
+ `an API key, a server name, a CA or a client certificate is set — ` +
103
+ `each of those needs TLS`);
104
+ }
105
+ if (!tlsRequested)
106
+ return {};
107
+ const tls = {
108
+ ...(serverName !== undefined ? { serverNameOverride: serverName } : {}),
109
+ ...(serverCa !== undefined ? { serverRootCACertificate: serverCa } : {}),
110
+ ...(clientCert !== undefined && clientKey !== undefined
111
+ ? { clientCertPair: { crt: clientCert, key: clientKey } }
112
+ : {}),
113
+ };
114
+ return { tls, ...(apiKey !== undefined ? { apiKey } : {}) };
115
+ }
116
+ /**
117
+ * Renders loaded settings back as the environment a child runner reads,
118
+ * every PEM item in its `_DATA` form, so that no file has to exist where
119
+ * the child runs. The server's sandbox drivers pass this to the runners
120
+ * they start. Round-trips: loading the rendered environment yields the
121
+ * same settings.
122
+ */
123
+ export function temporalConnectionEnv(config) {
124
+ const env = {};
125
+ if (config.tls === undefined)
126
+ return env;
127
+ env[TEMPORAL_TLS_ENV] = "true";
128
+ const text = (bytes) => Buffer.from(bytes).toString("utf8");
129
+ const { serverNameOverride, serverRootCACertificate, clientCertPair } = config.tls;
130
+ if (serverNameOverride !== undefined)
131
+ env[TEMPORAL_TLS_SERVER_NAME_ENV] = serverNameOverride;
132
+ if (serverRootCACertificate !== undefined) {
133
+ env[`${PEM_ITEMS.serverCa}_DATA`] = text(serverRootCACertificate);
134
+ }
135
+ if (clientCertPair !== undefined) {
136
+ env[`${PEM_ITEMS.clientCert}_DATA`] = text(clientCertPair.crt);
137
+ env[TEMPORAL_TLS_CLIENT_KEY_DATA_ENV] = text(clientCertPair.key);
138
+ }
139
+ if (config.apiKey !== undefined)
140
+ env[TEMPORAL_API_KEY_ENV] = config.apiKey;
141
+ return env;
142
+ }
143
+ function parseFlag(raw) {
144
+ if (raw === undefined)
145
+ return undefined;
146
+ const flag = raw.trim().toLowerCase();
147
+ if (flag === "true" || flag === "1")
148
+ return true;
149
+ if (flag === "false" || flag === "0")
150
+ return false;
151
+ throw new Error(`Temporal connection misconfigured: ${TEMPORAL_TLS_ENV} must be true, 1, false or 0, got "${raw}"`);
152
+ }
153
+ function readPem(item, value) {
154
+ const path = value(`${item}_PATH`);
155
+ const data = value(`${item}_DATA`);
156
+ if (path !== undefined && data !== undefined) {
157
+ throw new Error(`Temporal connection misconfigured: set ${item}_PATH or ${item}_DATA, not both`);
158
+ }
159
+ if (data !== undefined)
160
+ return Buffer.from(data, "utf8");
161
+ if (path === undefined)
162
+ return undefined;
163
+ try {
164
+ return readFileSync(path.trim());
165
+ }
166
+ catch (err) {
167
+ const reason = err instanceof Error ? err.message : String(err);
168
+ throw new Error(`Temporal connection misconfigured: ${item}_PATH "${path.trim()}" cannot be read (${reason})`);
169
+ }
170
+ }
171
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/connection/config.ts"],"names":[],"mappings":"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"}
package/index.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  /**
2
- * @stigmer/temporal-codecs — Temporal payload codecs shared by Stigmer's
3
- * TypeScript Temporal processes (the runner today; the TS server at its
4
- * cutover). The encryption envelope is a cross-language wire contract
5
- * (the Java decode-only codec in stigmer-cloud's temporal-starter must
6
- * match it byte-for-byte, pinned by the fixture in
7
- * src/__tests__/fixtures/), which is why the codecs live in one library
8
- * instead of per-consumer copies that could fork.
2
+ * @stigmer/temporal-codecs — the Temporal plumbing Stigmer's TypeScript
3
+ * Temporal processes (the server and the runner) share: payload codecs,
4
+ * and the connection-security settings every Temporal connection reads
5
+ * (`connection/config.ts`). The encryption envelope is a cross-language
6
+ * wire contract (the Java decode-only codec in stigmer-cloud's
7
+ * temporal-starter must match it byte-for-byte, pinned by the fixture in
8
+ * src/__tests__/fixtures/), and the connection settings must mean the same
9
+ * on every connection, which is why both live in one library instead of
10
+ * per-consumer copies that could fork.
9
11
  *
10
12
  * This is the package's ONLY public boundary. Codec order at the consumer
11
13
  * is a correctness property: install [encryption, claimcheck] so encode
@@ -21,6 +23,8 @@
21
23
  export { EncryptionPayloadCodec } from "./encryption/payload-codec.js";
22
24
  export { loadPayloadEncryptionConfig } from "./encryption/config.js";
23
25
  export type { BootstrapKeyMaterial, EncryptionKey, PayloadEncryptionConfig, PayloadKeyResolver, SecretReader, } from "./encryption/config.js";
26
+ export { loadTemporalConnectionConfig, temporalConnectionEnv, TEMPORAL_API_KEY_ENV, TEMPORAL_CONNECTION_ENV_NAMES, TEMPORAL_TLS_CLIENT_KEY_DATA_ENV, } from "./connection/config.js";
27
+ export type { TemporalConnectionConfig, TemporalTls, } from "./connection/config.js";
24
28
  export { ClaimcheckPayloadCodec } from "./claimcheck/payload-codec.js";
25
29
  export { loadClaimcheckConfig } from "./claimcheck/config.js";
26
30
  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,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;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,EACL,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,EACpB,6BAA6B,EAC7B,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,wBAAwB,EACxB,WAAW,GACZ,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 CHANGED
@@ -1,11 +1,13 @@
1
1
  /**
2
- * @stigmer/temporal-codecs — Temporal payload codecs shared by Stigmer's
3
- * TypeScript Temporal processes (the runner today; the TS server at its
4
- * cutover). The encryption envelope is a cross-language wire contract
5
- * (the Java decode-only codec in stigmer-cloud's temporal-starter must
6
- * match it byte-for-byte, pinned by the fixture in
7
- * src/__tests__/fixtures/), which is why the codecs live in one library
8
- * instead of per-consumer copies that could fork.
2
+ * @stigmer/temporal-codecs — the Temporal plumbing Stigmer's TypeScript
3
+ * Temporal processes (the server and the runner) share: payload codecs,
4
+ * and the connection-security settings every Temporal connection reads
5
+ * (`connection/config.ts`). The encryption envelope is a cross-language
6
+ * wire contract (the Java decode-only codec in stigmer-cloud's
7
+ * temporal-starter must match it byte-for-byte, pinned by the fixture in
8
+ * src/__tests__/fixtures/), and the connection settings must mean the same
9
+ * on every connection, which is why both live in one library instead of
10
+ * per-consumer copies that could fork.
9
11
  *
10
12
  * This is the package's ONLY public boundary. Codec order at the consumer
11
13
  * is a correctness property: install [encryption, claimcheck] so encode
@@ -20,6 +22,7 @@
20
22
  */
21
23
  export { EncryptionPayloadCodec } from "./encryption/payload-codec.js";
22
24
  export { loadPayloadEncryptionConfig } from "./encryption/config.js";
25
+ export { loadTemporalConnectionConfig, temporalConnectionEnv, TEMPORAL_API_KEY_ENV, TEMPORAL_CONNECTION_ENV_NAMES, TEMPORAL_TLS_CLIENT_KEY_DATA_ENV, } from "./connection/config.js";
23
26
  export { ClaimcheckPayloadCodec } from "./claimcheck/payload-codec.js";
24
27
  export { loadClaimcheckConfig } from "./claimcheck/config.js";
25
28
  //# sourceMappingURL=index.js.map
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;AASrE,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;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AASrE,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,EACpB,6BAA6B,EAC7B,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAMhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stigmer/temporal-codecs",
3
- "version": "3.29.0",
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",
3
+ "version": "3.31.0",
4
+ "description": "The Temporal plumbing Stigmer's TypeScript Temporal processes share — AES-256-GCM payload encryption and claim-check offloading (the one home for the cross-language envelope contract), and the connection-security settings every Temporal connection reads",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -18,7 +18,8 @@
18
18
  "temporal",
19
19
  "payload-codec",
20
20
  "encryption",
21
- "claim-check"
21
+ "claim-check",
22
+ "tls"
22
23
  ],
23
24
  "main": "./index.js",
24
25
  "types": "./index.d.ts",
@@ -0,0 +1,155 @@
1
+ /**
2
+ * The Temporal connection settings (`connection/config.ts`): what each
3
+ * setting turns on, the misconfigurations that stop the boot, the rule that
4
+ * an empty value is unset, the refusal to read Temporal's own names (they
5
+ * are the user's), and the round trip through the environment a child
6
+ * runner receives.
7
+ */
8
+
9
+ import { describe, it, expect, afterAll } from "vitest";
10
+ import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+
14
+ import {
15
+ loadTemporalConnectionConfig,
16
+ temporalConnectionEnv,
17
+ TEMPORAL_CONNECTION_ENV_NAMES,
18
+ } from "../connection/config.js";
19
+
20
+ const CA = "-----BEGIN CERTIFICATE-----\nca\n-----END CERTIFICATE-----\n";
21
+ const CRT = "-----BEGIN CERTIFICATE-----\nclient\n-----END CERTIFICATE-----\n";
22
+ const KEY = "-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----\n";
23
+
24
+ const dir = mkdtempSync(join(tmpdir(), "temporal-connection-config-"));
25
+ afterAll(() => rmSync(dir, { recursive: true, force: true }));
26
+
27
+ function load(env: Record<string, string>) {
28
+ return loadTemporalConnectionConfig((name) => env[name]);
29
+ }
30
+
31
+ function text(bytes: Uint8Array | undefined): string | undefined {
32
+ return bytes === undefined ? undefined : Buffer.from(bytes).toString("utf8");
33
+ }
34
+
35
+ describe("loadTemporalConnectionConfig", () => {
36
+ it("is a plaintext connection when nothing is set", () => {
37
+ expect(load({})).toEqual({});
38
+ });
39
+
40
+ it("treats every empty or blank value as unset", () => {
41
+ expect(
42
+ load({
43
+ STIGMER_TEMPORAL_API_KEY: "",
44
+ STIGMER_TEMPORAL_TLS: " ",
45
+ STIGMER_TEMPORAL_TLS_SERVER_NAME: "",
46
+ STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_DATA: "",
47
+ STIGMER_TEMPORAL_TLS_CLIENT_CERT_PATH: "",
48
+ }),
49
+ ).toEqual({});
50
+ });
51
+
52
+ it("never reads Temporal's own TEMPORAL_* names, which are the user's", () => {
53
+ expect(
54
+ load({
55
+ TEMPORAL_API_KEY: "the-users-own-key",
56
+ TEMPORAL_TLS: "true",
57
+ TEMPORAL_TLS_SERVER_CA_CERT_DATA: CA,
58
+ }),
59
+ ).toEqual({});
60
+ });
61
+
62
+ it("turns TLS on with the system's roots for STIGMER_TEMPORAL_TLS=true or 1", () => {
63
+ expect(load({ STIGMER_TEMPORAL_TLS: "true" })).toEqual({ tls: {} });
64
+ expect(load({ STIGMER_TEMPORAL_TLS: "1" })).toEqual({ tls: {} });
65
+ expect(load({ STIGMER_TEMPORAL_TLS: "false" })).toEqual({});
66
+ });
67
+
68
+ it("an API key implies TLS", () => {
69
+ expect(load({ STIGMER_TEMPORAL_API_KEY: " k-1 " })).toEqual({ tls: {}, apiKey: "k-1" });
70
+ });
71
+
72
+ it("a CA or a server name implies TLS and is carried in the SDK's shape", () => {
73
+ const config = load({
74
+ STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_DATA: CA,
75
+ STIGMER_TEMPORAL_TLS_SERVER_NAME: "temporal.internal",
76
+ });
77
+ expect(config.tls?.serverNameOverride).toBe("temporal.internal");
78
+ expect(text(config.tls?.serverRootCACertificate)).toBe(CA);
79
+ expect(config.apiKey).toBeUndefined();
80
+ });
81
+
82
+ it("reads a client pair for mutual TLS, from data or from files", () => {
83
+ const fromData = load({
84
+ STIGMER_TEMPORAL_TLS_CLIENT_CERT_DATA: CRT,
85
+ STIGMER_TEMPORAL_TLS_CLIENT_KEY_DATA: KEY,
86
+ });
87
+ expect(text(fromData.tls?.clientCertPair?.crt)).toBe(CRT);
88
+ expect(text(fromData.tls?.clientCertPair?.key)).toBe(KEY);
89
+
90
+ writeFileSync(join(dir, "client.crt"), CRT);
91
+ writeFileSync(join(dir, "client.key"), KEY);
92
+ const fromFiles = load({
93
+ STIGMER_TEMPORAL_TLS_CLIENT_CERT_PATH: join(dir, "client.crt"),
94
+ STIGMER_TEMPORAL_TLS_CLIENT_KEY_PATH: join(dir, "client.key"),
95
+ });
96
+ expect(text(fromFiles.tls?.clientCertPair?.crt)).toBe(CRT);
97
+ expect(text(fromFiles.tls?.clientCertPair?.key)).toBe(KEY);
98
+ });
99
+
100
+ it.each([
101
+ [{ STIGMER_TEMPORAL_TLS_CLIENT_CERT_DATA: CRT }, /mutual TLS needs both/],
102
+ [{ STIGMER_TEMPORAL_TLS_CLIENT_KEY_DATA: KEY }, /mutual TLS needs both/],
103
+ [
104
+ {
105
+ STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_DATA: CA,
106
+ STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_PATH: "/etc/ca.pem",
107
+ },
108
+ /set STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_PATH or STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_DATA, not both/,
109
+ ],
110
+ [
111
+ { STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_PATH: "/nonexistent/ca.pem" },
112
+ /STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_PATH "\/nonexistent\/ca.pem" cannot be read/,
113
+ ],
114
+ [{ STIGMER_TEMPORAL_TLS: "yes" }, /must be true, 1, false or 0/],
115
+ [
116
+ { STIGMER_TEMPORAL_TLS: "false", STIGMER_TEMPORAL_API_KEY: "k" },
117
+ /STIGMER_TEMPORAL_TLS is false while/,
118
+ ],
119
+ ])("stops the boot on a contradictory or unreadable setting (%o)", (env, message) => {
120
+ expect(() => load(env)).toThrow(message);
121
+ });
122
+
123
+ it("reads every value through the injected reader, and only it", () => {
124
+ const asked: string[] = [];
125
+ loadTemporalConnectionConfig((name) => {
126
+ asked.push(name);
127
+ return undefined;
128
+ });
129
+ // The exported list is exactly what the reader reads, so a process
130
+ // that strips inherited settings strips all of them and nothing else.
131
+ expect([...new Set(asked)].sort()).toEqual([...TEMPORAL_CONNECTION_ENV_NAMES].sort());
132
+ });
133
+ });
134
+
135
+ describe("temporalConnectionEnv", () => {
136
+ it("renders nothing for a plaintext connection", () => {
137
+ expect(temporalConnectionEnv({})).toEqual({});
138
+ });
139
+
140
+ it("round-trips every setting through the _DATA forms a child runner reads", () => {
141
+ writeFileSync(join(dir, "ca.pem"), CA);
142
+ const loaded = load({
143
+ STIGMER_TEMPORAL_API_KEY: "k-2",
144
+ STIGMER_TEMPORAL_TLS_SERVER_NAME: "temporal.internal",
145
+ STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_PATH: join(dir, "ca.pem"),
146
+ STIGMER_TEMPORAL_TLS_CLIENT_CERT_DATA: CRT,
147
+ STIGMER_TEMPORAL_TLS_CLIENT_KEY_DATA: KEY,
148
+ });
149
+ const env = temporalConnectionEnv(loaded);
150
+
151
+ expect(Object.keys(env).some((name) => name.endsWith("_PATH"))).toBe(false);
152
+ expect(env["STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_DATA"]).toBe(CA);
153
+ expect(load(env)).toEqual(loaded);
154
+ });
155
+ });
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Temporal connection security: how a Stigmer process authenticates to the
3
+ * Temporal frontend it dials. The server's client and worker connections
4
+ * and the runner's worker connection all read it, so it lives here, in the
5
+ * one library both processes share. Unset means today's plaintext
6
+ * connection, unchanged.
7
+ *
8
+ * The settings (each `_PATH` item has a `_DATA` twin holding the PEM text):
9
+ *
10
+ * STIGMER_TEMPORAL_API_KEY bearer key; implies TLS
11
+ * STIGMER_TEMPORAL_TLS "true" or "1": TLS with the
12
+ * system's trusted roots
13
+ * STIGMER_TEMPORAL_TLS_SERVER_NAME SNI / host-name override
14
+ * STIGMER_TEMPORAL_TLS_SERVER_CA_CERT_PATH the frontend's CA (implies TLS)
15
+ * STIGMER_TEMPORAL_TLS_CLIENT_CERT_PATH mutual TLS: the client's
16
+ * STIGMER_TEMPORAL_TLS_CLIENT_KEY_PATH certificate and key, together
17
+ *
18
+ * WHY Stigmer's own names, not Temporal's `TEMPORAL_API_KEY` and
19
+ * `TEMPORAL_TLS_*`: those belong to the user's own Temporal work. A local
20
+ * Stigmer (`stigmer up`) inherits the user's shell, and agents that build
21
+ * Temporal applications export `TEMPORAL_API_KEY` for their own namespaces.
22
+ * Reading it would present the user's key to Stigmer's own Temporal and,
23
+ * through the runner's secret custody, take it away from the agents that
24
+ * need it. The shape follows Temporal's so an operator recognises it.
25
+ *
26
+ * WHY not Temporal's reader (`@temporalio/envconfig`): at the SDK version
27
+ * every Stigmer package pins (1.16.2), it reads `TEMPORAL_TLS=true` as TLS
28
+ * disabled. Releases before 1.22.0 carry that inversion. It also ignores
29
+ * host-verification settings, and it reads the user's own Temporal profile
30
+ * file by default.
31
+ *
32
+ * Misconfiguration fails the boot, the posture of the payload-encryption
33
+ * config beside this module: an operator who set half a client pair, or
34
+ * the same item twice, believes the connection is authenticated, and a
35
+ * silently weaker connection would be a security failure. An empty value
36
+ * means unset, so a blank line in a deployment's environment turns nothing
37
+ * on.
38
+ *
39
+ * Values are read through an injected {@link SecretReader}, as the
40
+ * encryption keys are: secret custody is the consumer's policy. The runner
41
+ * passes its credential store's reader, so the API key and the client key
42
+ * never live in an environment its agent tools can read.
43
+ */
44
+
45
+ import { readFileSync } from "node:fs";
46
+
47
+ import type { SecretReader } from "../encryption/config.js";
48
+
49
+ /**
50
+ * TLS settings in the Temporal SDK's own shape (`TLSConfig` in
51
+ * `@temporalio/common`), declared structurally so this library imports
52
+ * nothing internal. An empty object means TLS with the system's roots.
53
+ */
54
+ export interface TemporalTls {
55
+ readonly serverNameOverride?: string;
56
+ readonly serverRootCACertificate?: Uint8Array;
57
+ readonly clientCertPair?: {
58
+ readonly crt: Uint8Array;
59
+ readonly key: Uint8Array;
60
+ };
61
+ }
62
+
63
+ /**
64
+ * What a connection needs beyond its address. Both fields are absent for a
65
+ * plaintext connection; spread it into `Connection.connect` or
66
+ * `NativeConnection.connect` as is.
67
+ */
68
+ export interface TemporalConnectionConfig {
69
+ readonly tls?: TemporalTls;
70
+ readonly apiKey?: string;
71
+ }
72
+
73
+ export const TEMPORAL_API_KEY_ENV = "STIGMER_TEMPORAL_API_KEY";
74
+ export const TEMPORAL_TLS_ENV = "STIGMER_TEMPORAL_TLS";
75
+ export const TEMPORAL_TLS_SERVER_NAME_ENV = "STIGMER_TEMPORAL_TLS_SERVER_NAME";
76
+
77
+ /** The PEM items, each settable as a file path or as its text. */
78
+ const PEM_ITEMS = {
79
+ serverCa: "STIGMER_TEMPORAL_TLS_SERVER_CA_CERT",
80
+ clientCert: "STIGMER_TEMPORAL_TLS_CLIENT_CERT",
81
+ clientKey: "STIGMER_TEMPORAL_TLS_CLIENT_KEY",
82
+ } as const;
83
+
84
+ /**
85
+ * The `_DATA` name of the client key: a credential, which the runner takes
86
+ * into custody beside the API key (its `_PATH` twin names a file, not a
87
+ * secret).
88
+ */
89
+ export const TEMPORAL_TLS_CLIENT_KEY_DATA_ENV = `${PEM_ITEMS.clientKey}_DATA`;
90
+
91
+ /**
92
+ * Every setting name this module reads, exactly. A process that hands its
93
+ * children their own rendered settings ({@link temporalConnectionEnv})
94
+ * removes these from what the child inherits; other `STIGMER_TEMPORAL_*`
95
+ * names (the CLI's own) are not connection settings.
96
+ */
97
+ export const TEMPORAL_CONNECTION_ENV_NAMES: readonly string[] = [
98
+ TEMPORAL_API_KEY_ENV,
99
+ TEMPORAL_TLS_ENV,
100
+ TEMPORAL_TLS_SERVER_NAME_ENV,
101
+ ...Object.values(PEM_ITEMS).flatMap((item) => [`${item}_PATH`, `${item}_DATA`]),
102
+ ];
103
+
104
+ /**
105
+ * Loads the connection settings.
106
+ *
107
+ * @throws when a setting is contradictory or unreadable: a client
108
+ * certificate without its key (or the reverse), both the `_PATH` and the
109
+ * `_DATA` form of one item, a path that cannot be read, or a
110
+ * `STIGMER_TEMPORAL_TLS` that is neither true nor false.
111
+ */
112
+ export function loadTemporalConnectionConfig(
113
+ read: SecretReader,
114
+ ): TemporalConnectionConfig {
115
+ const value = (name: string): string | undefined => {
116
+ const raw = read(name);
117
+ return raw === undefined || raw.trim() === "" ? undefined : raw;
118
+ };
119
+
120
+ const apiKey = value(TEMPORAL_API_KEY_ENV)?.trim();
121
+ const tlsFlag = parseFlag(value(TEMPORAL_TLS_ENV));
122
+ const serverName = value(TEMPORAL_TLS_SERVER_NAME_ENV)?.trim();
123
+ const serverCa = readPem(PEM_ITEMS.serverCa, value);
124
+ const clientCert = readPem(PEM_ITEMS.clientCert, value);
125
+ const clientKey = readPem(PEM_ITEMS.clientKey, value);
126
+
127
+ if ((clientCert === undefined) !== (clientKey === undefined)) {
128
+ throw new Error(
129
+ `Temporal connection misconfigured: mutual TLS needs both ` +
130
+ `${PEM_ITEMS.clientCert}_* and ${PEM_ITEMS.clientKey}_* — set both or neither`,
131
+ );
132
+ }
133
+
134
+ const tlsRequested =
135
+ tlsFlag === true ||
136
+ apiKey !== undefined ||
137
+ serverName !== undefined ||
138
+ serverCa !== undefined ||
139
+ clientCert !== undefined;
140
+ if (tlsFlag === false && tlsRequested) {
141
+ throw new Error(
142
+ `Temporal connection misconfigured: ${TEMPORAL_TLS_ENV} is false while ` +
143
+ `an API key, a server name, a CA or a client certificate is set — ` +
144
+ `each of those needs TLS`,
145
+ );
146
+ }
147
+ if (!tlsRequested) return {};
148
+
149
+ const tls: TemporalTls = {
150
+ ...(serverName !== undefined ? { serverNameOverride: serverName } : {}),
151
+ ...(serverCa !== undefined ? { serverRootCACertificate: serverCa } : {}),
152
+ ...(clientCert !== undefined && clientKey !== undefined
153
+ ? { clientCertPair: { crt: clientCert, key: clientKey } }
154
+ : {}),
155
+ };
156
+ return { tls, ...(apiKey !== undefined ? { apiKey } : {}) };
157
+ }
158
+
159
+ /**
160
+ * Renders loaded settings back as the environment a child runner reads,
161
+ * every PEM item in its `_DATA` form, so that no file has to exist where
162
+ * the child runs. The server's sandbox drivers pass this to the runners
163
+ * they start. Round-trips: loading the rendered environment yields the
164
+ * same settings.
165
+ */
166
+ export function temporalConnectionEnv(
167
+ config: TemporalConnectionConfig,
168
+ ): Record<string, string> {
169
+ const env: Record<string, string> = {};
170
+ if (config.tls === undefined) return env;
171
+ env[TEMPORAL_TLS_ENV] = "true";
172
+ const text = (bytes: Uint8Array): string => Buffer.from(bytes).toString("utf8");
173
+ const { serverNameOverride, serverRootCACertificate, clientCertPair } = config.tls;
174
+ if (serverNameOverride !== undefined) env[TEMPORAL_TLS_SERVER_NAME_ENV] = serverNameOverride;
175
+ if (serverRootCACertificate !== undefined) {
176
+ env[`${PEM_ITEMS.serverCa}_DATA`] = text(serverRootCACertificate);
177
+ }
178
+ if (clientCertPair !== undefined) {
179
+ env[`${PEM_ITEMS.clientCert}_DATA`] = text(clientCertPair.crt);
180
+ env[TEMPORAL_TLS_CLIENT_KEY_DATA_ENV] = text(clientCertPair.key);
181
+ }
182
+ if (config.apiKey !== undefined) env[TEMPORAL_API_KEY_ENV] = config.apiKey;
183
+ return env;
184
+ }
185
+
186
+ function parseFlag(raw: string | undefined): boolean | undefined {
187
+ if (raw === undefined) return undefined;
188
+ const flag = raw.trim().toLowerCase();
189
+ if (flag === "true" || flag === "1") return true;
190
+ if (flag === "false" || flag === "0") return false;
191
+ throw new Error(
192
+ `Temporal connection misconfigured: ${TEMPORAL_TLS_ENV} must be true, 1, false or 0, got "${raw}"`,
193
+ );
194
+ }
195
+
196
+ function readPem(
197
+ item: string,
198
+ value: (name: string) => string | undefined,
199
+ ): Uint8Array | undefined {
200
+ const path = value(`${item}_PATH`);
201
+ const data = value(`${item}_DATA`);
202
+ if (path !== undefined && data !== undefined) {
203
+ throw new Error(
204
+ `Temporal connection misconfigured: set ${item}_PATH or ${item}_DATA, not both`,
205
+ );
206
+ }
207
+ if (data !== undefined) return Buffer.from(data, "utf8");
208
+ if (path === undefined) return undefined;
209
+ try {
210
+ return readFileSync(path.trim());
211
+ } catch (err) {
212
+ const reason = err instanceof Error ? err.message : String(err);
213
+ throw new Error(
214
+ `Temporal connection misconfigured: ${item}_PATH "${path.trim()}" cannot be read (${reason})`,
215
+ );
216
+ }
217
+ }
package/src/index.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  /**
2
- * @stigmer/temporal-codecs — Temporal payload codecs shared by Stigmer's
3
- * TypeScript Temporal processes (the runner today; the TS server at its
4
- * cutover). The encryption envelope is a cross-language wire contract
5
- * (the Java decode-only codec in stigmer-cloud's temporal-starter must
6
- * match it byte-for-byte, pinned by the fixture in
7
- * src/__tests__/fixtures/), which is why the codecs live in one library
8
- * instead of per-consumer copies that could fork.
2
+ * @stigmer/temporal-codecs — the Temporal plumbing Stigmer's TypeScript
3
+ * Temporal processes (the server and the runner) share: payload codecs,
4
+ * and the connection-security settings every Temporal connection reads
5
+ * (`connection/config.ts`). The encryption envelope is a cross-language
6
+ * wire contract (the Java decode-only codec in stigmer-cloud's
7
+ * temporal-starter must match it byte-for-byte, pinned by the fixture in
8
+ * src/__tests__/fixtures/), and the connection settings must mean the same
9
+ * on every connection, which is why both live in one library instead of
10
+ * per-consumer copies that could fork.
9
11
  *
10
12
  * This is the package's ONLY public boundary. Codec order at the consumer
11
13
  * is a correctness property: install [encryption, claimcheck] so encode
@@ -29,6 +31,18 @@ export type {
29
31
  SecretReader,
30
32
  } from "./encryption/config.js";
31
33
 
34
+ export {
35
+ loadTemporalConnectionConfig,
36
+ temporalConnectionEnv,
37
+ TEMPORAL_API_KEY_ENV,
38
+ TEMPORAL_CONNECTION_ENV_NAMES,
39
+ TEMPORAL_TLS_CLIENT_KEY_DATA_ENV,
40
+ } from "./connection/config.js";
41
+ export type {
42
+ TemporalConnectionConfig,
43
+ TemporalTls,
44
+ } from "./connection/config.js";
45
+
32
46
  export { ClaimcheckPayloadCodec } from "./claimcheck/payload-codec.js";
33
47
  export { loadClaimcheckConfig } from "./claimcheck/config.js";
34
48
  export type { ClaimcheckConfig } from "./claimcheck/config.js";