@faizahmed/secret-keystore 1.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.
- package/LICENSE +21 -0
- package/README.md +1203 -0
- package/SECURITY.md +505 -0
- package/bin/cli.js +969 -0
- package/package.json +77 -0
- package/src/attestation/attestation-client.js +146 -0
- package/src/attestation/attestation-manager.js +339 -0
- package/src/attestation/cms-unwrap.js +166 -0
- package/src/attestation/index.js +66 -0
- package/src/attestation/key-pair.js +129 -0
- package/src/config.js +130 -0
- package/src/content-operations.js +494 -0
- package/src/errors.js +372 -0
- package/src/index.d.ts +641 -0
- package/src/index.js +438 -0
- package/src/keystore.js +678 -0
- package/src/kms.js +858 -0
- package/src/object-operations.js +232 -0
- package/src/options.js +541 -0
- package/src/path-matcher.js +319 -0
- package/src/rotate.js +92 -0
- package/src/yaml-utils.js +265 -0
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @faizahmed/secret-keystore - TypeScript Definitions
|
|
3
|
+
*
|
|
4
|
+
* Version 4.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Declare Buffer for environments without @types/node
|
|
8
|
+
declare global {
|
|
9
|
+
interface BufferConstructor {
|
|
10
|
+
from(data: string | ArrayBuffer | SharedArrayBuffer | Uint8Array, encoding?: string): Buffer;
|
|
11
|
+
}
|
|
12
|
+
interface Buffer extends Uint8Array {
|
|
13
|
+
toString(encoding?: string): string;
|
|
14
|
+
}
|
|
15
|
+
// eslint-disable-next-line no-var
|
|
16
|
+
var Buffer: BufferConstructor;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
// AWS OPTIONS
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
23
|
+
export interface AwsCredentials {
|
|
24
|
+
accessKeyId: string;
|
|
25
|
+
secretAccessKey: string;
|
|
26
|
+
sessionToken?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AwsOptions {
|
|
30
|
+
credentials?: AwsCredentials;
|
|
31
|
+
region?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
35
|
+
// ATTESTATION OPTIONS
|
|
36
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
37
|
+
|
|
38
|
+
export type AttestationDocument = Buffer | (() => Buffer) | (() => Promise<Buffer>);
|
|
39
|
+
|
|
40
|
+
export interface AttestationOptions {
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
required?: boolean;
|
|
43
|
+
fallbackToStandard?: boolean;
|
|
44
|
+
document?: AttestationDocument;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
48
|
+
// LOGGER
|
|
49
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
50
|
+
|
|
51
|
+
export interface Logger {
|
|
52
|
+
debug(message: string, ...args: unknown[]): void;
|
|
53
|
+
info(message: string, ...args: unknown[]): void;
|
|
54
|
+
warn(message: string, ...args: unknown[]): void;
|
|
55
|
+
error(message: string, ...args: unknown[]): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
59
|
+
|
|
60
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
61
|
+
// COMMON OPTIONS
|
|
62
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
63
|
+
|
|
64
|
+
export interface CommonOptions {
|
|
65
|
+
aws?: AwsOptions;
|
|
66
|
+
attestation?: AttestationOptions;
|
|
67
|
+
logger?: Logger;
|
|
68
|
+
logLevel?: LogLevel;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
72
|
+
// ENCRYPT OPTIONS
|
|
73
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
74
|
+
|
|
75
|
+
export type OutputFormat = 'base64' | 'buffer' | 'prefixed';
|
|
76
|
+
|
|
77
|
+
export interface EncryptOptions extends CommonOptions {
|
|
78
|
+
output?: {
|
|
79
|
+
format?: OutputFormat;
|
|
80
|
+
};
|
|
81
|
+
skip?: {
|
|
82
|
+
empty?: boolean;
|
|
83
|
+
alreadyEncrypted?: boolean;
|
|
84
|
+
};
|
|
85
|
+
continueOnError?: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
89
|
+
// DECRYPT OPTIONS
|
|
90
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
91
|
+
|
|
92
|
+
export type InputFormat = 'auto' | 'base64' | 'buffer' | 'prefixed';
|
|
93
|
+
|
|
94
|
+
export interface DecryptOptions extends CommonOptions {
|
|
95
|
+
input?: {
|
|
96
|
+
format?: InputFormat;
|
|
97
|
+
};
|
|
98
|
+
skip?: {
|
|
99
|
+
unencrypted?: boolean;
|
|
100
|
+
};
|
|
101
|
+
validation?: {
|
|
102
|
+
format?: boolean;
|
|
103
|
+
kmsKeyMatch?: boolean;
|
|
104
|
+
};
|
|
105
|
+
continueOnError?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
109
|
+
// PATH SELECTION OPTIONS
|
|
110
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
111
|
+
|
|
112
|
+
export interface PathSelectionOptions {
|
|
113
|
+
paths?: string[];
|
|
114
|
+
patterns?: string[];
|
|
115
|
+
exclude?: {
|
|
116
|
+
paths?: string[];
|
|
117
|
+
patterns?: string[];
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
122
|
+
// CONTENT OPTIONS
|
|
123
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
124
|
+
|
|
125
|
+
export interface ContentOptions {
|
|
126
|
+
preserve?: {
|
|
127
|
+
comments?: boolean;
|
|
128
|
+
formatting?: boolean;
|
|
129
|
+
anchors?: boolean;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
134
|
+
// KEYSTORE OPTIONS
|
|
135
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
136
|
+
|
|
137
|
+
export interface KeystoreValidationOptions {
|
|
138
|
+
noProcessEnvLeak?: boolean;
|
|
139
|
+
throwOnMissingKey?: boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface KeystoreOptions extends Omit<DecryptOptions, 'validation'>, PathSelectionOptions {
|
|
143
|
+
security?: {
|
|
144
|
+
inMemoryEncryption?: boolean;
|
|
145
|
+
secureWipe?: boolean;
|
|
146
|
+
};
|
|
147
|
+
access?: {
|
|
148
|
+
ttl?: number | null;
|
|
149
|
+
autoRefresh?: boolean;
|
|
150
|
+
accessLimit?: number | null;
|
|
151
|
+
clearOnAccess?: boolean;
|
|
152
|
+
};
|
|
153
|
+
validation?: KeystoreValidationOptions;
|
|
154
|
+
retry?: {
|
|
155
|
+
attempts?: number;
|
|
156
|
+
delay?: number;
|
|
157
|
+
backoff?: 'linear' | 'exponential';
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
162
|
+
// RESULT TYPES
|
|
163
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
164
|
+
|
|
165
|
+
export interface FailedItem {
|
|
166
|
+
key: string;
|
|
167
|
+
error: Error;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface FailedPath {
|
|
171
|
+
path: string;
|
|
172
|
+
error: Error;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface ValuesResult {
|
|
176
|
+
values: Record<string, string>;
|
|
177
|
+
encrypted?: string[];
|
|
178
|
+
decrypted?: string[];
|
|
179
|
+
skipped: string[];
|
|
180
|
+
failed: FailedItem[];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ObjectResult {
|
|
184
|
+
object: Record<string, unknown>;
|
|
185
|
+
encrypted?: string[];
|
|
186
|
+
decrypted?: string[];
|
|
187
|
+
skipped: string[];
|
|
188
|
+
failed: FailedPath[];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface ContentResult {
|
|
192
|
+
content: string;
|
|
193
|
+
encrypted?: string[];
|
|
194
|
+
decrypted?: string[];
|
|
195
|
+
skipped: string[];
|
|
196
|
+
failed: FailedItem[];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
200
|
+
// PARSED ENV ENTRY
|
|
201
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
202
|
+
|
|
203
|
+
export interface ParsedEnvEntry {
|
|
204
|
+
type: 'empty' | 'comment' | 'keyvalue' | 'other';
|
|
205
|
+
key?: string;
|
|
206
|
+
value?: string;
|
|
207
|
+
inlineComment?: string;
|
|
208
|
+
raw: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
212
|
+
// CORE KMS OPERATIONS
|
|
213
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
214
|
+
|
|
215
|
+
/** Encrypt a single value using AWS KMS */
|
|
216
|
+
export function encryptKMSValue(
|
|
217
|
+
plaintext: string,
|
|
218
|
+
kmsKeyId: string,
|
|
219
|
+
options?: EncryptOptions
|
|
220
|
+
): Promise<string | Buffer>;
|
|
221
|
+
|
|
222
|
+
/** Decrypt a single value using AWS KMS */
|
|
223
|
+
export function decryptKMSValue(
|
|
224
|
+
ciphertext: string | Buffer,
|
|
225
|
+
kmsKeyId: string,
|
|
226
|
+
options?: DecryptOptions
|
|
227
|
+
): Promise<string>;
|
|
228
|
+
|
|
229
|
+
/** Encrypt multiple key-value pairs using AWS KMS */
|
|
230
|
+
export function encryptKMSValues(
|
|
231
|
+
values: Record<string, string>,
|
|
232
|
+
kmsKeyId: string,
|
|
233
|
+
options?: EncryptOptions
|
|
234
|
+
): Promise<ValuesResult>;
|
|
235
|
+
|
|
236
|
+
/** Decrypt multiple key-value pairs using AWS KMS */
|
|
237
|
+
export function decryptKMSValues(
|
|
238
|
+
values: Record<string, string>,
|
|
239
|
+
kmsKeyId: string,
|
|
240
|
+
options?: DecryptOptions
|
|
241
|
+
): Promise<ValuesResult>;
|
|
242
|
+
|
|
243
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
244
|
+
// FORMAT HELPERS
|
|
245
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
246
|
+
|
|
247
|
+
export function isEncryptedFormat(value: string): boolean;
|
|
248
|
+
export function isKmsCiphertext(value: string): boolean;
|
|
249
|
+
export function isAlreadyEncrypted(value: string): boolean;
|
|
250
|
+
export function isEnvelopeFormat(buf: Buffer): boolean;
|
|
251
|
+
export function wrapCiphertext(ciphertext: string): string;
|
|
252
|
+
export function unwrapCiphertext(value: string): string;
|
|
253
|
+
export function maskKmsKeyId(keyId: string): string;
|
|
254
|
+
|
|
255
|
+
export const ENCRYPTED_PREFIX: string;
|
|
256
|
+
export const ENCRYPTED_SUFFIX: string;
|
|
257
|
+
|
|
258
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
259
|
+
// PATH MATCHING
|
|
260
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
261
|
+
|
|
262
|
+
export function getByPath<T = unknown>(obj: Record<string, unknown>, path: string): T | undefined;
|
|
263
|
+
export function setByPath<T extends Record<string, unknown>>(obj: T, path: string, value: unknown): T;
|
|
264
|
+
export function getAllPaths(obj: Record<string, unknown>, prefix?: string): string[];
|
|
265
|
+
export function matchesPattern(path: string, pattern: string): boolean;
|
|
266
|
+
export function filterPaths(allPaths: string[], options?: PathSelectionOptions): string[];
|
|
267
|
+
export function transformAtPaths(
|
|
268
|
+
obj: Record<string, unknown>,
|
|
269
|
+
paths: string[],
|
|
270
|
+
transformer: (value: unknown, path: string) => Promise<unknown>,
|
|
271
|
+
options?: { continueOnError?: boolean }
|
|
272
|
+
): Promise<{
|
|
273
|
+
object: Record<string, unknown>;
|
|
274
|
+
transformed: string[];
|
|
275
|
+
skipped: string[];
|
|
276
|
+
failed: FailedPath[];
|
|
277
|
+
}>;
|
|
278
|
+
|
|
279
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
280
|
+
// OBJECT-BASED OPERATIONS
|
|
281
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
282
|
+
|
|
283
|
+
export interface EncryptObjectOptions extends EncryptOptions, PathSelectionOptions {}
|
|
284
|
+
export interface DecryptObjectOptions extends DecryptOptions, PathSelectionOptions {}
|
|
285
|
+
|
|
286
|
+
/** Encrypt values at selected paths in a nested object using AWS KMS */
|
|
287
|
+
export function encryptKMSObject(
|
|
288
|
+
obj: Record<string, unknown>,
|
|
289
|
+
kmsKeyId: string,
|
|
290
|
+
options?: EncryptObjectOptions
|
|
291
|
+
): Promise<ObjectResult>;
|
|
292
|
+
|
|
293
|
+
/** Decrypt values at selected paths in a nested object using AWS KMS */
|
|
294
|
+
export function decryptKMSObject(
|
|
295
|
+
obj: Record<string, unknown>,
|
|
296
|
+
kmsKeyId: string,
|
|
297
|
+
options?: DecryptObjectOptions
|
|
298
|
+
): Promise<ObjectResult>;
|
|
299
|
+
|
|
300
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
301
|
+
// CONTENT-BASED OPERATIONS
|
|
302
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
303
|
+
|
|
304
|
+
export interface EncryptContentOptions extends EncryptOptions, PathSelectionOptions, ContentOptions {}
|
|
305
|
+
export interface DecryptContentOptions extends DecryptOptions, PathSelectionOptions, ContentOptions {}
|
|
306
|
+
|
|
307
|
+
/** Encrypt .env content string using AWS KMS */
|
|
308
|
+
export function encryptKMSEnvContent(
|
|
309
|
+
content: string,
|
|
310
|
+
kmsKeyId: string,
|
|
311
|
+
options?: EncryptContentOptions
|
|
312
|
+
): Promise<ContentResult>;
|
|
313
|
+
|
|
314
|
+
/** Decrypt .env content string using AWS KMS */
|
|
315
|
+
export function decryptKMSEnvContent(
|
|
316
|
+
content: string,
|
|
317
|
+
kmsKeyId: string,
|
|
318
|
+
options?: DecryptContentOptions
|
|
319
|
+
): Promise<ContentResult>;
|
|
320
|
+
|
|
321
|
+
export function parseEnvContent(content: string): ParsedEnvEntry[];
|
|
322
|
+
export function reconstructEnvContent(parsed: ParsedEnvEntry[]): string;
|
|
323
|
+
|
|
324
|
+
/** Encrypt JSON content string using AWS KMS */
|
|
325
|
+
export function encryptKMSJsonContent(
|
|
326
|
+
content: string,
|
|
327
|
+
kmsKeyId: string,
|
|
328
|
+
options?: EncryptContentOptions
|
|
329
|
+
): Promise<ContentResult>;
|
|
330
|
+
|
|
331
|
+
/** Decrypt JSON content string using AWS KMS */
|
|
332
|
+
export function decryptKMSJsonContent(
|
|
333
|
+
content: string,
|
|
334
|
+
kmsKeyId: string,
|
|
335
|
+
options?: DecryptContentOptions
|
|
336
|
+
): Promise<ContentResult>;
|
|
337
|
+
|
|
338
|
+
/** Encrypt YAML content string using AWS KMS */
|
|
339
|
+
export function encryptKMSYamlContent(
|
|
340
|
+
content: string,
|
|
341
|
+
kmsKeyId: string,
|
|
342
|
+
options?: EncryptContentOptions
|
|
343
|
+
): Promise<ContentResult>;
|
|
344
|
+
|
|
345
|
+
/** Decrypt YAML content string using AWS KMS */
|
|
346
|
+
export function decryptKMSYamlContent(
|
|
347
|
+
content: string,
|
|
348
|
+
kmsKeyId: string,
|
|
349
|
+
options?: DecryptContentOptions
|
|
350
|
+
): Promise<ContentResult>;
|
|
351
|
+
|
|
352
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
353
|
+
// YAML UTILITIES
|
|
354
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
355
|
+
|
|
356
|
+
/** Check if js-yaml is installed (for complex YAML support) */
|
|
357
|
+
export function isJsYamlAvailable(): boolean;
|
|
358
|
+
|
|
359
|
+
/** Parse YAML content to object (uses js-yaml if available, falls back to simple parser) */
|
|
360
|
+
export function parseYaml(content: string): Record<string, unknown>;
|
|
361
|
+
|
|
362
|
+
/** Serialize object to YAML string (uses js-yaml if available) */
|
|
363
|
+
export function serializeYaml(obj: Record<string, unknown>): string;
|
|
364
|
+
|
|
365
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
366
|
+
// OPTIONS & DEFAULTS
|
|
367
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
368
|
+
|
|
369
|
+
export const RESERVED_KEYS: string[];
|
|
370
|
+
export const DEFAULT_AWS_OPTIONS: AwsOptions;
|
|
371
|
+
export const DEFAULT_ATTESTATION_OPTIONS: AttestationOptions;
|
|
372
|
+
export const DEFAULT_COMMON_OPTIONS: CommonOptions;
|
|
373
|
+
export const DEFAULT_ENCRYPT_OPTIONS: EncryptOptions;
|
|
374
|
+
export const DEFAULT_DECRYPT_OPTIONS: DecryptOptions;
|
|
375
|
+
export const DEFAULT_PATH_SELECTION_OPTIONS: PathSelectionOptions;
|
|
376
|
+
export const DEFAULT_CONTENT_OPTIONS: ContentOptions;
|
|
377
|
+
export const DEFAULT_KEYSTORE_OPTIONS: KeystoreOptions;
|
|
378
|
+
|
|
379
|
+
export function validateKmsKeyId(kmsKeyId: string): void;
|
|
380
|
+
export function validateAwsOptions(aws: AwsOptions): void;
|
|
381
|
+
export function validateAttestationOptions(attestation: AttestationOptions): void;
|
|
382
|
+
export function validatePathSelectionOptions(options: PathSelectionOptions): void;
|
|
383
|
+
export function validateCommonOptions(options: CommonOptions): void;
|
|
384
|
+
|
|
385
|
+
export function buildCommonOptions(options?: CommonOptions): CommonOptions;
|
|
386
|
+
export function buildEncryptOptions(options?: EncryptOptions): EncryptOptions;
|
|
387
|
+
export function buildDecryptOptions(options?: DecryptOptions): DecryptOptions;
|
|
388
|
+
export function buildPathSelectionOptions(options?: PathSelectionOptions): PathSelectionOptions;
|
|
389
|
+
export function buildContentOptions(options?: ContentOptions): ContentOptions;
|
|
390
|
+
export function buildKeystoreOptions(options?: KeystoreOptions): KeystoreOptions;
|
|
391
|
+
export function buildAwsSdkOptions(options?: { aws?: AwsOptions }): Record<string, unknown>;
|
|
392
|
+
|
|
393
|
+
export function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T;
|
|
394
|
+
export function createLogger(baseLogger: Logger | null, logLevel?: LogLevel): Logger;
|
|
395
|
+
|
|
396
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
397
|
+
// ERROR CLASSES
|
|
398
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
399
|
+
|
|
400
|
+
export class SecretKeyStoreError extends Error {
|
|
401
|
+
code: string;
|
|
402
|
+
cause?: Error;
|
|
403
|
+
timestamp: Date;
|
|
404
|
+
constructor(message: string, code: string, cause?: Error);
|
|
405
|
+
toJSON(): Record<string, unknown>;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export class KmsError extends SecretKeyStoreError {
|
|
409
|
+
kmsKeyId: string;
|
|
410
|
+
awsRequestId?: string;
|
|
411
|
+
constructor(message: string, code: string, kmsKeyId: string, cause?: Error);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export class AttestationError extends SecretKeyStoreError {
|
|
415
|
+
constructor(message: string, code: string, cause?: Error);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export class ContentError extends SecretKeyStoreError {
|
|
419
|
+
format?: string;
|
|
420
|
+
constructor(message: string, code: string, format?: string, cause?: Error);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export class PathError extends SecretKeyStoreError {
|
|
424
|
+
path?: string;
|
|
425
|
+
constructor(message: string, code: string, path?: string, cause?: Error);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export class EncryptionError extends SecretKeyStoreError {
|
|
429
|
+
key?: string;
|
|
430
|
+
constructor(message: string, code: string, key?: string, cause?: Error);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export class DecryptionError extends SecretKeyStoreError {
|
|
434
|
+
key?: string;
|
|
435
|
+
constructor(message: string, code: string, key?: string, cause?: Error);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export class KeystoreError extends SecretKeyStoreError {
|
|
439
|
+
constructor(message: string, code: string, cause?: Error);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export class ValidationError extends SecretKeyStoreError {
|
|
443
|
+
field?: string;
|
|
444
|
+
constructor(message: string, code: string, field?: string, cause?: Error);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
448
|
+
// ERROR CODES
|
|
449
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
450
|
+
|
|
451
|
+
export const KMS_ERROR_CODES: {
|
|
452
|
+
KEY_NOT_FOUND: 'KMS_KEY_NOT_FOUND';
|
|
453
|
+
KEY_DISABLED: 'KMS_KEY_DISABLED';
|
|
454
|
+
ACCESS_DENIED: 'KMS_ACCESS_DENIED';
|
|
455
|
+
INVALID_CIPHERTEXT: 'KMS_INVALID_CIPHERTEXT';
|
|
456
|
+
THROTTLED: 'KMS_THROTTLED';
|
|
457
|
+
ENCRYPT_FAILED: 'KMS_ENCRYPT_FAILED';
|
|
458
|
+
DECRYPT_FAILED: 'KMS_DECRYPT_FAILED';
|
|
459
|
+
CONNECTION_ERROR: 'KMS_CONNECTION_ERROR';
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
export const ATTESTATION_ERROR_CODES: {
|
|
463
|
+
DOCUMENT_MISSING: 'ATTESTATION_DOCUMENT_MISSING';
|
|
464
|
+
DOCUMENT_INVALID: 'ATTESTATION_DOCUMENT_INVALID';
|
|
465
|
+
DOCUMENT_EXPIRED: 'ATTESTATION_DOCUMENT_EXPIRED';
|
|
466
|
+
GETTER_FAILED: 'ATTESTATION_GETTER_FAILED';
|
|
467
|
+
NOT_AVAILABLE: 'ATTESTATION_NOT_AVAILABLE';
|
|
468
|
+
RETRY_FAILED: 'ATTESTATION_RETRY_FAILED';
|
|
469
|
+
INIT_FAILED: 'ATTESTATION_INIT_FAILED';
|
|
470
|
+
CMS_UNWRAP_FAILED: 'ATTESTATION_CMS_UNWRAP_FAILED';
|
|
471
|
+
KEYPAIR_GENERATION_FAILED: 'ATTESTATION_KEYPAIR_GENERATION_FAILED';
|
|
472
|
+
ENDPOINT_UNREACHABLE: 'ATTESTATION_ENDPOINT_UNREACHABLE';
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
export const CONTENT_ERROR_CODES: {
|
|
476
|
+
PARSE_FAILED: 'CONTENT_PARSE_FAILED';
|
|
477
|
+
INVALID_FORMAT: 'CONTENT_INVALID_FORMAT';
|
|
478
|
+
EMPTY_CONTENT: 'CONTENT_EMPTY';
|
|
479
|
+
SERIALIZATION_FAILED: 'CONTENT_SERIALIZATION_FAILED';
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
export const PATH_ERROR_CODES: {
|
|
483
|
+
NOT_FOUND: 'PATH_NOT_FOUND';
|
|
484
|
+
INVALID_PATTERN: 'PATH_INVALID_PATTERN';
|
|
485
|
+
ACCESS_DENIED: 'PATH_ACCESS_DENIED';
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
export const ENCRYPTION_ERROR_CODES: {
|
|
489
|
+
FAILED: 'ENCRYPTION_FAILED';
|
|
490
|
+
INVALID_VALUE: 'ENCRYPTION_INVALID_VALUE';
|
|
491
|
+
ALREADY_ENCRYPTED: 'ENCRYPTION_ALREADY_ENCRYPTED';
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
export const DECRYPTION_ERROR_CODES: {
|
|
495
|
+
FAILED: 'DECRYPTION_FAILED';
|
|
496
|
+
INVALID_CIPHERTEXT: 'DECRYPTION_INVALID_CIPHERTEXT';
|
|
497
|
+
NOT_ENCRYPTED: 'DECRYPTION_NOT_ENCRYPTED';
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
export const KEYSTORE_ERROR_CODES: {
|
|
501
|
+
NOT_INITIALIZED: 'KEYSTORE_NOT_INITIALIZED';
|
|
502
|
+
ALREADY_INITIALIZED: 'KEYSTORE_ALREADY_INITIALIZED';
|
|
503
|
+
DESTROYED: 'KEYSTORE_DESTROYED';
|
|
504
|
+
SECRET_NOT_FOUND: 'SECRET_NOT_FOUND';
|
|
505
|
+
SECRET_EXPIRED: 'SECRET_EXPIRED';
|
|
506
|
+
ACCESS_LIMIT_EXCEEDED: 'SECRET_ACCESS_LIMIT_EXCEEDED';
|
|
507
|
+
INITIALIZATION_FAILED: 'KEYSTORE_INITIALIZATION_FAILED';
|
|
508
|
+
REFRESH_FAILED: 'KEYSTORE_REFRESH_FAILED';
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
export const VALIDATION_ERROR_CODES: {
|
|
512
|
+
REQUIRED_FIELD: 'VALIDATION_REQUIRED_FIELD';
|
|
513
|
+
INVALID_TYPE: 'VALIDATION_INVALID_TYPE';
|
|
514
|
+
INVALID_VALUE: 'VALIDATION_INVALID_VALUE';
|
|
515
|
+
INVALID_OPTIONS: 'VALIDATION_INVALID_OPTIONS';
|
|
516
|
+
KMS_KEY_REQUIRED: 'VALIDATION_KMS_KEY_REQUIRED';
|
|
517
|
+
PROCESS_ENV_LEAK: 'VALIDATION_PROCESS_ENV_LEAK';
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
export function isRecoverableError(error: Error): boolean;
|
|
521
|
+
export function createKmsErrorFromAws(awsError: Error, kmsKeyId: string, operation?: string): KmsError;
|
|
522
|
+
|
|
523
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
524
|
+
// RUNTIME KEYSTORE
|
|
525
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
526
|
+
|
|
527
|
+
export type KeystoreSourceType = 'env' | 'json' | 'yaml' | 'object' | 'values';
|
|
528
|
+
|
|
529
|
+
export interface KeystoreSource {
|
|
530
|
+
type: KeystoreSourceType;
|
|
531
|
+
content?: string;
|
|
532
|
+
object?: Record<string, unknown>;
|
|
533
|
+
values?: Record<string, string>;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export interface KeystoreMetadata {
|
|
537
|
+
initialized: boolean;
|
|
538
|
+
destroyed: boolean;
|
|
539
|
+
secretCount: number;
|
|
540
|
+
sourceType: KeystoreSourceType;
|
|
541
|
+
hasTTL: boolean;
|
|
542
|
+
ttl: number | null;
|
|
543
|
+
autoRefresh: boolean;
|
|
544
|
+
inMemoryEncryption: boolean;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export interface AccessStats {
|
|
548
|
+
createdAt: Date;
|
|
549
|
+
lastAccessedAt: Date | null;
|
|
550
|
+
accessCount: number;
|
|
551
|
+
expiresAt: Date | null;
|
|
552
|
+
isExpired: boolean;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export class SecretKeyStore {
|
|
556
|
+
constructor(source: KeystoreSource, kmsKeyId: string, options?: KeystoreOptions);
|
|
557
|
+
initialize(): Promise<void>;
|
|
558
|
+
get(key: string): string | undefined;
|
|
559
|
+
getSection(prefix: string): Record<string, string> | undefined;
|
|
560
|
+
getAll(): Record<string, string>;
|
|
561
|
+
has(key: string): boolean;
|
|
562
|
+
keys(): string[];
|
|
563
|
+
isInitialized(): boolean;
|
|
564
|
+
getMetadata(): KeystoreMetadata;
|
|
565
|
+
getAccessStats(key: string): AccessStats | null;
|
|
566
|
+
refresh(): Promise<void>;
|
|
567
|
+
clear(): void;
|
|
568
|
+
clearKey(key: string): void;
|
|
569
|
+
destroy(): void;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function createSecretKeyStore(
|
|
573
|
+
source: KeystoreSource,
|
|
574
|
+
kmsKeyId: string,
|
|
575
|
+
options?: KeystoreOptions
|
|
576
|
+
): Promise<SecretKeyStore>;
|
|
577
|
+
|
|
578
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
579
|
+
// RUNTIME CONFIG LOADER
|
|
580
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
581
|
+
|
|
582
|
+
export interface ConfigOptions extends KeystoreOptions {
|
|
583
|
+
/** REQUIRED KMS Key ID (explicit; no environment fallback). */
|
|
584
|
+
kmsKeyId: string;
|
|
585
|
+
/** Base directory for file discovery (default: process.cwd()). */
|
|
586
|
+
cwd?: string;
|
|
587
|
+
/** Explicit file path(s); when set, the cascade is skipped. */
|
|
588
|
+
path?: string | string[];
|
|
589
|
+
/** Environment name for the cascade (default: process.env.NODE_ENV). */
|
|
590
|
+
nodeEnv?: string;
|
|
591
|
+
/**
|
|
592
|
+
* Opt-in: also copy decrypted values into process.env (override).
|
|
593
|
+
* Discouraged — widens the RCE blast radius. Default: false.
|
|
594
|
+
*/
|
|
595
|
+
populateProcessEnv?: boolean;
|
|
596
|
+
/** Target object for populateProcessEnv (default: process.env). */
|
|
597
|
+
processEnv?: Record<string, string | undefined>;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Discover and cascade .env files, decrypt their ENC[...] values via KMS, and
|
|
602
|
+
* load everything into an in-memory SecretKeyStore.
|
|
603
|
+
*
|
|
604
|
+
* Cascade order (later overrides earlier):
|
|
605
|
+
* .env → .env.local → .env.<NODE_ENV> → .env.<NODE_ENV>.local
|
|
606
|
+
*
|
|
607
|
+
* Decrypted values live only in the returned store — never on disk, and never
|
|
608
|
+
* in process.env unless populateProcessEnv is explicitly enabled.
|
|
609
|
+
*/
|
|
610
|
+
export function config(options: ConfigOptions): Promise<SecretKeyStore>;
|
|
611
|
+
|
|
612
|
+
/** Resolve the ordered list of existing .env files for the cascade. */
|
|
613
|
+
export function resolveEnvFiles(options?: {
|
|
614
|
+
cwd?: string;
|
|
615
|
+
path?: string | string[];
|
|
616
|
+
nodeEnv?: string;
|
|
617
|
+
}): string[];
|
|
618
|
+
|
|
619
|
+
/** Merge parsed .env files into a single key→value map (later files win). */
|
|
620
|
+
export function mergeEnvFiles(files: string[]): {
|
|
621
|
+
merged: Record<string, string>;
|
|
622
|
+
used: string[];
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
626
|
+
// KEY ROTATION
|
|
627
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
628
|
+
|
|
629
|
+
export type ContentFormat = 'env' | 'json' | 'yaml';
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Re-encrypt the already-encrypted values in a config file under a new KMS Key
|
|
633
|
+
* ID (decrypting with the old key first). Plaintext values are left untouched.
|
|
634
|
+
*/
|
|
635
|
+
export function rotateKMSContent(
|
|
636
|
+
content: string,
|
|
637
|
+
format: ContentFormat,
|
|
638
|
+
oldKmsKeyId: string,
|
|
639
|
+
newKmsKeyId: string,
|
|
640
|
+
options?: EncryptContentOptions & DecryptContentOptions
|
|
641
|
+
): Promise<{ content: string; rotated: string[] }>;
|