@secrefs/node 0.1.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,581 @@
1
+ import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
2
+ import vaultFactory from 'node-vault';
3
+
4
+ /**
5
+ * The provider contract every SecRefs backend (AWS, Vault, local, or a
6
+ * custom one you bring yourself) implements. Providers never log, print,
7
+ * or persist the values they return - that discipline is enforced by the
8
+ * resolver and CLI layers above them, which only ever handle secret values
9
+ * long enough to hand them to `process.env` or a spawned child process.
10
+ */
11
+ interface SecretFetchRequest {
12
+ /** The provider-specific secret path/id, as written after `sec://<provider>/`. */
13
+ path: string;
14
+ /** Optional dot-notation field to extract from a JSON secret payload. */
15
+ field?: string;
16
+ }
17
+ interface ProviderHealth {
18
+ provider: string;
19
+ ok: boolean;
20
+ /** Human-readable diagnostic. Never contains secret material. */
21
+ message?: string;
22
+ }
23
+ interface ISecretProvider {
24
+ readonly name: string;
25
+ /** Fetch and resolve a single secret reference to its final string value. */
26
+ fetchOne(request: SecretFetchRequest): Promise<string>;
27
+ /**
28
+ * Fetch multiple secret references. Implementations may batch/dedupe
29
+ * against the backend where possible; the default behavior (provided by
30
+ * {@link BaseSecretProvider}) is concurrent individual fetches via
31
+ * `Promise.allSettled`, surfacing the first failure with full context.
32
+ */
33
+ fetchBatch(requests: SecretFetchRequest[]): Promise<string[]>;
34
+ /**
35
+ * Lightweight reachability/auth probe used by `secrefs check`. Must never
36
+ * throw for expected failure modes (bad credentials, unreachable host) -
37
+ * those are reported via the returned {@link ProviderHealth}.
38
+ */
39
+ healthCheck(): Promise<ProviderHealth>;
40
+ }
41
+ declare class SecretFetchError extends Error {
42
+ readonly provider: string;
43
+ readonly path: string;
44
+ constructor(provider: string, path: string, cause: unknown);
45
+ }
46
+ declare abstract class BaseSecretProvider implements ISecretProvider {
47
+ abstract readonly name: string;
48
+ abstract fetchOne(request: SecretFetchRequest): Promise<string>;
49
+ fetchBatch(requests: SecretFetchRequest[]): Promise<string[]>;
50
+ abstract healthCheck(): Promise<ProviderHealth>;
51
+ }
52
+ /**
53
+ * Extracts a (possibly dot-nested) field from a JSON-encoded secret. If no
54
+ * field is requested, the raw string is returned unchanged. Throws a plain
55
+ * `Error` (never leaking the secret value itself) when the payload isn't
56
+ * valid JSON or the field path doesn't resolve to a value.
57
+ */
58
+ declare function extractField(raw: string, field: string | undefined, context: {
59
+ provider: string;
60
+ path: string;
61
+ }): string;
62
+
63
+ type ProviderRegistry = Record<string, ISecretProvider>;
64
+ interface ExpandOptions {
65
+ providers: ProviderRegistry;
66
+ /**
67
+ * When true (default), a value that starts with `sec://` but fails to
68
+ * parse throws immediately. When false, such values are left untouched.
69
+ * This only affects syntactically malformed references - unknown
70
+ * providers and provider-side fetch failures always surface as errors,
71
+ * aggregated in a {@link SecRefsResolutionError}.
72
+ */
73
+ strict?: boolean;
74
+ }
75
+ interface ResolutionFailure {
76
+ /** The env var / map key the reference was assigned to. */
77
+ key: string;
78
+ /** The original `sec://` string. */
79
+ ref: string;
80
+ message: string;
81
+ }
82
+ declare class SecRefsResolutionError extends Error {
83
+ readonly errors: ResolutionFailure[];
84
+ constructor(errors: ResolutionFailure[]);
85
+ }
86
+ interface CheckResult {
87
+ key: string;
88
+ ref: string;
89
+ provider: string;
90
+ ok: boolean;
91
+ /** Present only when ok is false. Never contains the secret value. */
92
+ message?: string;
93
+ }
94
+ /**
95
+ * Expands every `sec://` value in a plain key/value map, resolving all
96
+ * references concurrently via `Promise.allSettled`. Non-reference values
97
+ * pass through untouched. Never writes anything to disk - the caller
98
+ * decides what to do with the returned map (assign to `process.env`,
99
+ * template into a string, etc).
100
+ *
101
+ * Throws {@link SecRefsResolutionError} aggregating every failed
102
+ * reference if any fail to resolve.
103
+ */
104
+ declare function expandKeyValueMap(input: Record<string, string | undefined>, options: ExpandOptions): Promise<Record<string, string>>;
105
+ /**
106
+ * Expands `sec://` values found in `process.env`, mutating it in place.
107
+ * Returns the list of env var names that were rewritten.
108
+ */
109
+ declare function expandProcessEnv(options: ExpandOptions): Promise<string[]>;
110
+ /**
111
+ * Dry-run validation: resolves every `sec://` reference found in `input`
112
+ * but reports only ok/failure per reference - the secret values themselves
113
+ * are never returned or logged. Used by `secrefs check`.
114
+ */
115
+ declare function checkReferences(input: Record<string, string | undefined>, options: ExpandOptions): Promise<CheckResult[]>;
116
+
117
+ /**
118
+ * URI parser for SecRefs' `sec://` reference format:
119
+ *
120
+ * sec://<provider-alias>/<secret-path-or-id>[#<json-field>]
121
+ *
122
+ * sec://aws/prod/db#password
123
+ * sec://vault/secret/data/stripe#key
124
+ * sec://local/mock-db#password
125
+ *
126
+ * The provider alias is a bare identifier (letters/digits/`-`/`_`), the path
127
+ * is opaque to this parser (providers interpret it however their backend
128
+ * needs), and the optional `#field` fragment supports dot-notation for
129
+ * traversing nested JSON secrets (e.g. `#nested.value`).
130
+ */
131
+ interface ParsedSecretRef {
132
+ /** The original, unmodified reference string. */
133
+ raw: string;
134
+ /** Lowercased provider alias, e.g. "aws", "vault", "local". */
135
+ provider: string;
136
+ /** The secret path/id as understood by the provider. */
137
+ path: string;
138
+ /** Optional dot-notation field to extract from a JSON secret. */
139
+ field?: string;
140
+ }
141
+ declare class SecRefParseError extends Error {
142
+ readonly raw: string;
143
+ readonly reason: string;
144
+ constructor(raw: string, reason: string);
145
+ }
146
+ /** True if `value` is a string that looks like a `sec://` reference at all. */
147
+ declare function isSecretRef(value: unknown): value is string;
148
+ /**
149
+ * Parses a `sec://` reference string. Throws {@link SecRefParseError} if the
150
+ * value isn't a string, doesn't start with `sec://`, or doesn't match the
151
+ * full `<provider>/<path>[#field]` shape.
152
+ */
153
+ declare function parseSecretRef(raw: unknown): ParsedSecretRef;
154
+ /** Best-effort parse that returns `null` instead of throwing. */
155
+ declare function tryParseSecretRef(raw: unknown): ParsedSecretRef | null;
156
+
157
+ /**
158
+ * dotenv treats `#` as a start-of-comment marker even mid-value (unless
159
+ * the value is quoted), which silently truncates the `#field` fragment
160
+ * off an unquoted `sec://provider/path#field` reference - the exact
161
+ * format SecRefs itself uses. Rather than require every `sec://` value in
162
+ * `.env` to be quoted (an easy thing to forget, with no error if you do),
163
+ * this re-scans the raw file for unquoted `sec://` assignments and
164
+ * restores the value dotenv would otherwise clip.
165
+ *
166
+ * Note: because of this, an unquoted `sec://` value can't have a
167
+ * trailing inline comment on the same line - put comments on their own
168
+ * line instead. Quoted values (`KEY="sec://...#field"`) are unaffected
169
+ * and already handled correctly by dotenv itself.
170
+ */
171
+ declare function recoverTruncatedSecRefs(rawText: string, parsed: Record<string, string>): Record<string, string>;
172
+ /** Parses a `.env` file's contents, correctly preserving `sec://...#field` fragments. */
173
+ declare function parseEnvFileText(rawText: string): Record<string, string>;
174
+
175
+ /**
176
+ * A cache that expires, used by every provider that fetches over the
177
+ * network.
178
+ *
179
+ * The default TTL is **zero** — every read re-fetches. That's deliberate
180
+ * and it's the whole point of the product: a `sec://` reference is a
181
+ * stable name for a value that changes underneath it. A consumer holding
182
+ * the reference is supposed to see a rotated secret without being
183
+ * redeployed, and a cache with no expiry silently breaks exactly that.
184
+ * Before this existed, a long-running process fetched once and held the
185
+ * old value until restart.
186
+ *
187
+ * A non-zero TTL is a real tradeoff, not a mistake: every expansion is a
188
+ * network round trip, so a busy caller may want to trade a bounded window
189
+ * of staleness for latency and API-rate-limit headroom. `ttlMs: 30_000`
190
+ * means "a rotation reaches me within 30 seconds" — usually fine, and it
191
+ * should be a decision someone made rather than a default they inherited.
192
+ */
193
+ interface TtlCacheOptions {
194
+ /** Milliseconds an entry stays fresh. `0` (default) disables caching
195
+ * entirely - every `fetch` call goes to the source. */
196
+ ttlMs?: number;
197
+ /** Injected in tests so expiry doesn't require real waiting. */
198
+ now?: () => number;
199
+ }
200
+ declare class TtlCache<T> {
201
+ /** Settled values, only populated when a TTL is configured. */
202
+ private readonly entries;
203
+ /** Requests currently in flight, tracked separately from `entries`
204
+ * because coalescing and caching are different things: sharing an
205
+ * unsettled request holds no value past the moment it resolves, so it
206
+ * stays correct even with caching fully disabled. */
207
+ private readonly inFlight;
208
+ private readonly ttlMs;
209
+ private readonly now;
210
+ constructor(options?: TtlCacheOptions);
211
+ /**
212
+ * Returns the cached value for `key` if it's still fresh, otherwise
213
+ * calls `load` and caches that. In-flight promises are shared, so N
214
+ * concurrent expansions of the same reference make one request rather
215
+ * than N even when the TTL is zero - that's request coalescing, not
216
+ * caching, and it doesn't hold a value past its use.
217
+ *
218
+ * A rejected load is evicted rather than remembered, so a transient
219
+ * failure doesn't become a sticky one.
220
+ */
221
+ fetch(key: string, load: () => Promise<T>): Promise<T>;
222
+ /** Drops everything - used when a credential changes underneath the
223
+ * cache and anything fetched with the old one is suspect. */
224
+ clear(): void;
225
+ }
226
+
227
+ /**
228
+ * Thin HTTP client for a running control plane's credential-broker
229
+ * endpoint (docs/control-plane-design.md §7). This is the piece §10
230
+ * flagged as the missing link: every provider that supports
231
+ * control-plane-sourced credentials (AwsSecretsManagerProvider,
232
+ * BitwardenProvider - see their `controlPlane` constructor option)
233
+ * constructs one of these instead of only ever reading ambient env vars.
234
+ *
235
+ * Deliberately just an HTTP wrapper with no retry/backoff/circuit-
236
+ * breaking logic - a mint failure surfaces as a normal rejected promise,
237
+ * same as any other provider fetch failure, and the caller's existing
238
+ * error handling (resolver.ts's Promise.allSettled aggregation) already
239
+ * does the right thing with that.
240
+ */
241
+ interface MintedAwsCredentials {
242
+ accessKeyId: string;
243
+ secretAccessKey: string;
244
+ sessionToken: string;
245
+ /** ISO-8601 expiration timestamp. */
246
+ expiration: string;
247
+ }
248
+ interface MintedBitwardenCredentials {
249
+ accessToken: string;
250
+ organizationId?: string;
251
+ /** Explicitly not a TTL promise - see apps/control-plane/src/providers/bitwarden.ts. */
252
+ note: string;
253
+ }
254
+ type MintCredentialResponse = {
255
+ provider: "aws";
256
+ credentials: MintedAwsCredentials;
257
+ } | {
258
+ provider: "bitwarden";
259
+ credentials: MintedBitwardenCredentials;
260
+ };
261
+ /** What a provider's `controlPlane` constructor option needs - shared
262
+ * shape between `AwsSecretsManagerProvider` and `BitwardenProvider` (and
263
+ * any future control-plane-aware provider). */
264
+ interface ControlPlaneCredentialSource {
265
+ /** Base URL of a running control plane, e.g. from $SECREFS_CONTROL_PLANE_URL. */
266
+ baseUrl: string;
267
+ /** Bootstrap token or a verified OIDC token, e.g. from $SECREFS_CONTROL_PLANE_TOKEN. */
268
+ token: string;
269
+ /** Which `VaultConnection` alias this provider instance represents -
270
+ * this is what the control plane's RBAC grants are actually scoped
271
+ * against, not the `sec://` alias this provider happens to be
272
+ * registered under (though in practice they're usually the same
273
+ * string). */
274
+ alias: string;
275
+ /** Injected for testing - defaults to a real `ControlPlaneClient`. */
276
+ client?: ControlPlaneClient;
277
+ }
278
+ interface ControlPlaneClientOptions {
279
+ /** Base URL of a running control plane, e.g. from $SECREFS_CONTROL_PLANE_URL. */
280
+ baseUrl: string;
281
+ /** Bootstrap token or a verified OIDC token, e.g. from $SECREFS_CONTROL_PLANE_TOKEN. */
282
+ token: string;
283
+ /** Injected for testing - defaults to the global `fetch`. */
284
+ fetchImpl?: typeof fetch;
285
+ }
286
+ /** Thrown for a well-formed error response from the control plane (401,
287
+ * 403, 502, ...) - `status` and `message` come straight from its `{ error }`
288
+ * body, so a denial reason (e.g. "no grant authorizes path...") reaches
289
+ * the caller verbatim rather than as an opaque HTTP failure. */
290
+ declare class ControlPlaneRequestError extends Error {
291
+ readonly status: number;
292
+ constructor(status: number, message: string);
293
+ }
294
+ declare class ControlPlaneClient {
295
+ private readonly baseUrl;
296
+ private readonly token;
297
+ private readonly fetchImpl;
298
+ constructor(options: ControlPlaneClientOptions);
299
+ /** Authenticates, authorizes, and resolves a credential for `alias`/`path`
300
+ * - see the control plane's `POST /v1/credentials/mint`. Throws
301
+ * `ControlPlaneRequestError` for any non-2xx response. */
302
+ mintCredential(alias: string, path: string): Promise<MintCredentialResponse>;
303
+ }
304
+
305
+ interface AwsProviderOptions {
306
+ region?: string;
307
+ /** Inject a pre-configured client (primarily for testing) - also wins
308
+ * over `controlPlane` if both are set, since a test that supplies an
309
+ * explicit client wants full control regardless of the mode. */
310
+ client?: SecretsManagerClient;
311
+ /**
312
+ * Sources per-request AWS credentials from a running control plane
313
+ * (docs/control-plane-design.md §7/§10) instead of the ambient default
314
+ * credential chain. Every `fetchOne` call mints a fresh, request-scoped
315
+ * credential via `sts:AssumeRole` on the control plane's side - see
316
+ * `apps/control-plane/src/providers/awsSts.ts`. Mutually exclusive
317
+ * with ambient auth in spirit (not enforced - `client` still wins if
318
+ * both are given, for testing).
319
+ */
320
+ controlPlane?: ControlPlaneCredentialSource;
321
+ /**
322
+ * How long a fetched secret value may be reused, in milliseconds.
323
+ * Defaults to 0 - every expansion re-fetches, so a rotated secret
324
+ * reaches a long-running consumer without a redeploy. Raise it to
325
+ * trade a bounded window of staleness for fewer round trips. See
326
+ * ../ttlCache.ts.
327
+ */
328
+ cacheTtlMs?: number;
329
+ }
330
+ /**
331
+ * AWS Secrets Manager provider. Two credential-sourcing modes:
332
+ *
333
+ * - **Ambient (default)**: the AWS SDK v3 default credential provider
334
+ * chain - environment variables, shared config/credentials files,
335
+ * ECS/EC2 instance metadata, or an assumed IAM role. No credentials
336
+ * ever need to live in SecRefs configuration itself. One client is
337
+ * built lazily and reused for the provider's lifetime.
338
+ * - **Control-plane-sourced** (`controlPlane` option): a fresh,
339
+ * request-scoped credential is minted per `fetchOne` call via the
340
+ * control plane's `/v1/credentials/mint`, so a distinct
341
+ * `SecretsManagerClient` is constructed per path rather than reused -
342
+ * each one only ever has the narrow scope that one mint granted.
343
+ *
344
+ * Raw secret values fetched per-path are cached in memory for the lifetime
345
+ * of the provider instance either way, so multiple `#field` references
346
+ * against the same secret only cost one API call (and, in control-plane
347
+ * mode, one mint).
348
+ */
349
+ declare class AwsSecretsManagerProvider extends BaseSecretProvider {
350
+ readonly name = "aws";
351
+ private readonly explicitClient?;
352
+ private readonly region?;
353
+ private readonly controlPlane?;
354
+ private readonly controlPlaneClient?;
355
+ private ambientClient;
356
+ private readonly rawCache;
357
+ constructor(options?: AwsProviderOptions);
358
+ /** Resolves the `SecretsManagerClient` to use for one `path` - lazily
359
+ * built and reused in ambient mode, freshly minted per call in
360
+ * control-plane mode. `explicitClient` (test injection) always wins. */
361
+ private clientFor;
362
+ private buildClientFromMintedCredentials;
363
+ private getRaw;
364
+ fetchOne(request: SecretFetchRequest): Promise<string>;
365
+ healthCheck(): Promise<ProviderHealth>;
366
+ }
367
+
368
+ interface VaultProviderOptions {
369
+ /** Defaults to $VAULT_ADDR. */
370
+ endpoint?: string;
371
+ /** Defaults to $VAULT_TOKEN. */
372
+ token?: string;
373
+ /** Inject a pre-configured client (primarily for testing). */
374
+ client?: ReturnType<typeof vaultFactory>;
375
+ /** How long a fetched secret may be reused, in ms. Defaults to 0 -
376
+ * every expansion re-fetches, so rotation reaches a long-running
377
+ * consumer without a redeploy. See ../ttlCache.ts. */
378
+ cacheTtlMs?: number;
379
+ }
380
+ /**
381
+ * HashiCorp Vault provider supporting both KV v1 and KV v2 secrets engines.
382
+ * Auth is ambient via `VAULT_ADDR`/`VAULT_TOKEN` - point `path` at whatever
383
+ * the Vault HTTP API itself expects (KV v2 mounts include a literal `data/`
384
+ * segment, e.g. `secret/data/stripe`; KV v1 mounts do not).
385
+ *
386
+ * The client is constructed lazily on first use so that simply having a
387
+ * `VaultProvider` in your provider registry doesn't require Vault to be
388
+ * configured if you never actually reference `sec://vault/...`.
389
+ */
390
+ declare class VaultProvider extends BaseSecretProvider {
391
+ readonly name = "vault";
392
+ private readonly explicitClient?;
393
+ private readonly endpoint?;
394
+ private readonly token?;
395
+ private client;
396
+ private readonly dataCache;
397
+ constructor(options?: VaultProviderOptions);
398
+ private getClient;
399
+ private getData;
400
+ fetchOne(request: SecretFetchRequest): Promise<string>;
401
+ healthCheck(): Promise<ProviderHealth>;
402
+ }
403
+
404
+ interface LocalProviderOptions {
405
+ /** Overrides the file path. Defaults to $SECREFS_LOCAL_FILE or ./.secrefs.local.json */
406
+ filePath?: string;
407
+ /** Keep the parsed file in memory instead of re-reading per fetch.
408
+ * Off by default so edits take effect immediately. */
409
+ cacheFile?: boolean;
410
+ }
411
+ /**
412
+ * Reads secrets from a gitignored, developer-local JSON file. Intended for
413
+ * local development only - never point this at anything checked into
414
+ * version control. Each top-level key is a secret path; its value is either
415
+ * a plain string (returned as-is when no `#field` is requested) or an
416
+ * object (JSON-stringified, then field-extracted as needed).
417
+ *
418
+ * Example `.secrefs.local.json`:
419
+ * ```json
420
+ * { "mock-db": { "password": "hunter2", "user": "postgres" } }
421
+ * ```
422
+ */
423
+ declare class LocalProvider extends BaseSecretProvider {
424
+ readonly name = "local";
425
+ private readonly filePath;
426
+ /** Re-read on every fetch. The file is local and tiny, and caching
427
+ * it meant editing it mid-session silently did nothing. */
428
+ private cache;
429
+ private readonly cacheFile;
430
+ constructor(options?: LocalProviderOptions);
431
+ private load;
432
+ fetchOne(request: SecretFetchRequest): Promise<string>;
433
+ healthCheck(): Promise<ProviderHealth>;
434
+ }
435
+
436
+ /** The subset of `@bitwarden/sdk-napi`'s `BitwardenClient` this provider
437
+ * calls - kept narrow so tests can inject a plain mock object instead of
438
+ * a real client, the same pattern `VaultProvider`/`AwsSecretsManagerProvider`
439
+ * use for their own SDK clients. */
440
+ interface BitwardenClientLike {
441
+ auth(): {
442
+ loginAccessToken(accessToken: string, stateFile?: string): Promise<void>;
443
+ };
444
+ secrets(): {
445
+ get(id: string): Promise<{
446
+ value: string;
447
+ }>;
448
+ list(organizationId: string): Promise<{
449
+ data: {
450
+ id: string;
451
+ key: string;
452
+ }[];
453
+ }>;
454
+ };
455
+ }
456
+ interface BitwardenProviderOptions {
457
+ /** Defaults to $BWS_ACCESS_TOKEN. Ignored if `controlPlane` is set. */
458
+ accessToken?: string;
459
+ /** Required only to resolve a `path` given as a secret *name* rather
460
+ * than its UUID (see class docs). Defaults to $BWS_ORGANIZATION_ID.
461
+ * Ignored if `controlPlane` is set - the control plane's distributed
462
+ * credential supplies this instead. */
463
+ organizationId?: string;
464
+ /** Self-hosted instance override. Defaults to $BWS_API_URL. */
465
+ apiUrl?: string;
466
+ /** Self-hosted instance override. Defaults to $BWS_IDENTITY_URL. */
467
+ identityUrl?: string;
468
+ /**
469
+ * Opt-in path to an encrypted session-state file the SDK can reuse
470
+ * across calls to reduce auth rate-limiting (Bitwarden's own docs
471
+ * describe this file's contents as fully encrypted, not plaintext
472
+ * secret material). Omitted by default - this provider re-authenticates
473
+ * in memory each time it's constructed and writes nothing to disk
474
+ * unless a caller opts in.
475
+ */
476
+ stateFile?: string;
477
+ /**
478
+ * Sources the access token/organizationId from a running control plane
479
+ * (docs/control-plane-design.md §7/§10, and §8 for why Bitwarden's
480
+ * distribution here isn't the same as AWS's per-request minting -
481
+ * see apps/control-plane/src/providers/bitwarden.ts). Every `fetchOne`
482
+ * call still requests a distribution for its specific `path`, so the
483
+ * control plane's RBAC `Grant.path_pattern` is enforced per secret even
484
+ * though the underlying Bitwarden token itself isn't scoped that
485
+ * narrowly - "SDK-side enforcement" as documented on the control-plane
486
+ * side.
487
+ */
488
+ controlPlane?: ControlPlaneCredentialSource;
489
+ /** Inject a pre-configured client (primarily for testing). */
490
+ client?: BitwardenClientLike;
491
+ }
492
+ /**
493
+ * Bitwarden **Secrets Manager** provider (not the password vault - see
494
+ * https://bitwarden.com/help/secrets-manager-overview/). Two structural
495
+ * differences from `AwsSecretsManagerProvider`/`VaultProvider` worth
496
+ * knowing before using this:
497
+ *
498
+ * 1. **Secrets are end-to-end encrypted.** There is no plain authenticated
499
+ * REST call to fetch a value - the official SDK derives a decryption
500
+ * key from the access token during login and decrypts client-side.
501
+ * That's why this provider depends on `@bitwarden/sdk-napi` (a beta
502
+ * Node-API binding maintained by Bitwarden) rather than a bare `fetch`.
503
+ * 2. **Bitwarden addresses secrets by UUID, with no path hierarchy** the
504
+ * way AWS/Vault secret names have. `path` may be that UUID directly, or
505
+ * - if `organizationId` is configured (ambient mode) or supplied by the
506
+ * control plane (control-plane mode) - a human-readable secret *name*
507
+ * (Bitwarden's "key" field), resolved via one cached `secrets().list()`
508
+ * call. With neither, only UUID paths work.
509
+ */
510
+ declare class BitwardenProvider extends BaseSecretProvider {
511
+ readonly name = "bitwarden";
512
+ private readonly explicitClient?;
513
+ private readonly ambientAccessToken?;
514
+ private readonly ambientOrganizationId?;
515
+ private readonly apiUrl?;
516
+ private readonly identityUrl?;
517
+ private readonly stateFile?;
518
+ private readonly controlPlane?;
519
+ private readonly controlPlaneClient?;
520
+ private client;
521
+ private loggedInAccessToken;
522
+ private loggedIn;
523
+ private organizationId;
524
+ /** Secret name -> id, populated by one `list()` call the first time a
525
+ * non-UUID path is requested. Invalidated if `organizationId` ever
526
+ * changes (control-plane mode, defensively - static in practice). */
527
+ private nameToId;
528
+ constructor(options?: BitwardenProviderOptions);
529
+ private getClient;
530
+ private loginWith;
531
+ /** Ensures a session exists for `path`. Ambient mode logs in once
532
+ * (memoized) with the ambient token; control-plane mode requests a
533
+ * distribution for this specific `path` every call - see the
534
+ * `controlPlane` option's docs for why that RBAC check has to be
535
+ * per-path even though the token it returns doesn't vary. */
536
+ private ensureLoggedInFor;
537
+ /** Assumes `ensureLoggedInFor(path)` has already run for this exact
538
+ * `path` - callers always do that first, so `this.organizationId` is
539
+ * already whatever this path's session resolved to. */
540
+ private resolveSecretId;
541
+ fetchOne(request: SecretFetchRequest): Promise<string>;
542
+ healthCheck(): Promise<ProviderHealth>;
543
+ }
544
+
545
+ /** Builds the default provider registry: aws, vault, local, bitwarden. */
546
+ declare function createDefaultProviders(): ProviderRegistry;
547
+ interface SecRefsOptions {
548
+ providers?: ProviderRegistry;
549
+ strict?: boolean;
550
+ }
551
+ /**
552
+ * The primary library entry point. Instantiate your own (with a custom
553
+ * provider registry) or use the default `secRefs` singleton below.
554
+ */
555
+ declare class SecRefs {
556
+ readonly providers: ProviderRegistry;
557
+ readonly strict: boolean;
558
+ constructor(options?: SecRefsOptions);
559
+ private get expandOptions();
560
+ /**
561
+ * Expands every `sec://` value found in `process.env`, mutating it in
562
+ * place. Returns the list of env var names that were rewritten.
563
+ */
564
+ init(): Promise<string[]>;
565
+ /**
566
+ * Expands `sec://` values in an arbitrary key/value map (e.g. a parsed
567
+ * `.env` file) without touching `process.env`.
568
+ */
569
+ expandEnv(env: Record<string, string | undefined>): Promise<Record<string, string>>;
570
+ /** Expands a single string if it's a `sec://` reference; otherwise returns it unchanged. */
571
+ expandString(value: string): Promise<string>;
572
+ /**
573
+ * Dry-run validation of every `sec://` reference in `env` (defaults to
574
+ * `process.env`). Never returns plaintext secret values.
575
+ */
576
+ check(env?: Record<string, string | undefined>): Promise<Awaited<ReturnType<typeof checkReferences>>>;
577
+ }
578
+ /** Convenience singleton mirroring `secRefs.init()` / `secRefs.expandEnv()` / `secRefs.expandString()`. */
579
+ declare const secRefs: SecRefs;
580
+
581
+ export { type AwsProviderOptions, AwsSecretsManagerProvider, BaseSecretProvider, type BitwardenClientLike, BitwardenProvider, type BitwardenProviderOptions, type CheckResult, ControlPlaneClient, type ControlPlaneClientOptions, type ControlPlaneCredentialSource, ControlPlaneRequestError, type ExpandOptions, type ISecretProvider, LocalProvider, type LocalProviderOptions, type MintCredentialResponse, type MintedAwsCredentials, type MintedBitwardenCredentials, type ParsedSecretRef, type ProviderHealth, type ProviderRegistry, type ResolutionFailure, SecRefParseError, SecRefs, type SecRefsOptions, SecRefsResolutionError, SecretFetchError, type SecretFetchRequest, TtlCache, type TtlCacheOptions, VaultProvider, type VaultProviderOptions, checkReferences, createDefaultProviders, expandKeyValueMap, expandProcessEnv, extractField, isSecretRef, parseEnvFileText, parseSecretRef, recoverTruncatedSecRefs, secRefs, tryParseSecretRef };