@push.rocks/smartconfig 6.1.1 → 6.2.2

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,453 @@
1
+ import { createCipheriv, createDecipheriv, createHash, createPublicKey, hkdfSync, randomBytes, verify, } from 'node:crypto';
2
+ import { promises as fs } from 'node:fs';
3
+ import * as net from 'node:net';
4
+ import { homedir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ const sshAgentFailure = 5;
7
+ const sshAgentRequestIdentities = 11;
8
+ const sshAgentIdentitiesAnswer = 12;
9
+ const sshAgentSignRequest = 13;
10
+ const sshAgentSignResponse = 14;
11
+ const maxAgentResponseBytes = 16 * 1024 * 1024;
12
+ const maxAgentIdentities = 1024;
13
+ const defaultAgentTimeoutMs = 15_000;
14
+ const secretFormat = 'ssh-agent-sign-v1';
15
+ const aadEncoding = 'utf8';
16
+ class BinaryReader {
17
+ constructor(buffer) {
18
+ this.buffer = buffer;
19
+ this.offset = 0;
20
+ }
21
+ readUInt8() {
22
+ this.ensureAvailable(1);
23
+ const value = this.buffer.readUInt8(this.offset);
24
+ this.offset += 1;
25
+ return value;
26
+ }
27
+ readUInt32BE() {
28
+ this.ensureAvailable(4);
29
+ const value = this.buffer.readUInt32BE(this.offset);
30
+ this.offset += 4;
31
+ return value;
32
+ }
33
+ readBuffer(lengthArg) {
34
+ this.ensureAvailable(lengthArg);
35
+ const value = this.buffer.subarray(this.offset, this.offset + lengthArg);
36
+ this.offset += lengthArg;
37
+ return value;
38
+ }
39
+ readSshStringBuffer() {
40
+ return this.readBuffer(this.readUInt32BE());
41
+ }
42
+ readSshStringUtf8() {
43
+ return this.readSshStringBuffer().toString('utf8');
44
+ }
45
+ get remainingBytes() {
46
+ return this.buffer.length - this.offset;
47
+ }
48
+ ensureAvailable(lengthArg) {
49
+ if (lengthArg < 0 || this.buffer.length - this.offset < lengthArg) {
50
+ throw new Error('Malformed ssh-agent response.');
51
+ }
52
+ }
53
+ }
54
+ class SshAgentClient {
55
+ constructor(socketPath = process.env.SSH_AUTH_SOCK ?? '') {
56
+ this.socketPath = socketPath;
57
+ }
58
+ async listIdentities() {
59
+ const response = await this.request(Buffer.from([sshAgentRequestIdentities]));
60
+ const reader = new BinaryReader(response);
61
+ const responseType = reader.readUInt8();
62
+ if (responseType === sshAgentFailure) {
63
+ throw new Error('ssh-agent refused to list identities.');
64
+ }
65
+ if (responseType !== sshAgentIdentitiesAnswer) {
66
+ throw new Error(`Unexpected ssh-agent response type ${responseType}.`);
67
+ }
68
+ const count = reader.readUInt32BE();
69
+ if (count > maxAgentIdentities) {
70
+ throw new Error(`Refusing oversized ssh-agent identity list with ${count} identities.`);
71
+ }
72
+ const identities = [];
73
+ for (let i = 0; i < count; i++) {
74
+ const keyBlob = reader.readSshStringBuffer();
75
+ const comment = reader.readSshStringUtf8();
76
+ const parsedKeyBlob = tryParseSupportedKeyBlob(keyBlob);
77
+ if (!parsedKeyBlob)
78
+ continue;
79
+ const fingerprint = fingerprintSha256(keyBlob);
80
+ const authorizedKey = formatAuthorizedKey(parsedKeyBlob.type, keyBlob, comment);
81
+ identities.push({
82
+ type: parsedKeyBlob.type,
83
+ keyBlob,
84
+ publicKeyBytes: parsedKeyBlob.publicKeyBytes,
85
+ comment,
86
+ fingerprint,
87
+ authorizedKey,
88
+ });
89
+ }
90
+ return identities;
91
+ }
92
+ async sign(keyBlobArg, dataArg) {
93
+ const payload = Buffer.concat([
94
+ Buffer.from([sshAgentSignRequest]),
95
+ sshString(keyBlobArg),
96
+ sshString(dataArg),
97
+ uint32(0),
98
+ ]);
99
+ const response = await this.request(payload);
100
+ const reader = new BinaryReader(response);
101
+ const responseType = reader.readUInt8();
102
+ if (responseType === sshAgentFailure) {
103
+ throw new Error('ssh-agent refused to sign. The key may be locked, constrained, or missing.');
104
+ }
105
+ if (responseType !== sshAgentSignResponse) {
106
+ throw new Error(`Unexpected ssh-agent response type ${responseType}.`);
107
+ }
108
+ const signatureBlob = reader.readSshStringBuffer();
109
+ const signatureReader = new BinaryReader(signatureBlob);
110
+ const algorithm = signatureReader.readSshStringUtf8();
111
+ const signature = signatureReader.readSshStringBuffer();
112
+ if (signatureReader.remainingBytes !== 0) {
113
+ throw new Error('Malformed ssh-agent signature blob.');
114
+ }
115
+ return {
116
+ algorithm,
117
+ signature,
118
+ signatureBlob,
119
+ };
120
+ }
121
+ async request(payloadArg) {
122
+ if (!this.socketPath) {
123
+ throw new Error('SSH_AUTH_SOCK is not set. Start ssh-agent and add the matching SSH key.');
124
+ }
125
+ return await new Promise((resolve, reject) => {
126
+ const socket = net.createConnection({ path: this.socketPath });
127
+ const chunks = [];
128
+ let finished = false;
129
+ let timeoutRef;
130
+ const cleanup = () => {
131
+ if (timeoutRef)
132
+ clearTimeout(timeoutRef);
133
+ };
134
+ const fail = (errorArg) => {
135
+ if (finished)
136
+ return;
137
+ finished = true;
138
+ cleanup();
139
+ socket.destroy();
140
+ reject(errorArg);
141
+ };
142
+ timeoutRef = setTimeout(() => {
143
+ fail(new Error(`ssh-agent request timed out after ${defaultAgentTimeoutMs}ms.`));
144
+ }, defaultAgentTimeoutMs);
145
+ timeoutRef.unref?.();
146
+ socket.on('connect', () => {
147
+ socket.write(Buffer.concat([uint32(payloadArg.length), payloadArg]));
148
+ });
149
+ socket.on('error', fail);
150
+ socket.on('close', () => {
151
+ if (!finished) {
152
+ fail(new Error('ssh-agent closed the connection before sending a complete response.'));
153
+ }
154
+ });
155
+ socket.on('data', (chunkArg) => {
156
+ if (finished)
157
+ return;
158
+ chunks.push(Buffer.from(chunkArg));
159
+ const buffer = Buffer.concat(chunks);
160
+ if (buffer.length < 4)
161
+ return;
162
+ const responseLength = buffer.readUInt32BE(0);
163
+ if (responseLength > maxAgentResponseBytes) {
164
+ fail(new Error('Refusing oversized ssh-agent response.'));
165
+ return;
166
+ }
167
+ if (buffer.length < 4 + responseLength)
168
+ return;
169
+ finished = true;
170
+ cleanup();
171
+ socket.destroy();
172
+ resolve(buffer.subarray(4, 4 + responseLength));
173
+ });
174
+ });
175
+ }
176
+ }
177
+ export class SmartconfigSshAgentSecrets {
178
+ static async encryptString(valueArg, optionsArg = {}) {
179
+ const publicKey = await resolvePublicKey(optionsArg);
180
+ const agent = new SshAgentClient(optionsArg.agentSocket);
181
+ const identity = await findIdentity(agent, publicKey);
182
+ const salt = randomBytes(32);
183
+ const challenge = makeChallenge(publicKey, salt);
184
+ const signature = await agent.sign(identity.keyBlob, challenge);
185
+ assertEd25519Signature(signature);
186
+ assertSignatureMatchesPublicKey(publicKey, challenge, signature.signature);
187
+ const kek = deriveKek(signature.signature, salt, publicKey);
188
+ const aad = makeAad(publicKey, salt);
189
+ const box = encryptAes256Gcm(kek, Buffer.from(valueArg, 'utf8'), aad);
190
+ return {
191
+ __smartconfigSecret: secretFormat,
192
+ keyHint: {
193
+ format: 'openssh-authorized-key-v1',
194
+ authorizedKey: publicKey.authorizedKey,
195
+ fingerprint: publicKey.fingerprint,
196
+ },
197
+ payload: {
198
+ encoding: aadEncoding,
199
+ },
200
+ kdf: {
201
+ name: 'HKDF-SHA256',
202
+ salt: toBase64(salt),
203
+ },
204
+ box,
205
+ };
206
+ }
207
+ static async decryptString(encryptedValueArg, optionsArg = {}) {
208
+ const buffer = await this.decryptBuffer(encryptedValueArg, optionsArg);
209
+ return buffer.toString('utf8');
210
+ }
211
+ static async decryptBuffer(encryptedValueArg, optionsArg = {}) {
212
+ const normalized = normalizeEncryptedValue(encryptedValueArg);
213
+ const publicKey = parseOpenSshPublicKeyLine(normalized.keyHint.authorizedKey);
214
+ if (publicKey.fingerprint !== normalized.keyHint.fingerprint) {
215
+ throw new Error('Encrypted value public key does not match its fingerprint.');
216
+ }
217
+ const salt = fromBase64(normalized.kdf.salt, 'kdf.salt', 32);
218
+ const agent = new SshAgentClient(optionsArg.agentSocket);
219
+ const identity = await findIdentity(agent, publicKey);
220
+ const challenge = makeChallenge(publicKey, salt);
221
+ const signature = await agent.sign(identity.keyBlob, challenge);
222
+ assertEd25519Signature(signature);
223
+ assertSignatureMatchesPublicKey(publicKey, challenge, signature.signature);
224
+ const kek = deriveKek(signature.signature, salt, publicKey);
225
+ return decryptAes256Gcm(kek, normalized.box, makeAad(publicKey, salt));
226
+ }
227
+ static isEncryptedValue(valueArg) {
228
+ return Boolean(valueArg
229
+ && typeof valueArg === 'object'
230
+ && valueArg.__smartconfigSecret === secretFormat);
231
+ }
232
+ static async writeKey(kvStoreArg, keyArg, valueArg, optionsArg = {}) {
233
+ const encryptedValue = await this.encryptString(valueArg, optionsArg);
234
+ await kvStoreArg.writeKey(keyArg, encryptedValue);
235
+ return encryptedValue;
236
+ }
237
+ static async readKey(kvStoreArg, keyArg, optionsArg = {}) {
238
+ const value = await kvStoreArg.readKey(keyArg);
239
+ if (!this.isEncryptedValue(value)) {
240
+ throw new Error(`Key "${keyArg}" does not contain a smartconfig ssh-agent encrypted value.`);
241
+ }
242
+ return await this.decryptString(value, optionsArg);
243
+ }
244
+ }
245
+ async function resolvePublicKey(optionsArg) {
246
+ if (optionsArg.authorizedKey) {
247
+ return parseOpenSshPublicKeyLine(optionsArg.authorizedKey);
248
+ }
249
+ const authorizedKeyPath = optionsArg.authorizedKeyPath ?? '~/.ssh/id_ed25519.pub';
250
+ const content = await fs.readFile(expandHome(authorizedKeyPath), 'utf8');
251
+ return parseOpenSshPublicKeyLine(content);
252
+ }
253
+ async function findIdentity(agentArg, publicKeyArg) {
254
+ const identities = await agentArg.listIdentities();
255
+ const identity = identities.find((candidate) => candidate.fingerprint === publicKeyArg.fingerprint && candidate.keyBlob.equals(publicKeyArg.keyBlob));
256
+ if (!identity) {
257
+ throw new Error(`Required SSH key ${publicKeyArg.fingerprint} is not loaded in ssh-agent.`);
258
+ }
259
+ return identity;
260
+ }
261
+ function normalizeEncryptedValue(valueArg) {
262
+ if (!SmartconfigSshAgentSecrets.isEncryptedValue(valueArg)) {
263
+ throw new Error('Value is not a smartconfig ssh-agent encrypted value.');
264
+ }
265
+ if (valueArg.keyHint?.format !== 'openssh-authorized-key-v1') {
266
+ throw new Error('Encrypted value uses an unsupported key hint format.');
267
+ }
268
+ if (typeof valueArg.keyHint.authorizedKey !== 'string' || !valueArg.keyHint.authorizedKey) {
269
+ throw new Error('Encrypted value is missing an authorized key hint.');
270
+ }
271
+ if (typeof valueArg.keyHint.fingerprint !== 'string' || !valueArg.keyHint.fingerprint.startsWith('SHA256:')) {
272
+ throw new Error('Encrypted value is missing a supported SSH key fingerprint.');
273
+ }
274
+ if (valueArg.payload?.encoding !== aadEncoding) {
275
+ throw new Error('Encrypted value uses an unsupported payload encoding.');
276
+ }
277
+ if (valueArg.kdf?.name !== 'HKDF-SHA256') {
278
+ throw new Error('Encrypted value uses an unsupported KDF.');
279
+ }
280
+ if (valueArg.box?.alg !== 'AES-256-GCM') {
281
+ throw new Error('Encrypted value uses an unsupported encryption algorithm.');
282
+ }
283
+ return valueArg;
284
+ }
285
+ function parseOpenSshPublicKeyLine(lineOrFileArg) {
286
+ const line = lineOrFileArg
287
+ .split(/\r?\n/)
288
+ .map((candidate) => candidate.trim())
289
+ .find((candidate) => candidate && !candidate.startsWith('#'));
290
+ if (!line) {
291
+ throw new Error('No OpenSSH authorized key line found.');
292
+ }
293
+ const tokens = line.split(/\s+/);
294
+ const keyIndex = tokens.findIndex((token) => token === 'ssh-ed25519');
295
+ if (keyIndex === -1 || !tokens[keyIndex + 1]) {
296
+ throw new Error('Only ssh-ed25519 authorized key hints are supported.');
297
+ }
298
+ const keyBlob = fromBase64(tokens[keyIndex + 1], 'authorizedKey.keyBlob');
299
+ const parsedKeyBlob = parseSupportedKeyBlob(keyBlob);
300
+ if (parsedKeyBlob.type !== tokens[keyIndex]) {
301
+ throw new Error(`SSH public key type mismatch: line says ${tokens[keyIndex]}, blob says ${parsedKeyBlob.type}.`);
302
+ }
303
+ const comment = tokens.slice(keyIndex + 2).join(' ');
304
+ const fingerprint = fingerprintSha256(keyBlob);
305
+ return {
306
+ type: parsedKeyBlob.type,
307
+ keyBlob,
308
+ publicKeyBytes: parsedKeyBlob.publicKeyBytes,
309
+ comment,
310
+ fingerprint,
311
+ authorizedKey: formatAuthorizedKey(parsedKeyBlob.type, keyBlob, comment),
312
+ };
313
+ }
314
+ function parseSupportedKeyBlob(keyBlobArg) {
315
+ const reader = new BinaryReader(keyBlobArg);
316
+ const type = reader.readSshStringUtf8();
317
+ if (type !== 'ssh-ed25519') {
318
+ throw new Error(`Unsupported SSH key type: ${type}.`);
319
+ }
320
+ const publicKeyBytes = reader.readSshStringBuffer();
321
+ if (publicKeyBytes.length !== 32) {
322
+ throw new Error(`Expected 32-byte Ed25519 public key, got ${publicKeyBytes.length} bytes.`);
323
+ }
324
+ if (reader.remainingBytes !== 0) {
325
+ throw new Error('Malformed ssh-ed25519 public key blob.');
326
+ }
327
+ return { type, publicKeyBytes };
328
+ }
329
+ function tryParseSupportedKeyBlob(keyBlobArg) {
330
+ try {
331
+ return parseSupportedKeyBlob(keyBlobArg);
332
+ }
333
+ catch (error) {
334
+ if (error.message.startsWith('Unsupported SSH key type:')) {
335
+ return undefined;
336
+ }
337
+ throw error;
338
+ }
339
+ }
340
+ function assertEd25519Signature(signatureArg) {
341
+ if (signatureArg.algorithm !== 'ssh-ed25519') {
342
+ throw new Error(`Expected ssh-ed25519 signature, got ${signatureArg.algorithm}.`);
343
+ }
344
+ if (signatureArg.signature.length !== 64) {
345
+ throw new Error(`Expected 64-byte Ed25519 signature, got ${signatureArg.signature.length} bytes.`);
346
+ }
347
+ }
348
+ function assertSignatureMatchesPublicKey(publicKeyArg, challengeArg, signatureArg) {
349
+ const spkiPrefix = Buffer.from('302a300506032b6570032100', 'hex');
350
+ const keyObject = createPublicKey({
351
+ key: Buffer.concat([spkiPrefix, publicKeyArg.publicKeyBytes]),
352
+ format: 'der',
353
+ type: 'spki',
354
+ });
355
+ const valid = verify(null, challengeArg, keyObject, signatureArg);
356
+ if (!valid) {
357
+ throw new Error('ssh-agent signature does not match the encrypted value key hint.');
358
+ }
359
+ }
360
+ function makeChallenge(publicKeyArg, saltArg) {
361
+ return Buffer.concat([
362
+ Buffer.from('smartconfig:ssh-agent-sign-v1:kek\n', 'utf8'),
363
+ sshString(publicKeyArg.fingerprint),
364
+ sshString(publicKeyArg.authorizedKey),
365
+ sshString(saltArg),
366
+ ]);
367
+ }
368
+ function deriveKek(signatureArg, saltArg, publicKeyArg) {
369
+ const info = Buffer.concat([
370
+ Buffer.from('smartconfig:ssh-agent-sign-v1:hkdf-info\n', 'utf8'),
371
+ sshString(publicKeyArg.fingerprint),
372
+ sshString(publicKeyArg.authorizedKey),
373
+ ]);
374
+ return Buffer.from(hkdfSync('sha256', signatureArg, saltArg, info, 32));
375
+ }
376
+ function makeAad(publicKeyArg, saltArg) {
377
+ return Buffer.from(JSON.stringify({
378
+ format: secretFormat,
379
+ keyHint: {
380
+ format: 'openssh-authorized-key-v1',
381
+ authorizedKey: publicKeyArg.authorizedKey,
382
+ fingerprint: publicKeyArg.fingerprint,
383
+ },
384
+ payload: {
385
+ encoding: aadEncoding,
386
+ },
387
+ kdf: {
388
+ name: 'HKDF-SHA256',
389
+ salt: toBase64(saltArg),
390
+ },
391
+ }), 'utf8');
392
+ }
393
+ function encryptAes256Gcm(keyArg, plaintextArg, aadArg) {
394
+ const nonce = randomBytes(12);
395
+ const cipher = createCipheriv('aes-256-gcm', keyArg, nonce);
396
+ cipher.setAAD(aadArg);
397
+ const ciphertext = Buffer.concat([cipher.update(plaintextArg), cipher.final()]);
398
+ const tag = cipher.getAuthTag();
399
+ return {
400
+ alg: 'AES-256-GCM',
401
+ nonce: toBase64(nonce),
402
+ ciphertext: toBase64(ciphertext),
403
+ tag: toBase64(tag),
404
+ };
405
+ }
406
+ function decryptAes256Gcm(keyArg, boxArg, aadArg) {
407
+ if (boxArg.alg !== 'AES-256-GCM') {
408
+ throw new Error(`Unsupported encryption algorithm: ${boxArg.alg}.`);
409
+ }
410
+ const nonce = fromBase64(boxArg.nonce, 'box.nonce', 12);
411
+ const ciphertext = fromBase64(boxArg.ciphertext, 'box.ciphertext');
412
+ const tag = fromBase64(boxArg.tag, 'box.tag', 16);
413
+ const decipher = createDecipheriv('aes-256-gcm', keyArg, nonce);
414
+ decipher.setAAD(aadArg);
415
+ decipher.setAuthTag(tag);
416
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
417
+ }
418
+ function formatAuthorizedKey(typeArg, keyBlobArg, commentArg) {
419
+ return `${typeArg} ${keyBlobArg.toString('base64')}${commentArg ? ` ${commentArg}` : ''}`;
420
+ }
421
+ function fingerprintSha256(keyBlobArg) {
422
+ return `SHA256:${createHash('sha256').update(keyBlobArg).digest('base64').replace(/=+$/g, '')}`;
423
+ }
424
+ function sshString(valueArg) {
425
+ const buffer = Buffer.isBuffer(valueArg) ? valueArg : Buffer.from(valueArg, 'utf8');
426
+ return Buffer.concat([uint32(buffer.length), buffer]);
427
+ }
428
+ function uint32(valueArg) {
429
+ const buffer = Buffer.alloc(4);
430
+ buffer.writeUInt32BE(valueArg, 0);
431
+ return buffer;
432
+ }
433
+ function toBase64(valueArg) {
434
+ return valueArg.toString('base64');
435
+ }
436
+ function fromBase64(valueArg, fieldNameArg, expectedLengthArg) {
437
+ if (typeof valueArg !== 'string' || !valueArg || valueArg.length % 4 === 1 || !/^[A-Za-z0-9+/]+={0,2}$/.test(valueArg)) {
438
+ throw new Error(`${fieldNameArg} must be a base64 string.`);
439
+ }
440
+ const buffer = Buffer.from(valueArg, 'base64');
441
+ if (expectedLengthArg !== undefined && buffer.length !== expectedLengthArg) {
442
+ throw new Error(`${fieldNameArg} must decode to ${expectedLengthArg} bytes.`);
443
+ }
444
+ return buffer;
445
+ }
446
+ function expandHome(pathArg) {
447
+ if (pathArg === '~')
448
+ return homedir();
449
+ if (pathArg.startsWith('~/'))
450
+ return join(homedir(), pathArg.slice(2));
451
+ return pathArg;
452
+ }
453
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRjb25maWcuc3NoYWdlbnRzZWNyZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zc2hhZ2VudC9zbWFydGNvbmZpZy5zc2hhZ2VudHNlY3JldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixVQUFVLEVBQ1YsZUFBZSxFQUNmLFFBQVEsRUFDUixXQUFXLEVBQ1gsTUFBTSxHQUNQLE1BQU0sYUFBYSxDQUFDO0FBQ3JCLE9BQU8sRUFBRSxRQUFRLElBQUksRUFBRSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ3pDLE9BQU8sS0FBSyxHQUFHLE1BQU0sVUFBVSxDQUFDO0FBQ2hDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDbEMsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLFdBQVcsQ0FBQztBQW1FakMsTUFBTSxlQUFlLEdBQUcsQ0FBQyxDQUFDO0FBQzFCLE1BQU0seUJBQXlCLEdBQUcsRUFBRSxDQUFDO0FBQ3JDLE1BQU0sd0JBQXdCLEdBQUcsRUFBRSxDQUFDO0FBQ3BDLE1BQU0sbUJBQW1CLEdBQUcsRUFBRSxDQUFDO0FBQy9CLE1BQU0sb0JBQW9CLEdBQUcsRUFBRSxDQUFDO0FBQ2hDLE1BQU0scUJBQXFCLEdBQUcsRUFBRSxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7QUFDL0MsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLENBQUM7QUFDaEMsTUFBTSxxQkFBcUIsR0FBRyxNQUFNLENBQUM7QUFDckMsTUFBTSxZQUFZLEdBQTZCLG1CQUFtQixDQUFDO0FBQ25FLE1BQU0sV0FBVyxHQUFzQyxNQUFNLENBQUM7QUFFOUQsTUFBTSxZQUFZO0lBR2hCLFlBQTZCLE1BQWM7UUFBZCxXQUFNLEdBQU4sTUFBTSxDQUFRO1FBRm5DLFdBQU0sR0FBRyxDQUFDLENBQUM7SUFFMkIsQ0FBQztJQUV4QyxTQUFTO1FBQ2QsSUFBSSxDQUFDLGVBQWUs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
package/package.json CHANGED
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "name": "@push.rocks/smartconfig",
3
- "version": "6.1.1",
3
+ "version": "6.2.2",
4
4
  "private": false,
5
5
  "description": "A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.",
6
6
  "main": "dist_ts/index.js",
7
7
  "typings": "dist_ts/index.d.ts",
8
- "scripts": {
9
- "test": "(tstest test/ --verbose --logfile --timeout 20)",
10
- "build": "(tsbuild --web)",
11
- "buildDocs": "tsdoc"
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist_ts/index.d.ts",
11
+ "import": "./dist_ts/index.js",
12
+ "default": "./dist_ts/index.js"
13
+ },
14
+ "./sshagent": {
15
+ "types": "./dist_ts/sshagent/index.d.ts",
16
+ "import": "./dist_ts/sshagent/index.js",
17
+ "default": "./dist_ts/sshagent/index.js"
18
+ },
19
+ "./dist_ts/*": "./dist_ts/*",
20
+ "./ts/*": "./ts/*",
21
+ "./package.json": "./package.json"
12
22
  },
