@nodetool-ai/security 0.7.0-rc.11
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/dist/crypto.d.ts +79 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +205 -0
- package/dist/crypto.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/master-key.d.ts +111 -0
- package/dist/master-key.d.ts.map +1 -0
- package/dist/master-key.js +255 -0
- package/dist/master-key.js.map +1 -0
- package/dist/startup-checks.d.ts +12 -0
- package/dist/startup-checks.d.ts.map +1 -0
- package/dist/startup-checks.js +45 -0
- package/dist/startup-checks.js.map +1 -0
- package/package.json +46 -0
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic utilities for secret encryption and decryption.
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for encrypting and decrypting secrets using
|
|
5
|
+
* AES-256-GCM symmetric encryption with PBKDF2 key derivation.
|
|
6
|
+
*
|
|
7
|
+
* The master key is combined with user_id (as salt) to derive user-specific
|
|
8
|
+
* encryption keys via PBKDF2-SHA256 with 100,000 iterations.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Generate a new random master key.
|
|
12
|
+
*
|
|
13
|
+
* @returns A base64-encoded 32-byte master key string.
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateMasterKey(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Derive an encryption key from the master key using user_id as salt.
|
|
18
|
+
*
|
|
19
|
+
* This ensures each user's secrets are encrypted with a unique derived key,
|
|
20
|
+
* providing isolation between users even if the master key is compromised.
|
|
21
|
+
*
|
|
22
|
+
* @param masterKey - The master key (base64-encoded or raw string).
|
|
23
|
+
* @param userId - The user ID to use as salt for key derivation.
|
|
24
|
+
* @returns A 32-byte derived key as a Buffer.
|
|
25
|
+
*/
|
|
26
|
+
export declare function deriveKey(masterKey: string, userId: string): Buffer;
|
|
27
|
+
/**
|
|
28
|
+
* Encrypt a plaintext value using AES-256-GCM.
|
|
29
|
+
*
|
|
30
|
+
* The output format is: base64(iv || ciphertext || authTag)
|
|
31
|
+
*
|
|
32
|
+
* @param masterKey - The master key to use for encryption.
|
|
33
|
+
* @param userId - The user ID to use as salt for key derivation.
|
|
34
|
+
* @param plaintext - The plaintext string to encrypt.
|
|
35
|
+
* @returns The encrypted value as a base64-encoded string.
|
|
36
|
+
*/
|
|
37
|
+
export declare function encrypt(masterKey: string, userId: string, plaintext: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Decrypt an encrypted value using AES-256-GCM.
|
|
40
|
+
*
|
|
41
|
+
* @param masterKey - The master key to use for decryption.
|
|
42
|
+
* @param userId - The user ID to use as salt for key derivation.
|
|
43
|
+
* @param encryptedValue - The encrypted value as a base64-encoded string.
|
|
44
|
+
* @returns The decrypted plaintext string.
|
|
45
|
+
* @throws {Error} If the master key is incorrect or the data is corrupted.
|
|
46
|
+
*/
|
|
47
|
+
export declare function decrypt(masterKey: string, userId: string, encryptedValue: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Decrypt a Fernet token encrypted by the Python side.
|
|
50
|
+
*
|
|
51
|
+
* Python uses: PBKDF2(master_key_bytes, user_id, 100k, 32) → fernet_key
|
|
52
|
+
* Fernet = AES-128-CBC + HMAC-SHA256, token = base64url(ver|ts|iv|ct|hmac)
|
|
53
|
+
*
|
|
54
|
+
* @param masterKey - The master key as a base64 or base64url string.
|
|
55
|
+
* @param userId - The user ID used as PBKDF2 salt.
|
|
56
|
+
* @param encryptedValue - The Fernet token (base64url encoded).
|
|
57
|
+
*/
|
|
58
|
+
export declare function decryptFernet(masterKey: string, userId: string, encryptedValue: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Encrypt a value using Fernet (Python-compatible).
|
|
61
|
+
*
|
|
62
|
+
* Produces a token that Python's cryptography.Fernet can decrypt.
|
|
63
|
+
* Format: base64url(version(1) | timestamp(8) | iv(16) | ciphertext(N) | hmac(32))
|
|
64
|
+
*
|
|
65
|
+
* @param masterKey - The master key as a base64 or utf-8 string.
|
|
66
|
+
* @param userId - The user ID used as PBKDF2 salt.
|
|
67
|
+
* @param plaintext - The plaintext to encrypt.
|
|
68
|
+
*/
|
|
69
|
+
export declare function encryptFernet(masterKey: string, userId: string, plaintext: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Check if a master key is valid by attempting to decrypt a test value.
|
|
72
|
+
*
|
|
73
|
+
* @param masterKey - The master key to validate.
|
|
74
|
+
* @param testEncryptedValue - An encrypted test value to decrypt.
|
|
75
|
+
* @param userId - The user ID used as salt for the test value.
|
|
76
|
+
* @returns True if the master key is valid, false otherwise.
|
|
77
|
+
*/
|
|
78
|
+
export declare function isValidMasterKey(masterKey: string, testEncryptedValue: string, userId: string): boolean;
|
|
79
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsBH;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,MAAM,CAcR;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,MAAM,CA8BR;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,MAAM,CAiDR;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,MAAM,CA+CR;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,MAAM,EAC1B,MAAM,EAAE,MAAM,GACb,OAAO,CAOT"}
|
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic utilities for secret encryption and decryption.
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for encrypting and decrypting secrets using
|
|
5
|
+
* AES-256-GCM symmetric encryption with PBKDF2 key derivation.
|
|
6
|
+
*
|
|
7
|
+
* The master key is combined with user_id (as salt) to derive user-specific
|
|
8
|
+
* encryption keys via PBKDF2-SHA256 with 100,000 iterations.
|
|
9
|
+
*/
|
|
10
|
+
import { randomBytes, pbkdf2Sync, createCipheriv, createDecipheriv, createHmac, timingSafeEqual } from "node:crypto";
|
|
11
|
+
import { createLogger } from "@nodetool-ai/config";
|
|
12
|
+
const log = createLogger("nodetool.security.crypto");
|
|
13
|
+
// Fernet token version byte
|
|
14
|
+
const FERNET_VERSION = 0x80;
|
|
15
|
+
const PBKDF2_ITERATIONS = 100_000;
|
|
16
|
+
const KEY_LENGTH = 32; // 256 bits for AES-256
|
|
17
|
+
const IV_LENGTH = 12; // 96 bits for GCM
|
|
18
|
+
const AUTH_TAG_LENGTH = 16; // 128 bits
|
|
19
|
+
/**
|
|
20
|
+
* Generate a new random master key.
|
|
21
|
+
*
|
|
22
|
+
* @returns A base64-encoded 32-byte master key string.
|
|
23
|
+
*/
|
|
24
|
+
export function generateMasterKey() {
|
|
25
|
+
return randomBytes(KEY_LENGTH).toString("base64");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Derive an encryption key from the master key using user_id as salt.
|
|
29
|
+
*
|
|
30
|
+
* This ensures each user's secrets are encrypted with a unique derived key,
|
|
31
|
+
* providing isolation between users even if the master key is compromised.
|
|
32
|
+
*
|
|
33
|
+
* @param masterKey - The master key (base64-encoded or raw string).
|
|
34
|
+
* @param userId - The user ID to use as salt for key derivation.
|
|
35
|
+
* @returns A 32-byte derived key as a Buffer.
|
|
36
|
+
*/
|
|
37
|
+
export function deriveKey(masterKey, userId) {
|
|
38
|
+
const salt = Buffer.from(userId, "utf-8");
|
|
39
|
+
return pbkdf2Sync(masterKey, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Encrypt a plaintext value using AES-256-GCM.
|
|
43
|
+
*
|
|
44
|
+
* The output format is: base64(iv || ciphertext || authTag)
|
|
45
|
+
*
|
|
46
|
+
* @param masterKey - The master key to use for encryption.
|
|
47
|
+
* @param userId - The user ID to use as salt for key derivation.
|
|
48
|
+
* @param plaintext - The plaintext string to encrypt.
|
|
49
|
+
* @returns The encrypted value as a base64-encoded string.
|
|
50
|
+
*/
|
|
51
|
+
export function encrypt(masterKey, userId, plaintext) {
|
|
52
|
+
const key = deriveKey(masterKey, userId);
|
|
53
|
+
const iv = randomBytes(IV_LENGTH);
|
|
54
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
55
|
+
const encrypted = Buffer.concat([
|
|
56
|
+
cipher.update(plaintext, "utf-8"),
|
|
57
|
+
cipher.final()
|
|
58
|
+
]);
|
|
59
|
+
const authTag = cipher.getAuthTag();
|
|
60
|
+
// Pack: iv (12) + ciphertext (variable) + authTag (16)
|
|
61
|
+
const packed = Buffer.concat([iv, encrypted, authTag]);
|
|
62
|
+
return packed.toString("base64");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Decrypt an encrypted value using AES-256-GCM.
|
|
66
|
+
*
|
|
67
|
+
* @param masterKey - The master key to use for decryption.
|
|
68
|
+
* @param userId - The user ID to use as salt for key derivation.
|
|
69
|
+
* @param encryptedValue - The encrypted value as a base64-encoded string.
|
|
70
|
+
* @returns The decrypted plaintext string.
|
|
71
|
+
* @throws {Error} If the master key is incorrect or the data is corrupted.
|
|
72
|
+
*/
|
|
73
|
+
export function decrypt(masterKey, userId, encryptedValue) {
|
|
74
|
+
const key = deriveKey(masterKey, userId);
|
|
75
|
+
const packed = Buffer.from(encryptedValue, "base64");
|
|
76
|
+
if (packed.length < IV_LENGTH + AUTH_TAG_LENGTH) {
|
|
77
|
+
throw new Error("Failed to decrypt secret: data too short");
|
|
78
|
+
}
|
|
79
|
+
const iv = packed.subarray(0, IV_LENGTH);
|
|
80
|
+
const authTag = packed.subarray(packed.length - AUTH_TAG_LENGTH);
|
|
81
|
+
const ciphertext = packed.subarray(IV_LENGTH, packed.length - AUTH_TAG_LENGTH);
|
|
82
|
+
try {
|
|
83
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
84
|
+
decipher.setAuthTag(authTag);
|
|
85
|
+
const decrypted = Buffer.concat([
|
|
86
|
+
decipher.update(ciphertext),
|
|
87
|
+
decipher.final()
|
|
88
|
+
]);
|
|
89
|
+
return decrypted.toString("utf-8");
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
log.debug("AES-GCM decryption failed (may fall back to Fernet)", {
|
|
93
|
+
error: String(err)
|
|
94
|
+
});
|
|
95
|
+
throw new Error("Failed to decrypt secret");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Decrypt a Fernet token encrypted by the Python side.
|
|
100
|
+
*
|
|
101
|
+
* Python uses: PBKDF2(master_key_bytes, user_id, 100k, 32) → fernet_key
|
|
102
|
+
* Fernet = AES-128-CBC + HMAC-SHA256, token = base64url(ver|ts|iv|ct|hmac)
|
|
103
|
+
*
|
|
104
|
+
* @param masterKey - The master key as a base64 or base64url string.
|
|
105
|
+
* @param userId - The user ID used as PBKDF2 salt.
|
|
106
|
+
* @param encryptedValue - The Fernet token (base64url encoded).
|
|
107
|
+
*/
|
|
108
|
+
export function decryptFernet(masterKey, userId, encryptedValue) {
|
|
109
|
+
// Python uses master_key.encode() — the UTF-8 bytes of the base64 string itself as PBKDF2 password
|
|
110
|
+
const masterKeyBytes = Buffer.from(masterKey, "utf-8");
|
|
111
|
+
const salt = Buffer.from(userId, "utf-8");
|
|
112
|
+
const derived = pbkdf2Sync(masterKeyBytes, salt, PBKDF2_ITERATIONS, 32, "sha256");
|
|
113
|
+
// Fernet splits the 32-byte derived key: first 16 = HMAC key, last 16 = AES-128 key
|
|
114
|
+
const hmacKey = derived.subarray(0, 16);
|
|
115
|
+
const aesKey = derived.subarray(16, 32);
|
|
116
|
+
// Decode Fernet token from base64url
|
|
117
|
+
const b64token = encryptedValue.replace(/-/g, "+").replace(/_/g, "/");
|
|
118
|
+
const token = Buffer.from(b64token + "=".repeat((4 - (b64token.length % 4)) % 4), "base64");
|
|
119
|
+
// Token layout: version(1) + timestamp(8) + iv(16) + ciphertext(N) + hmac(32)
|
|
120
|
+
if (token.length < 1 + 8 + 16 + 32) {
|
|
121
|
+
throw new Error("Invalid Fernet token: too short");
|
|
122
|
+
}
|
|
123
|
+
if (token[0] !== 0x80) {
|
|
124
|
+
throw new Error(`Invalid Fernet token version: ${token[0]}`);
|
|
125
|
+
}
|
|
126
|
+
const body = token.subarray(0, token.length - 32);
|
|
127
|
+
const tokenHmac = token.subarray(token.length - 32);
|
|
128
|
+
const iv = body.subarray(9, 25); // 1 + 8 = 9
|
|
129
|
+
const ciphertext = body.subarray(25);
|
|
130
|
+
// Verify HMAC-SHA256
|
|
131
|
+
const expectedHmac = createHmac("sha256", hmacKey).update(body).digest();
|
|
132
|
+
if (!timingSafeEqual(expectedHmac, tokenHmac)) {
|
|
133
|
+
throw new Error("Invalid Fernet token: HMAC mismatch");
|
|
134
|
+
}
|
|
135
|
+
// Decrypt AES-128-CBC and strip PKCS7 padding
|
|
136
|
+
const decipher = createDecipheriv("aes-128-cbc", aesKey, iv);
|
|
137
|
+
const decrypted = Buffer.concat([
|
|
138
|
+
decipher.update(ciphertext),
|
|
139
|
+
decipher.final()
|
|
140
|
+
]);
|
|
141
|
+
return decrypted.toString("utf-8");
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Encrypt a value using Fernet (Python-compatible).
|
|
145
|
+
*
|
|
146
|
+
* Produces a token that Python's cryptography.Fernet can decrypt.
|
|
147
|
+
* Format: base64url(version(1) | timestamp(8) | iv(16) | ciphertext(N) | hmac(32))
|
|
148
|
+
*
|
|
149
|
+
* @param masterKey - The master key as a base64 or utf-8 string.
|
|
150
|
+
* @param userId - The user ID used as PBKDF2 salt.
|
|
151
|
+
* @param plaintext - The plaintext to encrypt.
|
|
152
|
+
*/
|
|
153
|
+
export function encryptFernet(masterKey, userId, plaintext) {
|
|
154
|
+
const masterKeyBytes = Buffer.from(masterKey, "utf-8");
|
|
155
|
+
const salt = Buffer.from(userId, "utf-8");
|
|
156
|
+
const derived = pbkdf2Sync(masterKeyBytes, salt, PBKDF2_ITERATIONS, 32, "sha256");
|
|
157
|
+
const hmacKey = derived.subarray(0, 16);
|
|
158
|
+
const aesKey = derived.subarray(16, 32);
|
|
159
|
+
const iv = randomBytes(16);
|
|
160
|
+
// Timestamp: seconds since epoch as big-endian uint64
|
|
161
|
+
const timestamp = BigInt(Math.floor(Date.now() / 1000));
|
|
162
|
+
const tsBuf = Buffer.alloc(8);
|
|
163
|
+
tsBuf.writeBigUInt64BE(timestamp);
|
|
164
|
+
// Pad plaintext to 16-byte boundary (PKCS7)
|
|
165
|
+
const plaintextBuf = Buffer.from(plaintext, "utf-8");
|
|
166
|
+
const padLen = 16 - (plaintextBuf.length % 16);
|
|
167
|
+
const padded = Buffer.concat([plaintextBuf, Buffer.alloc(padLen, padLen)]);
|
|
168
|
+
const cipher = createCipheriv("aes-128-cbc", aesKey, iv);
|
|
169
|
+
cipher.setAutoPadding(false);
|
|
170
|
+
const ciphertext = Buffer.concat([cipher.update(padded), cipher.final()]);
|
|
171
|
+
// Build body: version(1) + timestamp(8) + iv(16) + ciphertext
|
|
172
|
+
const body = Buffer.concat([
|
|
173
|
+
Buffer.from([FERNET_VERSION]),
|
|
174
|
+
tsBuf,
|
|
175
|
+
iv,
|
|
176
|
+
ciphertext
|
|
177
|
+
]);
|
|
178
|
+
// HMAC-SHA256 over body
|
|
179
|
+
const hmac = createHmac("sha256", hmacKey).update(body).digest();
|
|
180
|
+
const token = Buffer.concat([body, hmac]);
|
|
181
|
+
// Encode as base64url (no padding)
|
|
182
|
+
return token
|
|
183
|
+
.toString("base64")
|
|
184
|
+
.replace(/\+/g, "-")
|
|
185
|
+
.replace(/\//g, "_")
|
|
186
|
+
.replace(/=/g, "");
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check if a master key is valid by attempting to decrypt a test value.
|
|
190
|
+
*
|
|
191
|
+
* @param masterKey - The master key to validate.
|
|
192
|
+
* @param testEncryptedValue - An encrypted test value to decrypt.
|
|
193
|
+
* @param userId - The user ID used as salt for the test value.
|
|
194
|
+
* @returns True if the master key is valid, false otherwise.
|
|
195
|
+
*/
|
|
196
|
+
export function isValidMasterKey(masterKey, testEncryptedValue, userId) {
|
|
197
|
+
try {
|
|
198
|
+
decrypt(masterKey, userId, testEncryptedValue);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { generateMasterKey, deriveKey, encrypt, decrypt, decryptFernet, encryptFernet, isValidMasterKey } from "./crypto.js";
|
|
2
|
+
export { getMasterKey, initMasterKey, clearMasterKeyCache, setMasterKey, setMasterKeyPersistent, deleteMasterKey, isUsingEnvKey, isUsingAwsKey, setKeytarLoader, resetKeytarLoader, KeychainAccessError } from "./master-key.js";
|
|
3
|
+
export { runStartupChecks, type StartupCheckResult } from "./startup-checks.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,OAAO,EACP,OAAO,EACP,aAAa,EACb,aAAa,EACb,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { generateMasterKey, deriveKey, encrypt, decrypt, decryptFernet, encryptFernet, isValidMasterKey } from "./crypto.js";
|
|
2
|
+
export { getMasterKey, initMasterKey, clearMasterKeyCache, setMasterKey, setMasterKeyPersistent, deleteMasterKey, isUsingEnvKey, isUsingAwsKey, setKeytarLoader, resetKeytarLoader, KeychainAccessError } from "./master-key.js";
|
|
3
|
+
export { runStartupChecks } from "./startup-checks.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,OAAO,EACP,OAAO,EACP,aAAa,EACb,aAAa,EACb,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,gBAAgB,EAA2B,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Master key management with keychain and AWS Secrets Manager support.
|
|
3
|
+
*
|
|
4
|
+
* This module manages the master encryption key for secrets, storing it securely
|
|
5
|
+
* in the system keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
|
|
6
|
+
* or AWS Secrets Manager.
|
|
7
|
+
*
|
|
8
|
+
* Key sources (in order of precedence):
|
|
9
|
+
* 1. SECRETS_MASTER_KEY environment variable
|
|
10
|
+
* 2. AWS Secrets Manager (if AWS_SECRETS_MASTER_KEY_NAME env var set)
|
|
11
|
+
* 3. System keychain via keytar
|
|
12
|
+
* 4. Auto-generated key (persisted to keychain when available)
|
|
13
|
+
*
|
|
14
|
+
* NOTE on encryption compatibility: TS uses AES-256-GCM while Python uses Fernet.
|
|
15
|
+
* Secrets encrypted in one runtime cannot be decrypted in the other.
|
|
16
|
+
* New secrets must be created via the appropriate runtime's endpoints.
|
|
17
|
+
*/
|
|
18
|
+
/** Minimal keytar interface for the methods we use. */
|
|
19
|
+
interface KeytarModule {
|
|
20
|
+
getPassword(service: string, account: string): Promise<string | null>;
|
|
21
|
+
setPassword(service: string, account: string, password: string): Promise<void>;
|
|
22
|
+
deletePassword(service: string, account: string): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Thrown when the system keychain cannot be accessed. Callers can detect this
|
|
26
|
+
* specifically (vs other startup failures) to decide whether re-prompting the
|
|
27
|
+
* user for keychain access makes sense.
|
|
28
|
+
*/
|
|
29
|
+
export declare class KeychainAccessError extends Error {
|
|
30
|
+
constructor(message: string);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Replace the keytar implementation (for testing / dependency injection).
|
|
34
|
+
*/
|
|
35
|
+
export declare function setKeytarLoader(keytarImpl: KeytarModule): void;
|
|
36
|
+
/**
|
|
37
|
+
* Restore the default keytar implementation (for testing).
|
|
38
|
+
*/
|
|
39
|
+
export declare function resetKeytarLoader(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get the master encryption key (synchronous).
|
|
42
|
+
*
|
|
43
|
+
* Checks sources in order:
|
|
44
|
+
* 1. Cached key (if previously retrieved, e.g. via initMasterKey)
|
|
45
|
+
* 2. SECRETS_MASTER_KEY environment variable
|
|
46
|
+
* 3. Auto-generates a new key (will not persist unless initMasterKey was called)
|
|
47
|
+
*
|
|
48
|
+
* For full resolution including keychain and AWS, call initMasterKey() once at startup.
|
|
49
|
+
*
|
|
50
|
+
* @returns The master key as a base64-encoded string.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getMasterKey(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Initialize the master key from all available sources (async).
|
|
55
|
+
*
|
|
56
|
+
* Should be called once at application startup to resolve the master key
|
|
57
|
+
* from keychain or AWS Secrets Manager. After this call, getMasterKey()
|
|
58
|
+
* will return the resolved key synchronously.
|
|
59
|
+
*
|
|
60
|
+
* Resolution order:
|
|
61
|
+
* 1. SECRETS_MASTER_KEY environment variable
|
|
62
|
+
* 2. AWS Secrets Manager (if AWS_SECRETS_MASTER_KEY_NAME is set)
|
|
63
|
+
* 3. System keychain via keytar
|
|
64
|
+
* 4. Auto-generate and persist to keychain
|
|
65
|
+
*
|
|
66
|
+
* @returns The master key as a base64-encoded string.
|
|
67
|
+
*/
|
|
68
|
+
export declare function initMasterKey(): Promise<string>;
|
|
69
|
+
/**
|
|
70
|
+
* Clear the cached master key.
|
|
71
|
+
*
|
|
72
|
+
* Forces the next call to getMasterKey() or initMasterKey() to
|
|
73
|
+
* re-read from environment, AWS, or keychain.
|
|
74
|
+
*/
|
|
75
|
+
export declare function clearMasterKeyCache(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Set a specific master key (useful for testing or migration).
|
|
78
|
+
*
|
|
79
|
+
* @param masterKey - The master key to use.
|
|
80
|
+
*/
|
|
81
|
+
export declare function setMasterKey(masterKey: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Set the master key and persist it to the system keychain.
|
|
84
|
+
*
|
|
85
|
+
* @param masterKey - The master key to set (base64-encoded string).
|
|
86
|
+
* @throws {Error} If keychain write fails.
|
|
87
|
+
*/
|
|
88
|
+
export declare function setMasterKeyPersistent(masterKey: string): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Delete the master key from the system keychain.
|
|
91
|
+
*
|
|
92
|
+
* WARNING: This will make all encrypted secrets inaccessible!
|
|
93
|
+
*
|
|
94
|
+
* @returns True if the key was deleted, false otherwise.
|
|
95
|
+
* @throws {Error} If keychain deletion fails.
|
|
96
|
+
*/
|
|
97
|
+
export declare function deleteMasterKey(): Promise<boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* Check if the master key is being sourced from an environment variable.
|
|
100
|
+
*
|
|
101
|
+
* @returns True if SECRETS_MASTER_KEY environment variable is set.
|
|
102
|
+
*/
|
|
103
|
+
export declare function isUsingEnvKey(): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Check if the master key should be sourced from AWS Secrets Manager.
|
|
106
|
+
*
|
|
107
|
+
* @returns True if AWS_SECRETS_MASTER_KEY_NAME environment variable is set.
|
|
108
|
+
*/
|
|
109
|
+
export declare function isUsingAwsKey(): boolean;
|
|
110
|
+
export {};
|
|
111
|
+
//# sourceMappingURL=master-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"master-key.d.ts","sourceRoot":"","sources":["../src/master-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAgBH,uDAAuD;AACvD,UAAU,YAAY;IACpB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtE,WAAW,CACT,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpE;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAkCD;;GAEG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,YAAY,GAAG,IAAI,CAG9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AAiCD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAkBrC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAuDrD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7E;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAQxD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Master key management with keychain and AWS Secrets Manager support.
|
|
3
|
+
*
|
|
4
|
+
* This module manages the master encryption key for secrets, storing it securely
|
|
5
|
+
* in the system keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
|
|
6
|
+
* or AWS Secrets Manager.
|
|
7
|
+
*
|
|
8
|
+
* Key sources (in order of precedence):
|
|
9
|
+
* 1. SECRETS_MASTER_KEY environment variable
|
|
10
|
+
* 2. AWS Secrets Manager (if AWS_SECRETS_MASTER_KEY_NAME env var set)
|
|
11
|
+
* 3. System keychain via keytar
|
|
12
|
+
* 4. Auto-generated key (persisted to keychain when available)
|
|
13
|
+
*
|
|
14
|
+
* NOTE on encryption compatibility: TS uses AES-256-GCM while Python uses Fernet.
|
|
15
|
+
* Secrets encrypted in one runtime cannot be decrypted in the other.
|
|
16
|
+
* New secrets must be created via the appropriate runtime's endpoints.
|
|
17
|
+
*/
|
|
18
|
+
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
|
|
19
|
+
import { generateMasterKey } from "./crypto.js";
|
|
20
|
+
import { createLogger } from "@nodetool-ai/config";
|
|
21
|
+
const log = createLogger("nodetool.security.master-key");
|
|
22
|
+
const KEYRING_SERVICE = "nodetool";
|
|
23
|
+
const KEYRING_ACCOUNT = "secrets_master_key";
|
|
24
|
+
let cachedMasterKey = null;
|
|
25
|
+
/**
|
|
26
|
+
* Thrown when the system keychain cannot be accessed. Callers can detect this
|
|
27
|
+
* specifically (vs other startup failures) to decide whether re-prompting the
|
|
28
|
+
* user for keychain access makes sense.
|
|
29
|
+
*/
|
|
30
|
+
export class KeychainAccessError extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "KeychainAccessError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function keychainAccessError(message) {
|
|
37
|
+
return new KeychainAccessError(`${message}. Allow NodeTool access to the system keychain when prompted.`);
|
|
38
|
+
}
|
|
39
|
+
/** Lazy-load keytar. Keychain failures are fatal: no generated fallback key. */
|
|
40
|
+
let _keytarResolved = null;
|
|
41
|
+
async function loadKeytar() {
|
|
42
|
+
if (_keytarResolved) {
|
|
43
|
+
return _keytarResolved;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const mod = await import("keytar");
|
|
47
|
+
_keytarResolved = mod.default ?? mod;
|
|
48
|
+
return _keytarResolved;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
+
log.error("keytar native module failed to load. For headless deployments " +
|
|
53
|
+
"(Docker, CI, Linux servers without libsecret) set the SECRETS_MASTER_KEY " +
|
|
54
|
+
"environment variable to a base64-encoded 32-byte key, or set " +
|
|
55
|
+
"AWS_SECRETS_MASTER_KEY_NAME to source the key from AWS Secrets Manager.", { error: message });
|
|
56
|
+
throw keychainAccessError(`Unable to load system keychain backend: ${message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Active keytar implementation (can be overridden in tests). */
|
|
60
|
+
let _keytar = null;
|
|
61
|
+
/**
|
|
62
|
+
* Replace the keytar implementation (for testing / dependency injection).
|
|
63
|
+
*/
|
|
64
|
+
export function setKeytarLoader(keytarImpl) {
|
|
65
|
+
_keytar = keytarImpl;
|
|
66
|
+
_keytarResolved = keytarImpl;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Restore the default keytar implementation (for testing).
|
|
70
|
+
*/
|
|
71
|
+
export function resetKeytarLoader() {
|
|
72
|
+
_keytar = null;
|
|
73
|
+
_keytarResolved = null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Retrieve master key from AWS Secrets Manager.
|
|
77
|
+
*
|
|
78
|
+
* Only attempted if AWS_SECRETS_MASTER_KEY_NAME environment variable is set.
|
|
79
|
+
*/
|
|
80
|
+
async function getFromAwsSecrets(secretName) {
|
|
81
|
+
try {
|
|
82
|
+
const region = process.env["AWS_REGION"] ?? "us-east-1";
|
|
83
|
+
const client = new SecretsManagerClient({ region });
|
|
84
|
+
const response = await client.send(new GetSecretValueCommand({ SecretId: secretName }));
|
|
85
|
+
if (response.SecretString) {
|
|
86
|
+
return response.SecretString;
|
|
87
|
+
}
|
|
88
|
+
if (response.SecretBinary) {
|
|
89
|
+
return Buffer.from(response.SecretBinary).toString("utf-8");
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
95
|
+
log.warn("Master key source failed, trying next", {
|
|
96
|
+
source: "aws",
|
|
97
|
+
error: message
|
|
98
|
+
});
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get the master encryption key (synchronous).
|
|
104
|
+
*
|
|
105
|
+
* Checks sources in order:
|
|
106
|
+
* 1. Cached key (if previously retrieved, e.g. via initMasterKey)
|
|
107
|
+
* 2. SECRETS_MASTER_KEY environment variable
|
|
108
|
+
* 3. Auto-generates a new key (will not persist unless initMasterKey was called)
|
|
109
|
+
*
|
|
110
|
+
* For full resolution including keychain and AWS, call initMasterKey() once at startup.
|
|
111
|
+
*
|
|
112
|
+
* @returns The master key as a base64-encoded string.
|
|
113
|
+
*/
|
|
114
|
+
export function getMasterKey() {
|
|
115
|
+
if (cachedMasterKey !== null) {
|
|
116
|
+
return cachedMasterKey;
|
|
117
|
+
}
|
|
118
|
+
// 1. Check environment variable
|
|
119
|
+
const envKey = process.env["SECRETS_MASTER_KEY"];
|
|
120
|
+
if (envKey) {
|
|
121
|
+
log.debug("Master key source", { source: "env" });
|
|
122
|
+
cachedMasterKey = envKey;
|
|
123
|
+
return envKey;
|
|
124
|
+
}
|
|
125
|
+
// 2. Auto-generate (not persisted without initMasterKey)
|
|
126
|
+
log.debug("Master key source", { source: "generated" });
|
|
127
|
+
const newKey = generateMasterKey();
|
|
128
|
+
cachedMasterKey = newKey;
|
|
129
|
+
return newKey;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Initialize the master key from all available sources (async).
|
|
133
|
+
*
|
|
134
|
+
* Should be called once at application startup to resolve the master key
|
|
135
|
+
* from keychain or AWS Secrets Manager. After this call, getMasterKey()
|
|
136
|
+
* will return the resolved key synchronously.
|
|
137
|
+
*
|
|
138
|
+
* Resolution order:
|
|
139
|
+
* 1. SECRETS_MASTER_KEY environment variable
|
|
140
|
+
* 2. AWS Secrets Manager (if AWS_SECRETS_MASTER_KEY_NAME is set)
|
|
141
|
+
* 3. System keychain via keytar
|
|
142
|
+
* 4. Auto-generate and persist to keychain
|
|
143
|
+
*
|
|
144
|
+
* @returns The master key as a base64-encoded string.
|
|
145
|
+
*/
|
|
146
|
+
export async function initMasterKey() {
|
|
147
|
+
// Return cached key if available
|
|
148
|
+
if (cachedMasterKey !== null) {
|
|
149
|
+
return cachedMasterKey;
|
|
150
|
+
}
|
|
151
|
+
// 1. Check environment variable
|
|
152
|
+
const envKey = process.env["SECRETS_MASTER_KEY"];
|
|
153
|
+
if (envKey) {
|
|
154
|
+
log.debug("Master key source", { source: "env" });
|
|
155
|
+
cachedMasterKey = envKey;
|
|
156
|
+
return envKey;
|
|
157
|
+
}
|
|
158
|
+
// 2. Check AWS Secrets Manager if configured
|
|
159
|
+
const awsSecretName = process.env["AWS_SECRETS_MASTER_KEY_NAME"];
|
|
160
|
+
if (awsSecretName) {
|
|
161
|
+
const awsKey = await getFromAwsSecrets(awsSecretName);
|
|
162
|
+
if (awsKey) {
|
|
163
|
+
log.debug("Master key source", { source: "aws" });
|
|
164
|
+
cachedMasterKey = awsKey;
|
|
165
|
+
return awsKey;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// 3. Try system keychain via keytar
|
|
169
|
+
const keytar = _keytar ?? await loadKeytar();
|
|
170
|
+
try {
|
|
171
|
+
const storedKey = await keytar.getPassword(KEYRING_SERVICE, KEYRING_ACCOUNT);
|
|
172
|
+
if (storedKey) {
|
|
173
|
+
log.debug("Master key source", { source: "keychain" });
|
|
174
|
+
cachedMasterKey = storedKey;
|
|
175
|
+
return storedKey;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
180
|
+
throw keychainAccessError(`Unable to read master key from system keychain: ${message}`);
|
|
181
|
+
}
|
|
182
|
+
// 4. Auto-generate and persist to keychain. Persisting is mandatory; using
|
|
183
|
+
// an unpersisted generated key would make encrypted secrets unrecoverable on
|
|
184
|
+
// the next launch.
|
|
185
|
+
const newKey = generateMasterKey();
|
|
186
|
+
try {
|
|
187
|
+
await keytar.setPassword(KEYRING_SERVICE, KEYRING_ACCOUNT, newKey);
|
|
188
|
+
log.info("Master key generated and stored");
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
192
|
+
throw keychainAccessError(`Unable to store master key in system keychain: ${message}`);
|
|
193
|
+
}
|
|
194
|
+
cachedMasterKey = newKey;
|
|
195
|
+
return newKey;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Clear the cached master key.
|
|
199
|
+
*
|
|
200
|
+
* Forces the next call to getMasterKey() or initMasterKey() to
|
|
201
|
+
* re-read from environment, AWS, or keychain.
|
|
202
|
+
*/
|
|
203
|
+
export function clearMasterKeyCache() {
|
|
204
|
+
cachedMasterKey = null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Set a specific master key (useful for testing or migration).
|
|
208
|
+
*
|
|
209
|
+
* @param masterKey - The master key to use.
|
|
210
|
+
*/
|
|
211
|
+
export function setMasterKey(masterKey) {
|
|
212
|
+
cachedMasterKey = masterKey;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Set the master key and persist it to the system keychain.
|
|
216
|
+
*
|
|
217
|
+
* @param masterKey - The master key to set (base64-encoded string).
|
|
218
|
+
* @throws {Error} If keychain write fails.
|
|
219
|
+
*/
|
|
220
|
+
export async function setMasterKeyPersistent(masterKey) {
|
|
221
|
+
const keytar = _keytar ?? await loadKeytar();
|
|
222
|
+
await keytar.setPassword(KEYRING_SERVICE, KEYRING_ACCOUNT, masterKey);
|
|
223
|
+
cachedMasterKey = masterKey;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Delete the master key from the system keychain.
|
|
227
|
+
*
|
|
228
|
+
* WARNING: This will make all encrypted secrets inaccessible!
|
|
229
|
+
*
|
|
230
|
+
* @returns True if the key was deleted, false otherwise.
|
|
231
|
+
* @throws {Error} If keychain deletion fails.
|
|
232
|
+
*/
|
|
233
|
+
export async function deleteMasterKey() {
|
|
234
|
+
const keytar = _keytar ?? await loadKeytar();
|
|
235
|
+
const deleted = await keytar.deletePassword(KEYRING_SERVICE, KEYRING_ACCOUNT);
|
|
236
|
+
cachedMasterKey = null;
|
|
237
|
+
return deleted;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Check if the master key is being sourced from an environment variable.
|
|
241
|
+
*
|
|
242
|
+
* @returns True if SECRETS_MASTER_KEY environment variable is set.
|
|
243
|
+
*/
|
|
244
|
+
export function isUsingEnvKey() {
|
|
245
|
+
return process.env["SECRETS_MASTER_KEY"] !== undefined;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Check if the master key should be sourced from AWS Secrets Manager.
|
|
249
|
+
*
|
|
250
|
+
* @returns True if AWS_SECRETS_MASTER_KEY_NAME environment variable is set.
|
|
251
|
+
*/
|
|
252
|
+
export function isUsingAwsKey() {
|
|
253
|
+
return process.env["AWS_SECRETS_MASTER_KEY_NAME"] !== undefined;
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=master-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"master-key.js","sourceRoot":"","sources":["../src/master-key.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface StartupCheckResult {
|
|
2
|
+
errors: string[];
|
|
3
|
+
warnings: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Run startup health checks.
|
|
7
|
+
*
|
|
8
|
+
* - Checks that the master encryption key is loadable.
|
|
9
|
+
* - Warns about missing optional API keys.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runStartupChecks(): Promise<StartupCheckResult>;
|
|
12
|
+
//# sourceMappingURL=startup-checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startup-checks.d.ts","sourceRoot":"","sources":["../src/startup-checks.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAaD;;;;;GAKG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAyBpE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup checks — T-SEC-8.
|
|
3
|
+
*
|
|
4
|
+
* Validates that the system is properly configured before accepting requests.
|
|
5
|
+
*/
|
|
6
|
+
import { getMasterKey } from "./master-key.js";
|
|
7
|
+
/** Well-known optional API keys that should be configured for full functionality. */
|
|
8
|
+
const OPTIONAL_API_KEYS = [
|
|
9
|
+
"OPENAI_API_KEY",
|
|
10
|
+
"ANTHROPIC_API_KEY",
|
|
11
|
+
"GEMINI_API_KEY",
|
|
12
|
+
"REPLICATE_API_TOKEN",
|
|
13
|
+
"HF_TOKEN",
|
|
14
|
+
"ELEVENLABS_API_KEY",
|
|
15
|
+
"FAL_API_KEY"
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Run startup health checks.
|
|
19
|
+
*
|
|
20
|
+
* - Checks that the master encryption key is loadable.
|
|
21
|
+
* - Warns about missing optional API keys.
|
|
22
|
+
*/
|
|
23
|
+
export async function runStartupChecks() {
|
|
24
|
+
const errors = [];
|
|
25
|
+
const warnings = [];
|
|
26
|
+
// Check master key
|
|
27
|
+
try {
|
|
28
|
+
const key = getMasterKey();
|
|
29
|
+
if (!key) {
|
|
30
|
+
errors.push("Master encryption key could not be loaded");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
35
|
+
errors.push(`Master key initialization failed: ${msg}`);
|
|
36
|
+
}
|
|
37
|
+
// Check optional API keys
|
|
38
|
+
for (const envVar of OPTIONAL_API_KEYS) {
|
|
39
|
+
if (!process.env[envVar]) {
|
|
40
|
+
warnings.push(`${envVar} is not set — related features will be unavailable`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { errors, warnings };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=startup-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startup-checks.js","sourceRoot":"","sources":["../src/startup-checks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO/C,qFAAqF;AACrF,MAAM,iBAAiB,GAAG;IACxB,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;IAChB,qBAAqB;IACrB,UAAU;IACV,oBAAoB;IACpB,aAAa;CACd,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,mBAAmB;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,CAAC,IAAI,CAAC,qCAAqC,GAAG,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,0BAA0B;IAC1B,KAAK,MAAM,MAAM,IAAI,iBAAiB,EAAE,CAAC;QACvC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,QAAQ,CAAC,IAAI,CACX,GAAG,MAAM,oDAAoD,CAC9D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nodetool-ai/security",
|
|
3
|
+
"version": "0.7.0-rc.11",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "node ../../scripts/build-typescript-workspace.mjs",
|
|
9
|
+
"test": "vitest run",
|
|
10
|
+
"lint": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@nodetool-ai/config": "^0.7.0-rc.11",
|
|
14
|
+
"@aws-sdk/client-secrets-manager": "^3.1029.0",
|
|
15
|
+
"keytar": "^7.9.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"vitest": "^4.1.2",
|
|
19
|
+
"typescript": "^5.7.2"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"nodetool-dev": "./src/index.ts",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/nodetool-ai/nodetool.git",
|
|
33
|
+
"directory": "packages/security"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://nodetool.ai",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/nodetool-ai/nodetool/issues"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md"
|
|
45
|
+
]
|
|
46
|
+
}
|