@agent-vm/secret-management 0.0.71

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Shravan Sunder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
1
+ //#region src/contracts.d.ts
2
+ interface MediatedSecretSpec {
3
+ readonly hosts: readonly string[];
4
+ readonly value: string;
5
+ }
6
+ type SecretRef = {
7
+ readonly source: '1password';
8
+ readonly ref: string;
9
+ } | {
10
+ readonly source: 'environment';
11
+ readonly ref: string;
12
+ };
13
+ interface SecretResolver {
14
+ resolve(ref: SecretRef): Promise<string>;
15
+ resolveAll(refs: Record<string, SecretRef>): Promise<Record<string, string>>;
16
+ }
17
+ //#endregion
18
+ export { MediatedSecretSpec, SecretRef, SecretResolver };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import { MediatedSecretSpec, SecretRef, SecretResolver } from "./contracts.js";
2
+ import { createStaticSecretResolver } from "./testing.js";
3
+ import { ResolveAllResponse } from "@1password/sdk";
4
+
5
+ //#region src/composite-secret-resolver.d.ts
6
+ type SecretEnvironment = Readonly<Record<string, string | undefined>>;
7
+ declare function createCompositeSecretResolver(onePasswordResolver: SecretResolver | null, env?: SecretEnvironment): SecretResolver;
8
+ //#endregion
9
+ //#region src/onepassword-secret-resolver.d.ts
10
+ interface SecretResolverClient {
11
+ readonly secrets: {
12
+ resolve(secretReference: string): Promise<string>;
13
+ resolveAll(secretReferences: readonly string[]): Promise<ResolveAllResponse>;
14
+ };
15
+ }
16
+ type TokenSource = {
17
+ readonly type: 'op-cli';
18
+ readonly ref: string;
19
+ } | {
20
+ readonly type: 'env';
21
+ readonly envVar?: string | undefined;
22
+ } | {
23
+ readonly type: 'keychain';
24
+ readonly service: string;
25
+ readonly account: string;
26
+ };
27
+ interface ExecFileOptions {
28
+ readonly env?: Readonly<Record<string, string | undefined>>;
29
+ readonly input?: string | undefined;
30
+ readonly redactErrorOutput?: boolean | undefined;
31
+ }
32
+ interface ExecFileResult {
33
+ readonly stdout: string;
34
+ readonly stderr: string;
35
+ }
36
+ declare function resolveServiceAccountToken(source: TokenSource, dependencies?: {
37
+ readonly execFileAsync?: (command: string, args: readonly string[], options?: ExecFileOptions) => Promise<ExecFileResult>;
38
+ }): Promise<string>;
39
+ interface CreateSecretResolverDependencies {
40
+ readonly createClient?: (config: {
41
+ auth: string;
42
+ integrationName: string;
43
+ integrationVersion: string;
44
+ }) => Promise<SecretResolverClient>;
45
+ readonly execFileAsync?: (command: string, args: readonly string[], options?: ExecFileOptions) => Promise<ExecFileResult>;
46
+ readonly integrationName?: string;
47
+ readonly integrationVersion?: string;
48
+ }
49
+ declare function createSecretResolver(options: {
50
+ readonly serviceAccountToken: string;
51
+ }, dependencies?: CreateSecretResolverDependencies): Promise<SecretResolver>;
52
+ declare function createOpCliSecretResolver(options: {
53
+ readonly serviceAccountToken: string;
54
+ }, dependencies?: Pick<CreateSecretResolverDependencies, 'execFileAsync'>): Promise<SecretResolver>;
55
+ //#endregion
56
+ export { CreateSecretResolverDependencies, ExecFileOptions, ExecFileResult, MediatedSecretSpec, SecretRef, SecretResolver, SecretResolverClient, type TokenSource, createCompositeSecretResolver, createOpCliSecretResolver, createSecretResolver, createStaticSecretResolver, resolveServiceAccountToken };
package/dist/index.js ADDED
@@ -0,0 +1,500 @@
1
+ import "./contracts.js";
2
+ import { createStaticSecretResolver } from "./testing.js";
3
+ import { execFile } from "node:child_process";
4
+ import { randomUUID } from "node:crypto";
5
+ import { createClient } from "@1password/sdk";
6
+ //#region src/composite-secret-resolver.ts
7
+ function resolveEnvironmentSecret(ref, env) {
8
+ const value = env[ref.ref];
9
+ if (value === void 0) throw new Error(`Environment variable '${ref.ref}' is not set.`);
10
+ if (value.trim().length === 0) throw new Error(`Environment variable '${ref.ref}' is set but empty.`);
11
+ return value;
12
+ }
13
+ function formatUnknownError$1(error) {
14
+ return error instanceof Error ? error.message : String(error);
15
+ }
16
+ function createSecretResolutionError(options) {
17
+ return new Error(`Failed to resolve secret '${options.secretName}' from '${options.ref.ref}': ${formatUnknownError$1(options.cause)}`, { cause: options.cause });
18
+ }
19
+ function throwAggregateSecretResolutionError(failures) {
20
+ if (failures.length > 0) throw new AggregateError(failures, `Failed to resolve ${String(failures.length)} secret(s).`);
21
+ }
22
+ function extractAggregateErrors(error) {
23
+ return (Array.isArray(error.errors) ? error.errors : [error]).map((failure) => failure instanceof Error ? failure : new Error(formatUnknownError$1(failure), { cause: failure }));
24
+ }
25
+ function createCompositeSecretResolver(onePasswordResolver, env = process.env) {
26
+ return {
27
+ async resolve(ref) {
28
+ switch (ref.source) {
29
+ case "environment": return resolveEnvironmentSecret(ref, env);
30
+ case "1password":
31
+ if (!onePasswordResolver) throw new Error("Secret with source '1password' requires host.secretsProvider to be configured.");
32
+ return await onePasswordResolver.resolve(ref);
33
+ default: throw new Error(`Unsupported secret source: ${JSON.stringify(ref)}`);
34
+ }
35
+ },
36
+ async resolveAll(refs) {
37
+ const resolved = {};
38
+ const onePasswordRefs = {};
39
+ const failures = [];
40
+ for (const [name, ref] of Object.entries(refs)) switch (ref.source) {
41
+ case "environment":
42
+ try {
43
+ resolved[name] = resolveEnvironmentSecret(ref, env);
44
+ } catch (error) {
45
+ failures.push(createSecretResolutionError({
46
+ cause: error,
47
+ ref,
48
+ secretName: name
49
+ }));
50
+ }
51
+ break;
52
+ case "1password":
53
+ onePasswordRefs[name] = ref;
54
+ break;
55
+ default: throw new Error(`Unsupported secret source: ${JSON.stringify(ref)}`);
56
+ }
57
+ if (Object.keys(onePasswordRefs).length > 0) if (!onePasswordResolver) failures.push(...Object.entries(onePasswordRefs).map(([name, ref]) => createSecretResolutionError({
58
+ cause: /* @__PURE__ */ new Error("Secret with source '1password' requires host.secretsProvider to be configured."),
59
+ ref,
60
+ secretName: name
61
+ })));
62
+ else {
63
+ const resolver = onePasswordResolver;
64
+ try {
65
+ Object.assign(resolved, await resolver.resolveAll(onePasswordRefs));
66
+ } catch (error) {
67
+ if (error instanceof AggregateError) failures.push(...extractAggregateErrors(error));
68
+ else failures.push(...Object.entries(onePasswordRefs).map(([name, ref]) => createSecretResolutionError({
69
+ cause: error,
70
+ ref,
71
+ secretName: name
72
+ })));
73
+ }
74
+ }
75
+ throwAggregateSecretResolutionError(failures);
76
+ return resolved;
77
+ }
78
+ };
79
+ }
80
+ //#endregion
81
+ //#region src/onepassword-secret-resolver.ts
82
+ function formatUnknownError(error) {
83
+ if (error instanceof AggregateError) {
84
+ const childMessages = readAggregateErrorChildren(error).map(formatUnknownError);
85
+ if (childMessages.length === 0) return error.message;
86
+ const separator = error.message.endsWith(".") ? "" : ".";
87
+ return `${error.message}${separator} Details: ${childMessages.join("; ")}`;
88
+ }
89
+ return error instanceof Error ? error.message : String(error);
90
+ }
91
+ var RedactedExecFileError = class extends Error {
92
+ safeDetail;
93
+ constructor(message, safeDetail, options) {
94
+ super(message, options);
95
+ this.safeDetail = safeDetail;
96
+ this.name = "RedactedExecFileError";
97
+ }
98
+ };
99
+ var OpInjectOutputError = class extends Error {
100
+ constructor(message) {
101
+ super(message);
102
+ this.name = "OpInjectOutputError";
103
+ }
104
+ };
105
+ function formatErrorMetadataValue(value) {
106
+ if (typeof value === "number" || typeof value === "string") return String(value);
107
+ }
108
+ function readErrorCode(error) {
109
+ if (!("code" in error)) return;
110
+ return formatErrorMetadataValue(error.code);
111
+ }
112
+ function readErrorSignal(error) {
113
+ if (!("signal" in error)) return;
114
+ return formatErrorMetadataValue(error.signal);
115
+ }
116
+ function formatRedactedExecErrorDetail(error) {
117
+ const exitCode = readErrorCode(error) ?? "unknown";
118
+ const signal = readErrorSignal(error);
119
+ return signal === void 0 ? `exit code ${exitCode}` : `exit code ${exitCode}, signal ${signal}`;
120
+ }
121
+ function createExecFileError(options) {
122
+ if (options.redactErrorOutput) {
123
+ const safeDetail = formatRedactedExecErrorDetail(options.error);
124
+ return new RedactedExecFileError(`${options.command} failed: ${safeDetail}`, safeDetail);
125
+ }
126
+ const errorDetail = options.stderr.trim() || options.error.message;
127
+ return /* @__PURE__ */ new Error(`${options.command} failed: ${errorDetail}`);
128
+ }
129
+ function formatStdinWriteErrorDetail(error) {
130
+ const errorCode = readErrorCode(error);
131
+ return errorCode === void 0 ? "stdin write failed" : `stdin write failed: ${errorCode}`;
132
+ }
133
+ function createStdinWriteError(command, error, redactErrorOutput) {
134
+ if (redactErrorOutput) {
135
+ const safeDetail = formatStdinWriteErrorDetail(error);
136
+ return new RedactedExecFileError(`${command} failed writing stdin: ${safeDetail}`, safeDetail, { cause: error });
137
+ }
138
+ return new Error(`${command} failed writing stdin: ${formatUnknownError(error)}`, { cause: error });
139
+ }
140
+ function ensureMacOsForKeychain() {
141
+ if (process.platform !== "darwin") throw new Error("Keychain token source is only supported on macOS. Use an env or op-cli token source on this platform so cmd-ts can surface a clear startup error.");
142
+ }
143
+ function execFileAsync(command, args, options) {
144
+ return new Promise((resolve, reject) => {
145
+ let hasSettled = false;
146
+ const rejectOnce = (error) => {
147
+ if (hasSettled) return;
148
+ hasSettled = true;
149
+ reject(error);
150
+ };
151
+ const resolveOnce = (result) => {
152
+ if (hasSettled) return;
153
+ hasSettled = true;
154
+ resolve(result);
155
+ };
156
+ const child = execFile(command, [...args], {
157
+ env: options?.env,
158
+ timeout: 3e4
159
+ }, (error, stdout, stderr) => {
160
+ if (error) {
161
+ rejectOnce(createExecFileError({
162
+ command,
163
+ error,
164
+ redactErrorOutput: options?.redactErrorOutput,
165
+ stderr
166
+ }));
167
+ return;
168
+ }
169
+ resolveOnce({
170
+ stdout,
171
+ stderr
172
+ });
173
+ });
174
+ if (options?.input !== void 0) {
175
+ if (!child.stdin) {
176
+ child.kill();
177
+ rejectOnce(/* @__PURE__ */ new Error(`${command} did not expose stdin for input`));
178
+ return;
179
+ }
180
+ child.stdin.once("error", (error) => {
181
+ child.kill();
182
+ rejectOnce(createStdinWriteError(command, error, options.redactErrorOutput));
183
+ });
184
+ child.stdin.end(options.input);
185
+ }
186
+ });
187
+ }
188
+ const SAFE_IDENTIFIER_PATTERN = /^[\w.@-]+$/u;
189
+ async function resolveServiceAccountToken(source, dependencies) {
190
+ const exec = dependencies?.execFileAsync ?? execFileAsync;
191
+ switch (source.type) {
192
+ case "op-cli": {
193
+ const token = (await exec("op", ["read", source.ref], { redactErrorOutput: true })).stdout.trim();
194
+ if (token.length === 0) throw new Error("op-cli token resolution returned empty value");
195
+ return token;
196
+ }
197
+ case "env": {
198
+ const envVar = source.envVar ?? "OP_SERVICE_ACCOUNT_TOKEN";
199
+ const token = process.env[envVar]?.trim();
200
+ if (!token) throw new Error(`Environment variable ${envVar} is not set`);
201
+ return token;
202
+ }
203
+ case "keychain": {
204
+ ensureMacOsForKeychain();
205
+ if (!SAFE_IDENTIFIER_PATTERN.test(source.service)) throw new Error("Keychain service name contains invalid characters");
206
+ if (!SAFE_IDENTIFIER_PATTERN.test(source.account)) throw new Error("Keychain account name contains invalid characters");
207
+ const token = (await exec("security", [
208
+ "find-generic-password",
209
+ "-s",
210
+ source.service,
211
+ "-a",
212
+ source.account,
213
+ "-w"
214
+ ])).stdout.trim();
215
+ if (token.length === 0) throw new Error("Keychain token resolution returned empty value");
216
+ return token;
217
+ }
218
+ default: throw new Error(`Unsupported token source: ${JSON.stringify(source)}`);
219
+ }
220
+ }
221
+ async function resolveSecretWithOpCli(serviceAccountToken, secretReference, exec) {
222
+ return stripOpReadStdoutTerminator((await exec("op", ["read", secretReference], {
223
+ env: createOpCliServiceAccountEnv(serviceAccountToken),
224
+ redactErrorOutput: true
225
+ })).stdout);
226
+ }
227
+ function stripOpReadStdoutTerminator(stdout) {
228
+ if (stdout.endsWith("\r\n")) return stdout.slice(0, -2);
229
+ if (stdout.endsWith("\n")) return stdout.slice(0, -1);
230
+ return stdout;
231
+ }
232
+ const opCliProcessPlumbingEnvNames = [
233
+ "APPDATA",
234
+ "ALL_PROXY",
235
+ "all_proxy",
236
+ "COMSPEC",
237
+ "HOME",
238
+ "HTTP_PROXY",
239
+ "http_proxy",
240
+ "HTTPS_PROXY",
241
+ "https_proxy",
242
+ "LANG",
243
+ "LC_ALL",
244
+ "LC_CTYPE",
245
+ "LOCALAPPDATA",
246
+ "NO_PROXY",
247
+ "no_proxy",
248
+ "PATH",
249
+ "SSL_CERT_DIR",
250
+ "SSL_CERT_FILE",
251
+ "TEMP",
252
+ "TMP",
253
+ "TMPDIR",
254
+ "TZ",
255
+ "USERPROFILE",
256
+ "WINDIR",
257
+ "XDG_CACHE_HOME",
258
+ "XDG_CONFIG_HOME",
259
+ "XDG_DATA_HOME",
260
+ "XDG_RUNTIME_DIR"
261
+ ];
262
+ function createOpCliServiceAccountEnv(serviceAccountToken) {
263
+ const env = {};
264
+ for (const envName of opCliProcessPlumbingEnvNames) {
265
+ const envValue = process.env[envName];
266
+ if (envValue !== void 0) env[envName] = envValue;
267
+ }
268
+ env.OP_SERVICE_ACCOUNT_TOKEN = serviceAccountToken;
269
+ return env;
270
+ }
271
+ const opInjectTemplateDelimiterPattern = /(?:\{\{|\}\})/u;
272
+ function assertOpInjectTemplateSafeReference(entry) {
273
+ if (!opInjectTemplateDelimiterPattern.test(entry.secretRef.ref) && !entry.secretRef.ref.includes("\0") && !entry.secretRef.ref.includes("\r") && !entry.secretRef.ref.includes("\n")) return;
274
+ throw new OpInjectOutputError(`op inject template rejected unsafe 1Password reference for secret '${entry.secretName}'.`);
275
+ }
276
+ async function resolveAllSecretsWithOpCli(serviceAccountToken, refs, exec) {
277
+ try {
278
+ return await resolveAllSecretsWithOpInject(serviceAccountToken, refs, exec);
279
+ } catch (error) {
280
+ const sanitizedInjectError = sanitizeOpInjectError(error);
281
+ try {
282
+ return await resolveAllSecretsWithSerialOpReads(serviceAccountToken, refs, exec);
283
+ } catch (readError) {
284
+ if (readError instanceof AggregateError) throw createAggregateErrorWithCause({
285
+ cause: readError,
286
+ errors: [sanitizedInjectError, ...readAggregateErrorChildren(readError)],
287
+ message: readError.message
288
+ });
289
+ throw createAggregateErrorWithCause({
290
+ cause: readError,
291
+ errors: [sanitizedInjectError, readError],
292
+ message: "op inject and serial op read both failed."
293
+ });
294
+ }
295
+ }
296
+ }
297
+ function sanitizeOpInjectError(error) {
298
+ if (error instanceof RedactedExecFileError) return /* @__PURE__ */ new Error(`op inject failed before serial op read: ${error.safeDetail}`);
299
+ if (error instanceof OpInjectOutputError) return /* @__PURE__ */ new Error(`op inject failed before serial op read: ${error.message}`);
300
+ const errorType = error instanceof Error ? error.name : typeof error;
301
+ return /* @__PURE__ */ new Error(`op inject failed before serial op read: ${errorType}`);
302
+ }
303
+ function readAggregateErrorChildren(error) {
304
+ const errorChildren = error.errors;
305
+ return Array.isArray(errorChildren) ? errorChildren : [];
306
+ }
307
+ function createAggregateErrorWithCause(options) {
308
+ const aggregateError = new AggregateError(options.errors, options.message);
309
+ aggregateError.cause = options.cause;
310
+ return aggregateError;
311
+ }
312
+ function createFallbackStageError(stage, error) {
313
+ return new Error(`${stage} failed before op CLI fallback: ${formatUnknownError(error)}`, { cause: error });
314
+ }
315
+ function createFallbackFailureError(options) {
316
+ if (options.fallbackError instanceof AggregateError) return createAggregateErrorWithCause({
317
+ cause: options.fallbackError,
318
+ errors: [options.stageError, ...readAggregateErrorChildren(options.fallbackError)],
319
+ message: options.fallbackError.message
320
+ });
321
+ return createAggregateErrorWithCause({
322
+ cause: options.fallbackError,
323
+ errors: [options.stageError, options.fallbackError],
324
+ message: options.message
325
+ });
326
+ }
327
+ function opInjectStartMarker(markerId) {
328
+ return `agent-vm-op-inject-start:${markerId}`;
329
+ }
330
+ function opInjectEndMarker(markerId) {
331
+ return `agent-vm-op-inject-end:${markerId}`;
332
+ }
333
+ function createOpInjectEntries(refs) {
334
+ return Object.entries(refs).map(([secretName, secretRef]) => ({
335
+ markerId: randomUUID(),
336
+ secretName,
337
+ secretRef
338
+ }));
339
+ }
340
+ function buildOpInjectTemplate(entries) {
341
+ return entries.map((entry) => {
342
+ assertOpInjectTemplateSafeReference(entry);
343
+ return [
344
+ opInjectStartMarker(entry.markerId),
345
+ `{{ ${entry.secretRef.ref} }}`,
346
+ opInjectEndMarker(entry.markerId)
347
+ ].join("\n");
348
+ }).join("\n");
349
+ }
350
+ function findUniqueOpInjectMarker(options) {
351
+ const markerIndex = options.output.indexOf(options.marker);
352
+ if (markerIndex === -1) throw new OpInjectOutputError(`op inject output omitted ${options.markerDescription} marker for secret '${options.secretName}' (${options.secretReference}).`);
353
+ if (options.output.indexOf(options.marker, markerIndex + options.marker.length) !== -1) throw new OpInjectOutputError(`op inject output for secret '${options.secretName}' (${options.secretReference}) contained repeated ${options.markerDescription} marker.`);
354
+ return markerIndex;
355
+ }
356
+ function extractInjectedSecret(options) {
357
+ const startToken = `${opInjectStartMarker(options.entry.markerId)}\n`;
358
+ const endToken = `\n${opInjectEndMarker(options.entry.markerId)}`;
359
+ const secretStartIndex = findUniqueOpInjectMarker({
360
+ marker: startToken,
361
+ markerDescription: "start",
362
+ output: options.output,
363
+ secretName: options.entry.secretName,
364
+ secretReference: options.entry.secretRef.ref
365
+ }) + startToken.length;
366
+ const secretEndIndex = findUniqueOpInjectMarker({
367
+ marker: endToken,
368
+ markerDescription: "end",
369
+ output: options.output,
370
+ secretName: options.entry.secretName,
371
+ secretReference: options.entry.secretRef.ref
372
+ });
373
+ return options.output.slice(secretStartIndex, secretEndIndex);
374
+ }
375
+ function mapOpInjectOutput(entries, output) {
376
+ return Object.fromEntries(entries.map((entry) => [entry.secretName, extractInjectedSecret({
377
+ entry,
378
+ output
379
+ })]));
380
+ }
381
+ async function resolveAllSecretsWithOpInject(serviceAccountToken, refs, exec) {
382
+ const entries = createOpInjectEntries(refs);
383
+ if (entries.length === 0) return {};
384
+ return mapOpInjectOutput(entries, (await exec("op", [
385
+ "inject",
386
+ "--in-file",
387
+ "/dev/stdin"
388
+ ], {
389
+ env: createOpCliServiceAccountEnv(serviceAccountToken),
390
+ input: buildOpInjectTemplate(entries),
391
+ redactErrorOutput: true
392
+ })).stdout);
393
+ }
394
+ async function resolveAllSecretsWithSerialOpReads(serviceAccountToken, refs, exec) {
395
+ const resolvedSecrets = {};
396
+ const failures = [];
397
+ for (const [secretName, secretRef] of Object.entries(refs)) try {
398
+ resolvedSecrets[secretName] = await resolveSecretWithOpCli(serviceAccountToken, secretRef.ref, exec);
399
+ } catch (error) {
400
+ failures.push(new Error(`Failed to resolve secret '${secretName}' from '${secretRef.ref}' via op read: ${formatUnknownError(error)}`, { cause: error }));
401
+ }
402
+ if (failures.length > 0) throw new AggregateError(failures, `Failed to resolve ${String(failures.length)} secret(s) via op read.`);
403
+ return resolvedSecrets;
404
+ }
405
+ function formatResolveReferenceError(error) {
406
+ return "message" in error && typeof error.message === "string" ? `${error.type}: ${error.message}` : error.type;
407
+ }
408
+ function readSdkBatchSecret(options) {
409
+ const individualResponse = options.response.individualResponses[options.secretReference];
410
+ if (!individualResponse) throw new Error(`1Password SDK resolveAll response omitted '${options.secretName}' (${options.secretReference}).`);
411
+ if (individualResponse.content !== void 0) return individualResponse.content.secret;
412
+ if (individualResponse.error !== void 0) throw new Error(`1Password SDK resolveAll failed for '${options.secretName}' (${options.secretReference}): ${formatResolveReferenceError(individualResponse.error)}`);
413
+ throw new Error(`1Password SDK resolveAll returned neither content nor error for '${options.secretName}' (${options.secretReference}).`);
414
+ }
415
+ function mapSdkResolveAllResponse(refs, response) {
416
+ return Object.fromEntries(Object.entries(refs).map(([secretName, secretRef]) => [secretName, readSdkBatchSecret({
417
+ response,
418
+ secretName,
419
+ secretReference: secretRef.ref
420
+ })]));
421
+ }
422
+ async function createSecretResolver(options, dependencies = {}) {
423
+ const exec = dependencies.execFileAsync ?? execFileAsync;
424
+ try {
425
+ const client = await (dependencies.createClient ?? createClient)({
426
+ auth: options.serviceAccountToken,
427
+ integrationName: dependencies.integrationName ?? "agent-vm",
428
+ integrationVersion: dependencies.integrationVersion ?? "0.0.1"
429
+ });
430
+ return {
431
+ resolve: async (ref) => {
432
+ try {
433
+ return await client.secrets.resolve(ref.ref);
434
+ } catch (error) {
435
+ const sdkResolveError = createFallbackStageError("1Password SDK resolve", error);
436
+ try {
437
+ return await resolveSecretWithOpCli(options.serviceAccountToken, ref.ref, exec);
438
+ } catch (fallbackError) {
439
+ throw createFallbackFailureError({
440
+ fallbackError,
441
+ message: "1Password SDK resolve and op CLI fallback both failed.",
442
+ stageError: sdkResolveError
443
+ });
444
+ }
445
+ }
446
+ },
447
+ resolveAll: async (refs) => {
448
+ try {
449
+ return mapSdkResolveAllResponse(refs, await client.secrets.resolveAll(Object.values(refs).map((secretRef) => secretRef.ref)));
450
+ } catch (error) {
451
+ const sdkResolveAllError = createFallbackStageError("1Password SDK resolveAll", error);
452
+ try {
453
+ return await resolveAllSecretsWithOpCli(options.serviceAccountToken, refs, exec);
454
+ } catch (fallbackError) {
455
+ throw createFallbackFailureError({
456
+ fallbackError,
457
+ message: "1Password SDK resolveAll and op CLI fallback both failed.",
458
+ stageError: sdkResolveAllError
459
+ });
460
+ }
461
+ }
462
+ }
463
+ };
464
+ } catch (error) {
465
+ const sdkClientCreationError = createFallbackStageError("1Password SDK client creation", error);
466
+ return {
467
+ resolve: async (ref) => {
468
+ try {
469
+ return await resolveSecretWithOpCli(options.serviceAccountToken, ref.ref, exec);
470
+ } catch (fallbackError) {
471
+ throw createFallbackFailureError({
472
+ fallbackError,
473
+ message: "1Password SDK client creation and op CLI fallback both failed.",
474
+ stageError: sdkClientCreationError
475
+ });
476
+ }
477
+ },
478
+ resolveAll: async (refs) => {
479
+ try {
480
+ return await resolveAllSecretsWithOpCli(options.serviceAccountToken, refs, exec);
481
+ } catch (fallbackError) {
482
+ throw createFallbackFailureError({
483
+ fallbackError,
484
+ message: "1Password SDK client creation and op CLI fallback both failed.",
485
+ stageError: sdkClientCreationError
486
+ });
487
+ }
488
+ }
489
+ };
490
+ }
491
+ }
492
+ async function createOpCliSecretResolver(options, dependencies = {}) {
493
+ const exec = dependencies.execFileAsync ?? execFileAsync;
494
+ return {
495
+ resolve: async (ref) => await resolveSecretWithOpCli(options.serviceAccountToken, ref.ref, exec),
496
+ resolveAll: async (refs) => await resolveAllSecretsWithOpCli(options.serviceAccountToken, refs, exec)
497
+ };
498
+ }
499
+ //#endregion
500
+ export { createCompositeSecretResolver, createOpCliSecretResolver, createSecretResolver, createStaticSecretResolver, resolveServiceAccountToken };
@@ -0,0 +1,6 @@
1
+ import { SecretResolver } from "./contracts.js";
2
+
3
+ //#region src/testing.d.ts
4
+ declare function createStaticSecretResolver(values: Readonly<Record<string, string>>): SecretResolver;
5
+ //#endregion
6
+ export { createStaticSecretResolver };
@@ -0,0 +1,16 @@
1
+ //#region src/testing.ts
2
+ function createStaticSecretResolver(values) {
3
+ const resolve = async (ref) => {
4
+ const value = values[ref.ref];
5
+ if (value === void 0) throw new Error(`No test secret value configured for '${ref.ref}'.`);
6
+ return value;
7
+ };
8
+ return {
9
+ resolve,
10
+ resolveAll: async (refs) => {
11
+ return Object.fromEntries(await Promise.all(Object.entries(refs).map(async ([name, ref]) => [name, await resolve(ref)])));
12
+ }
13
+ };
14
+ }
15
+ //#endregion
16
+ export { createStaticSecretResolver };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@agent-vm/secret-management",
3
+ "version": "0.0.71",
4
+ "description": "Shared secret reference contracts and resolvers for agent-vm packages.",
5
+ "homepage": "https://github.com/ShravanSunder/agent-vm#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/ShravanSunder/agent-vm/issues"
8
+ },
9
+ "license": "MIT",
10
+ "author": "Shravan Sunder <ShravanSunder@users.noreply.github.com>",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/ShravanSunder/agent-vm.git",
14
+ "directory": "packages/secret-management"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "type": "module",
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ },
27
+ "./contracts": {
28
+ "types": "./dist/contracts.d.ts",
29
+ "import": "./dist/contracts.js"
30
+ },
31
+ "./testing": {
32
+ "types": "./dist/testing.d.ts",
33
+ "import": "./dist/testing.js"
34
+ }
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "dependencies": {
40
+ "@1password/sdk": "^0.4.0"
41
+ },
42
+ "scripts": {
43
+ "build": "tsdown",
44
+ "typecheck": "tsc -p tsconfig.json --noEmit"
45
+ }
46
+ }