13
23
  "repository": {
14
24
  "type": "git",
@@ -32,11 +42,11 @@
32
42
  "@tsclass/tsclass": "^9.5.1"
33
43
  },
34
44
  "devDependencies": {
35
- "@git.zone/tsbuild": "^4.4.0",
36
- "@git.zone/tsrun": "^2.0.2",
37
- "@git.zone/tstest": "^3.6.3",
45
+ "@git.zone/tsbuild": "^4.4.2",
46
+ "@git.zone/tsrun": "^2.0.6",
47
+ "@git.zone/tstest": "^4.0.0",
38
48
  "@types/lodash.clonedeep": "^4.5.9",
39
- "@types/node": "^25.6.0"
49
+ "@types/node": "26.4.0"
40
50
  },
41
51
  "files": [
42
52
  "ts/**/*",
@@ -72,5 +82,9 @@
72
82
  "workflow improvement",
73
83
  "persistent storage"
74
84
  ],
75
- "packageManager": "pnpm@10.28.2"
76
- }
85
+ "scripts": {
86
+ "test": "(tstest test/ --verbose --logfile --timeout 20)",
87
+ "build": "(tsbuild --web)",
88
+ "buildDocs": "tsdoc"
89
+ }
90
+ }
package/readme.md CHANGED
@@ -133,6 +133,61 @@ kv.changeSubject.subscribe((data) => console.log('changed:', data));
133
133
 
134
134
  ---
135
135
 
136
+ ### SSH-Agent Encrypted Single Values
137
+
138
+ For Node.js CLI tools, individual `KeyValueStore` values can be stored as encrypted JSON envelopes and unlocked later through a matching SSH key loaded in `ssh-agent`. This API is intentionally exposed from the Node-only subpath `@push.rocks/smartconfig/sshagent` so browser bundles do not pull in `node:net`, `node:fs`, or `node:crypto`.
139
+
140
+ ```typescript
141
+ import { KeyValueStore } from '@push.rocks/smartconfig';
142
+ import { SmartconfigSshAgentSecrets } from '@push.rocks/smartconfig/sshagent';
143
+
144
+ const kv = new KeyValueStore({
145
+ typeArg: 'userHomeDir',
146
+ identityArg: 'my-tool',
147
+ });
148
+
149
+ await SmartconfigSshAgentSecrets.writeKey(kv, 'apiToken', 'secret-token', {
150
+ authorizedKeyPath: '~/.ssh/id_ed25519.pub',
151
+ });
152
+
153
+ const apiToken = await SmartconfigSshAgentSecrets.readKey(kv, 'apiToken');
154
+ ```
155
+
156
+ The stored value is a single self-describing object. Other keys in the same config can remain plain JSON values.
157
+
158
+ ```json
159
+ {
160
+ "apiUrl": "https://api.example.test",
161
+ "apiToken": {
162
+ "__smartconfigSecret": "ssh-agent-sign-v1",
163
+ "keyHint": {
164
+ "format": "openssh-authorized-key-v1",
165
+ "authorizedKey": "ssh-ed25519 AAAAC3... user@host",
166
+ "fingerprint": "SHA256:..."
167
+ },
168
+ "payload": {
169
+ "encoding": "utf8"
170
+ },
171
+ "kdf": {
172
+ "name": "HKDF-SHA256",
173
+ "salt": "..."
174
+ },
175
+ "box": {
176
+ "alg": "AES-256-GCM",
177
+ "nonce": "...",
178
+ "ciphertext": "...",
179
+ "tag": "..."
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ The encrypted value includes an OpenSSH authorized-key-style hint. Smartconfig uses the hint's public key blob and `SHA256:` fingerprint to find the matching identity in `ssh-agent`; the key comment is not trusted for matching.
186
+
187
+ This is ssh-agent-signature-derived encryption, not public-key encryption. Encryption and decryption both require the matching `ssh-ed25519` key to be available through `ssh-agent`. Anyone who can access your `SSH_AUTH_SOCK` can ask the agent to sign the unlock challenge, so this protects config values at rest on disk, not an already-compromised unlocked session.
188
+
189
+ ---
190
+
136
191
  ### 3. `AppData<T>` — Environment Variable Mapping 🌟
137
192
 
138
193
  The flagship class. Maps environment variables (or hardcoded values) into a typed config object with automatic type conversions, nested object support, and smart storage path selection.
@@ -250,7 +305,19 @@ const port = await AppData.valueAsNumber('PORT');
250
305
 
251
306
  ## Security 🔐
252
307
 
253
- AppData automatically redacts sensitive values in its console logs. Keys matching patterns like `secret`, `token`, `password`, `api`, `auth`, `jwt`, etc. are truncated. JWT tokens (starting with `eyJ`) are also detected and shortened. Your actual stored values are never modified — only log output is redacted.
308
+ AppData diagnostics report configuration key names, environment-variable names,
309
+ source kinds, transforms, presence and value types, never configuration values.
310
+ This applies to ordinary-looking names as well as credentials, nested values,
311
+ hardcoded literals, boolean conversions and overrides. No prefixes or previews
312
+ of secret values are emitted. Mapping-failure diagnostics identify the affected
313
+ key without logging arbitrary exception messages or objects, which may contain
314
+ values. Keep key names, environment-variable names and storage paths free of secrets.
315
+
316
+ This logging guarantee does not encrypt stored values or change configuration
317
+ results. AppData persists evaluated values by default; use `ephemeral: true` for
318
+ runtime secrets that must stay in memory. Previously exposed credentials must
319
+ be rotated or revoked in their owning service; upgrading the logger does not
320
+ invalidate credentials or remove historical logs.
254
321
 
255
322
  ---
256
323
 
@@ -302,7 +369,7 @@ console.log(`Model: ${settings.model}, Cache: ${settings.cache.enabled}`);
302
369
 
303
370
  ## License and Legal Information
304
371
 
305
- This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
372
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
306
373
 
307
374
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
308
375
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartconfig',
6
- version: '6.1.1',
6
+ version: '6.2.2',
7
7
  description: 'A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.'
8
8
  }