@moxxy/plugin-vault 0.0.33
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/dist/crypto.d.ts +30 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +87 -0
- package/dist/crypto.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/dist/keysource.d.ts +50 -0
- package/dist/keysource.d.ts.map +1 -0
- package/dist/keysource.js +145 -0
- package/dist/keysource.js.map +1 -0
- package/dist/placeholder.d.ts +10 -0
- package/dist/placeholder.d.ts.map +1 -0
- package/dist/placeholder.js +92 -0
- package/dist/placeholder.js.map +1 -0
- package/dist/store.d.ts +106 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +415 -0
- package/dist/store.js.map +1 -0
- package/package.json +65 -0
- package/src/command.test.ts +101 -0
- package/src/crypto.test.ts +66 -0
- package/src/crypto.ts +106 -0
- package/src/index.ts +269 -0
- package/src/keysource.test.ts +285 -0
- package/src/keysource.ts +198 -0
- package/src/placeholder.test.ts +142 -0
- package/src/placeholder.ts +104 -0
- package/src/store.canary.test.ts +151 -0
- package/src/store.test.ts +283 -0
- package/src/store.ts +457 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moxxy (moxxy.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface EncryptedBlob {
|
|
2
|
+
readonly iv: string;
|
|
3
|
+
readonly tag: string;
|
|
4
|
+
readonly data: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function deriveKey(passphrase: string, salt: Buffer): Buffer;
|
|
7
|
+
/**
|
|
8
|
+
* Off-thread KDF. scrypt (N=16384) is a CPU-bound stall of ~tens of ms;
|
|
9
|
+
* `scryptSync` blocks the single-threaded runner event loop for that whole
|
|
10
|
+
* window, while this runs on libuv's threadpool and yields. Functionally
|
|
11
|
+
* identical to {@link deriveKey} (same params, same output). Used on the
|
|
12
|
+
* runtime key-resolution hot paths; the sync variant stays for callers that
|
|
13
|
+
* derive a key eagerly to construct a static key source.
|
|
14
|
+
*/
|
|
15
|
+
export declare function deriveKeyAsync(passphrase: string, salt: Buffer): Promise<Buffer>;
|
|
16
|
+
export declare function generateSalt(): Buffer;
|
|
17
|
+
export declare function encrypt(plaintext: string, key: Buffer): EncryptedBlob;
|
|
18
|
+
export declare function decrypt(blob: EncryptedBlob, key: Buffer): string;
|
|
19
|
+
/**
|
|
20
|
+
* A uniformly-random decimal string of exactly `digits` digits (zero-padded).
|
|
21
|
+
*
|
|
22
|
+
* Draws enough entropy for the requested width (not a fixed 4 bytes, which
|
|
23
|
+
* capped any code wider than ~9 digits — 10**digits overflowed a uint32, so
|
|
24
|
+
* the leading digits were always 0) and rejection-samples to strip the
|
|
25
|
+
* modulo bias that `% 10**digits` introduces when the byte range isn't an
|
|
26
|
+
* exact multiple of the modulus. Used for short pairing codes today; correct
|
|
27
|
+
* for any width.
|
|
28
|
+
*/
|
|
29
|
+
export declare function randomCode(digits?: number): string;
|
|
30
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkBD,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAElE;AASD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEhF;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CAUrE;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,MAAM,SAAI,GAAG,MAAM,CAqB7C"}
|
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, scrypt, scryptSync } from 'node:crypto';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const KEY_BYTES = 32;
|
|
4
|
+
const IV_BYTES = 12;
|
|
5
|
+
const SCRYPT_N = 16384;
|
|
6
|
+
const SCRYPT_R = 8;
|
|
7
|
+
const SCRYPT_P = 1;
|
|
8
|
+
const SCRYPT_OPTS = {
|
|
9
|
+
N: SCRYPT_N,
|
|
10
|
+
r: SCRYPT_R,
|
|
11
|
+
p: SCRYPT_P,
|
|
12
|
+
// scrypt's internal buffer is ~128 * N * r bytes (~16 MiB here). Node's
|
|
13
|
+
// default maxmem (32 MiB) is enough today, but pin it explicitly so a future
|
|
14
|
+
// parameter bump can't silently start throwing "memory limit exceeded".
|
|
15
|
+
maxmem: 64 * 1024 * 1024,
|
|
16
|
+
};
|
|
17
|
+
export function deriveKey(passphrase, salt) {
|
|
18
|
+
return scryptSync(passphrase, salt, KEY_BYTES, SCRYPT_OPTS);
|
|
19
|
+
}
|
|
20
|
+
const scryptAsync = promisify(scrypt);
|
|
21
|
+
/**
|
|
22
|
+
* Off-thread KDF. scrypt (N=16384) is a CPU-bound stall of ~tens of ms;
|
|
23
|
+
* `scryptSync` blocks the single-threaded runner event loop for that whole
|
|
24
|
+
* window, while this runs on libuv's threadpool and yields. Functionally
|
|
25
|
+
* identical to {@link deriveKey} (same params, same output). Used on the
|
|
26
|
+
* runtime key-resolution hot paths; the sync variant stays for callers that
|
|
27
|
+
* derive a key eagerly to construct a static key source.
|
|
28
|
+
*/
|
|
29
|
+
export function deriveKeyAsync(passphrase, salt) {
|
|
30
|
+
return scryptAsync(passphrase, salt, KEY_BYTES, SCRYPT_OPTS);
|
|
31
|
+
}
|
|
32
|
+
export function generateSalt() {
|
|
33
|
+
return randomBytes(16);
|
|
34
|
+
}
|
|
35
|
+
export function encrypt(plaintext, key) {
|
|
36
|
+
const iv = randomBytes(IV_BYTES);
|
|
37
|
+
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
38
|
+
const data = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
39
|
+
const tag = cipher.getAuthTag();
|
|
40
|
+
return {
|
|
41
|
+
iv: iv.toString('base64'),
|
|
42
|
+
tag: tag.toString('base64'),
|
|
43
|
+
data: data.toString('base64'),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function decrypt(blob, key) {
|
|
47
|
+
const iv = Buffer.from(blob.iv, 'base64');
|
|
48
|
+
const tag = Buffer.from(blob.tag, 'base64');
|
|
49
|
+
const data = Buffer.from(blob.data, 'base64');
|
|
50
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
51
|
+
decipher.setAuthTag(tag);
|
|
52
|
+
const plaintext = Buffer.concat([decipher.update(data), decipher.final()]);
|
|
53
|
+
return plaintext.toString('utf8');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A uniformly-random decimal string of exactly `digits` digits (zero-padded).
|
|
57
|
+
*
|
|
58
|
+
* Draws enough entropy for the requested width (not a fixed 4 bytes, which
|
|
59
|
+
* capped any code wider than ~9 digits — 10**digits overflowed a uint32, so
|
|
60
|
+
* the leading digits were always 0) and rejection-samples to strip the
|
|
61
|
+
* modulo bias that `% 10**digits` introduces when the byte range isn't an
|
|
62
|
+
* exact multiple of the modulus. Used for short pairing codes today; correct
|
|
63
|
+
* for any width.
|
|
64
|
+
*/
|
|
65
|
+
export function randomCode(digits = 6) {
|
|
66
|
+
if (!Number.isInteger(digits) || digits < 1) {
|
|
67
|
+
throw new Error(`randomCode: digits must be a positive integer, got ${digits}`);
|
|
68
|
+
}
|
|
69
|
+
const modulus = 10n ** BigInt(digits);
|
|
70
|
+
// Enough whole bytes to cover the modulus, with one spare byte so the
|
|
71
|
+
// accepted region is a large fraction of the draw space (few rejections).
|
|
72
|
+
const byteLen = Math.ceil((digits * Math.log2(10)) / 8) + 1;
|
|
73
|
+
const space = 1n << BigInt(byteLen * 8);
|
|
74
|
+
// Largest multiple of `modulus` that fits in `space`; draws at or above it
|
|
75
|
+
// would bias the low digits, so reject and redraw.
|
|
76
|
+
const limit = space - (space % modulus);
|
|
77
|
+
for (;;) {
|
|
78
|
+
let value = 0n;
|
|
79
|
+
for (const byte of randomBytes(byteLen)) {
|
|
80
|
+
value = (value << 8n) | BigInt(byte);
|
|
81
|
+
}
|
|
82
|
+
if (value < limit) {
|
|
83
|
+
return (value % modulus).toString().padStart(digits, '0');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# 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,21 @@
|
|
|
1
|
+
import { type Plugin } from '@moxxy/sdk';
|
|
2
|
+
import { type MasterKeySource } from './keysource.js';
|
|
3
|
+
import { VaultStore } from './store.js';
|
|
4
|
+
export { VaultStore, VaultPassphraseError } from './store.js';
|
|
5
|
+
export type { VaultEntry, VaultEntryInfo, VaultStoreOptions } from './store.js';
|
|
6
|
+
export { createCombinedKeySource, createStaticKeySource, type MasterKeySource } from './keysource.js';
|
|
7
|
+
export { resolveString, resolveValue, containsPlaceholder } from './placeholder.js';
|
|
8
|
+
export { deriveKey, deriveKeyAsync, encrypt, decrypt, generateSalt, randomCode } from './crypto.js';
|
|
9
|
+
export interface BuildVaultPluginOptions {
|
|
10
|
+
readonly filePath?: string;
|
|
11
|
+
readonly keySource?: MasterKeySource;
|
|
12
|
+
readonly passphrasePrompt?: () => Promise<string>;
|
|
13
|
+
readonly envVar?: string;
|
|
14
|
+
readonly disableKeytar?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function defaultVaultPath(): string;
|
|
17
|
+
export declare function buildVaultPlugin(opts?: BuildVaultPluginOptions): {
|
|
18
|
+
plugin: Plugin;
|
|
19
|
+
vault: VaultStore;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,MAAM,EAIZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE/E,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtG,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEpG,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC;IACrC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,uBAA4B,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CA+L1G"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { z, defineTool, definePlugin, MoxxyError, } from '@moxxy/sdk';
|
|
2
|
+
import { moxxyPath } from '@moxxy/sdk/server';
|
|
3
|
+
import { createCombinedKeySource } from './keysource.js';
|
|
4
|
+
import { resolveString } from './placeholder.js';
|
|
5
|
+
import { VaultStore } from './store.js';
|
|
6
|
+
export { VaultStore, VaultPassphraseError } from './store.js';
|
|
7
|
+
export { createCombinedKeySource, createStaticKeySource } from './keysource.js';
|
|
8
|
+
export { resolveString, resolveValue, containsPlaceholder } from './placeholder.js';
|
|
9
|
+
export { deriveKey, deriveKeyAsync, encrypt, decrypt, generateSalt, randomCode } from './crypto.js';
|
|
10
|
+
export function defaultVaultPath() {
|
|
11
|
+
return moxxyPath('vault.json');
|
|
12
|
+
}
|
|
13
|
+
export function buildVaultPlugin(opts = {}) {
|
|
14
|
+
const filePath = opts.filePath ?? defaultVaultPath();
|
|
15
|
+
const keySource = opts.keySource ??
|
|
16
|
+
createCombinedKeySource({
|
|
17
|
+
passphrasePrompt: opts.passphrasePrompt ?? defaultPrompt,
|
|
18
|
+
envVar: opts.envVar,
|
|
19
|
+
disableKeytar: opts.disableKeytar,
|
|
20
|
+
});
|
|
21
|
+
const vault = new VaultStore({ filePath, keySource });
|
|
22
|
+
// `/vault` slash command. Lets the USER store a secret out-of-band: the
|
|
23
|
+
// value travels in the slash-command args, which channels intercept and
|
|
24
|
+
// never send to the model. After storing, we inject a note into the event
|
|
25
|
+
// log telling the model only the REFERENCE (`${vault:NAME}`) — never the
|
|
26
|
+
// plaintext — so the model can wire it up (config/tools) without seeing it.
|
|
27
|
+
const vaultCmd = {
|
|
28
|
+
name: 'vault',
|
|
29
|
+
description: 'Store a secret or list stored names',
|
|
30
|
+
argumentHint: 'set <name> <value>',
|
|
31
|
+
pendingNotice: 'updating vault',
|
|
32
|
+
handler: async (ctx) => {
|
|
33
|
+
const trimmed = ctx.args.trim();
|
|
34
|
+
const [sub, ...rest] = trimmed.split(/\s+/);
|
|
35
|
+
if (!sub || sub === 'help') {
|
|
36
|
+
return { kind: 'text', text: vaultCommandUsage() };
|
|
37
|
+
}
|
|
38
|
+
if (sub === 'list') {
|
|
39
|
+
const entries = await vault.list();
|
|
40
|
+
if (entries.length === 0) {
|
|
41
|
+
return { kind: 'text', text: 'Vault is empty. Store a secret with `/vault set <name> <value>`.' };
|
|
42
|
+
}
|
|
43
|
+
const lines = entries.map((e) => ` ${e.name}${e.tags && e.tags.length ? ` [${e.tags.join(', ')}]` : ''} → \${vault:${e.name}}`);
|
|
44
|
+
return { kind: 'text', text: `Stored secrets (values hidden):\n${lines.join('\n')}` };
|
|
45
|
+
}
|
|
46
|
+
if (sub === 'set') {
|
|
47
|
+
const name = rest[0];
|
|
48
|
+
const value = rest.slice(1).join(' ');
|
|
49
|
+
if (!name || !/^[A-Za-z0-9_.-]+$/.test(name)) {
|
|
50
|
+
return {
|
|
51
|
+
kind: 'error',
|
|
52
|
+
message: 'usage: /vault set <name> <value> (name may contain letters, digits, _ . -)',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (!value) {
|
|
56
|
+
return { kind: 'error', message: `usage: /vault set ${name} <value> — a value is required` };
|
|
57
|
+
}
|
|
58
|
+
await vault.set(name, value);
|
|
59
|
+
// Inform the model with a reference only — never the plaintext. Keep it
|
|
60
|
+
// terse: the behavioral guidance ("never ask for plaintext") lives in
|
|
61
|
+
// the vault-setup skill. The note projects as a message on the next turn
|
|
62
|
+
// and renders as a dim system note in the TUI (see EventLine).
|
|
63
|
+
const note = `[vault] Secret "${name}" stored. Reference it as \${vault:${name}} — its value is hidden from you.`;
|
|
64
|
+
const s = ctx.session;
|
|
65
|
+
if (typeof s.log?.append === 'function' && typeof s.startTurn === 'function') {
|
|
66
|
+
try {
|
|
67
|
+
await s.log.append({
|
|
68
|
+
type: 'user_prompt',
|
|
69
|
+
sessionId: ctx.sessionId,
|
|
70
|
+
turnId: s.startTurn().turnId,
|
|
71
|
+
source: 'system',
|
|
72
|
+
text: note,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
// The session exposes the log API but the append itself failed
|
|
77
|
+
// (e.g. disk full / log-rotation race). The secret IS stored, so
|
|
78
|
+
// still confirm to the user below, but surface a warning so a
|
|
79
|
+
// broken log path is diagnosable rather than a silent partial
|
|
80
|
+
// failure where the model never learns the reference exists.
|
|
81
|
+
console.warn(`[vault] stored "${name}" but failed to record its reference note: ` +
|
|
82
|
+
(err instanceof Error ? err.message : String(err)));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// If the host session doesn't expose log/startTurn (unexpected),
|
|
86
|
+
// we skip the note entirely and just confirm storage below.
|
|
87
|
+
return {
|
|
88
|
+
kind: 'text',
|
|
89
|
+
text: `✓ Stored "${name}" securely in the vault. ` +
|
|
90
|
+
`The assistant can reference it as \${vault:${name}} without ever seeing the value.`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return { kind: 'error', message: `unknown subcommand "${sub}".\n${vaultCommandUsage()}` };
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const plugin = definePlugin({
|
|
97
|
+
name: '@moxxy/plugin-vault',
|
|
98
|
+
version: '0.0.0',
|
|
99
|
+
// Publish the secret store on the inter-plugin service registry so sibling
|
|
100
|
+
// plugins (oauth, telegram, mcp, …) can resolve it in their own onInit
|
|
101
|
+
// instead of being hand-built with `{ vault }` — the seam that lets them be
|
|
102
|
+
// discovery-loaded. Consumers declare a requirement on @moxxy/plugin-vault
|
|
103
|
+
// so this onInit runs first.
|
|
104
|
+
hooks: {
|
|
105
|
+
onInit: (ctx) => {
|
|
106
|
+
ctx.services.register('vault', vault);
|
|
107
|
+
// Also publish a `${vault:NAME}`-placeholder resolver so consumers (mcp's
|
|
108
|
+
// server env/headers) can resolve secrets without depending on the vault
|
|
109
|
+
// package's `resolveString` helper directly.
|
|
110
|
+
ctx.services.register('resolveSecrets', (value) => resolveString(value, vault));
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
commands: [vaultCmd],
|
|
114
|
+
tools: [
|
|
115
|
+
defineTool({
|
|
116
|
+
name: 'vault_set',
|
|
117
|
+
description: 'Store a secret in the encrypted vault. Overwrites if name exists. ' +
|
|
118
|
+
'IMPORTANT: do NOT use this for a secret the USER supplies — that would route the ' +
|
|
119
|
+
'plaintext through the conversation. Instead tell the user to run `/vault set <name> <value>` ' +
|
|
120
|
+
'themselves; you only receive a ${vault:<name>} reference. Use this tool only for a value you ' +
|
|
121
|
+
'legitimately already hold and may see.',
|
|
122
|
+
inputSchema: z.object({
|
|
123
|
+
name: z.string().min(1).regex(/^[A-Za-z0-9_.-]+$/),
|
|
124
|
+
value: z.string().min(1),
|
|
125
|
+
tags: z.array(z.string()).optional(),
|
|
126
|
+
}),
|
|
127
|
+
permission: { action: 'prompt' },
|
|
128
|
+
handler: async ({ name, value, tags }) => {
|
|
129
|
+
await vault.set(name, value, tags);
|
|
130
|
+
return `stored ${name} (${value.length} chars) in vault`;
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
defineTool({
|
|
134
|
+
name: 'vault_get',
|
|
135
|
+
description: 'Fetch a secret from the encrypted vault by name. Returns the plaintext value.',
|
|
136
|
+
inputSchema: z.object({ name: z.string().min(1) }),
|
|
137
|
+
permission: { action: 'prompt' },
|
|
138
|
+
handler: async ({ name }) => {
|
|
139
|
+
const value = await vault.get(name);
|
|
140
|
+
if (value === null) {
|
|
141
|
+
throw new MoxxyError({
|
|
142
|
+
code: 'TOOL_ERROR',
|
|
143
|
+
message: `vault: '${name}' not found`,
|
|
144
|
+
hint: 'Use `vault_list` to see stored entry names.',
|
|
145
|
+
context: { name },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
},
|
|
150
|
+
}),
|
|
151
|
+
defineTool({
|
|
152
|
+
name: 'vault_list',
|
|
153
|
+
description: 'List entries in the vault. Returns names + metadata only, never plaintext.',
|
|
154
|
+
inputSchema: z.object({}),
|
|
155
|
+
permission: { action: 'prompt' },
|
|
156
|
+
handler: async () => {
|
|
157
|
+
const entries = await vault.list();
|
|
158
|
+
return entries.map((e) => ({ name: e.name, createdAt: e.createdAt, tags: e.tags ?? [] }));
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
defineTool({
|
|
162
|
+
name: 'vault_delete',
|
|
163
|
+
description: 'Delete a vault entry by name.',
|
|
164
|
+
inputSchema: z.object({ name: z.string().min(1) }),
|
|
165
|
+
permission: { action: 'prompt' },
|
|
166
|
+
handler: async ({ name }) => {
|
|
167
|
+
const removed = await vault.delete(name);
|
|
168
|
+
return removed ? `deleted ${name}` : `not found: ${name}`;
|
|
169
|
+
},
|
|
170
|
+
}),
|
|
171
|
+
defineTool({
|
|
172
|
+
name: 'vault_status',
|
|
173
|
+
description: 'Report which key source unlocked the vault (keychain, env, passphrase).',
|
|
174
|
+
inputSchema: z.object({}),
|
|
175
|
+
handler: async () => {
|
|
176
|
+
await vault.open();
|
|
177
|
+
const entries = await vault.list();
|
|
178
|
+
return { source: vault.sourceName, entries: entries.length };
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
return { plugin, vault };
|
|
184
|
+
}
|
|
185
|
+
function vaultCommandUsage() {
|
|
186
|
+
return ('usage:\n' +
|
|
187
|
+
' /vault set <name> <value> store a secret (the value is hidden from the assistant)\n' +
|
|
188
|
+
' /vault list list stored secret names');
|
|
189
|
+
}
|
|
190
|
+
async function defaultPrompt() {
|
|
191
|
+
// Headless default: refuse if no TTY. Interactive shells override this via opts.
|
|
192
|
+
if (!process.stdin.isTTY) {
|
|
193
|
+
throw new Error('vault: passphrase required but no interactive terminal. Set MOXXY_VAULT_PASSPHRASE or pass a custom passphrasePrompt.');
|
|
194
|
+
}
|
|
195
|
+
const readline = await import('node:readline/promises');
|
|
196
|
+
// Write to stdout (not stderr) and include a leading newline + bold-ish
|
|
197
|
+
// banner so users can't miss the prompt — the bare `vault passphrase: `
|
|
198
|
+
// sent to stderr was easy to overlook (looked like a hang).
|
|
199
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
200
|
+
try {
|
|
201
|
+
process.stdout.write('\n[1m[33mmoxxy vault[39m[22m needs a passphrase.\n' +
|
|
202
|
+
'Pick one now (it\'s stored in your OS keychain if available).\n' +
|
|
203
|
+
'Set [2mMOXXY_VAULT_PASSPHRASE[22m to skip this prompt.\n\n');
|
|
204
|
+
const answer = (await rl.question('passphrase: ')).trim();
|
|
205
|
+
process.stdout.write('\n');
|
|
206
|
+
return answer;
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
rl.close();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,CAAC,EACD,UAAU,EACV,YAAY,EACZ,UAAU,GAKX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAwB,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE9D,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAwB,MAAM,gBAAgB,CAAC;AACtG,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAUpG,MAAM,UAAU,gBAAgB;IAC9B,OAAO,SAAS,CAAC,YAAY,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAgC,EAAE;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,gBAAgB,EAAE,CAAC;IACrD,MAAM,SAAS,GACb,IAAI,CAAC,SAAS;QACd,uBAAuB,CAAC;YACtB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,aAAa;YACxD,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,aAAa,EAAE,IAAI,CAAC,aAAa;SAClC,CAAC,CAAC;IACL,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;IAEtD,wEAAwE;IACxE,wEAAwE;IACxE,0EAA0E;IAC1E,yEAAyE;IACzE,4EAA4E;IAC5E,MAAM,QAAQ,GAAe;QAC3B,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,qCAAqC;QAClD,YAAY,EAAE,oBAAoB;QAClC,aAAa,EAAE,gBAAgB;QAC/B,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACrB,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAE5C,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;gBAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,CAAC;YACrD,CAAC;YAED,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;gBACnB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;gBACnC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kEAAkE,EAAE,CAAC;gBACpG,CAAC;gBACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CACvB,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,IAAI,GAAG,CACzG,CAAC;gBACF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,oCAAoC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACxF,CAAC;YAED,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBACrB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACtC,IAAI,CAAC,IAAI,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC7C,OAAO;wBACL,IAAI,EAAE,OAAO;wBACb,OAAO,EAAE,6EAA6E;qBACvF,CAAC;gBACJ,CAAC;gBACD,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,IAAI,iCAAiC,EAAE,CAAC;gBAChG,CAAC;gBAED,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBAE7B,wEAAwE;gBACxE,sEAAsE;gBACtE,yEAAyE;gBACzE,+DAA+D;gBAC/D,MAAM,IAAI,GACR,mBAAmB,IAAI,sCAAsC,IAAI,mCAAmC,CAAC;gBACvG,MAAM,CAAC,GAAG,GAAG,CAAC,OAAuC,CAAC;gBACtD,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;oBAC7E,IAAI,CAAC;wBACH,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;4BACjB,IAAI,EAAE,aAAa;4BACnB,SAAS,EAAE,GAAG,CAAC,SAAS;4BACxB,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,MAAM;4BAC5B,MAAM,EAAE,QAAQ;4BAChB,IAAI,EAAE,IAAI;yBACX,CAAC,CAAC;oBACL,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,+DAA+D;wBAC/D,iEAAiE;wBACjE,8DAA8D;wBAC9D,8DAA8D;wBAC9D,6DAA6D;wBAC7D,OAAO,CAAC,IAAI,CACV,mBAAmB,IAAI,6CAA6C;4BAClE,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CACrD,CAAC;oBACJ,CAAC;gBACH,CAAC;gBACD,iEAAiE;gBACjE,4DAA4D;gBAE5D,OAAO;oBACL,IAAI,EAAE,MAAM;oBACZ,IAAI,EACF,aAAa,IAAI,2BAA2B;wBAC5C,8CAA8C,IAAI,kCAAkC;iBACvF,CAAC;YACJ,CAAC;YAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,iBAAiB,EAAE,EAAE,EAAE,CAAC;QAC5F,CAAC;KACF,CAAC;IAEF,MAAM,MAAM,GAAG,YAAY,CAAC;QAC1B,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;QAChB,2EAA2E;QAC3E,uEAAuE;QACvE,4EAA4E;QAC5E,2EAA2E;QAC3E,6BAA6B;QAC7B,KAAK,EAAE;YACL,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;gBACd,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBACtC,0EAA0E;gBAC1E,yEAAyE;gBACzE,6CAA6C;gBAC7C,GAAG,CAAC,QAAQ,CAAC,QAAQ,CACnB,gBAAgB,EAChB,CAAC,KAAa,EAAmB,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAChE,CAAC;YACJ,CAAC;SACF;QACD,QAAQ,EAAE,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE;YACL,UAAU,CAAC;gBACT,IAAI,EAAE,WAAW;gBACjB,WAAW,EACT,oEAAoE;oBACpE,mFAAmF;oBACnF,+FAA+F;oBAC/F,+FAA+F;oBAC/F,wCAAwC;gBAC1C,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;oBACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC;oBAClD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;oBACxB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;iBACrC,CAAC;gBACF,UAAU,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;gBAChC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE;oBACvC,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;oBACnC,OAAO,UAAU,IAAI,KAAK,KAAK,CAAC,MAAM,kBAAkB,CAAC;gBAC3D,CAAC;aACF,CAAC;YACF,UAAU,CAAC;gBACT,IAAI,EAAE,WAAW;gBACjB,WAAW,EAAE,+EAA+E;gBAC5F,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClD,UAAU,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;gBAChC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;oBAC1B,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBACpC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;wBACnB,MAAM,IAAI,UAAU,CAAC;4BACnB,IAAI,EAAE,YAAY;4BAClB,OAAO,EAAE,WAAW,IAAI,aAAa;4BACrC,IAAI,EAAE,6CAA6C;4BACnD,OAAO,EAAE,EAAE,IAAI,EAAE;yBAClB,CAAC,CAAC;oBACL,CAAC;oBACD,OAAO,KAAK,CAAC;gBACf,CAAC;aACF,CAAC;YACF,UAAU,CAAC;gBACT,IAAI,EAAE,YAAY;gBAClB,WAAW,EAAE,4EAA4E;gBACzF,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,UAAU,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;gBAChC,OAAO,EAAE,KAAK,IAAI,EAAE;oBAClB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;oBACnC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC5F,CAAC;aACF,CAAC;YACF,UAAU,CAAC;gBACT,IAAI,EAAE,cAAc;gBACpB,WAAW,EAAE,+BAA+B;gBAC5C,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClD,UAAU,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;gBAChC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;oBAC1B,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBACzC,OAAO,OAAO,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC,cAAc,IAAI,EAAE,CAAC;gBAC5D,CAAC;aACF,CAAC;YACF,UAAU,CAAC;gBACT,IAAI,EAAE,cAAc;gBACpB,WAAW,EAAE,yEAAyE;gBACtF,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,OAAO,EAAE,KAAK,IAAI,EAAE;oBAClB,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;oBACnB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;oBACnC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC/D,CAAC;aACF,CAAC;SACH;KACF,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC;AAYD,SAAS,iBAAiB;IACxB,OAAO,CACL,UAAU;QACV,yFAAyF;QACzF,wDAAwD,CACzD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,aAAa;IAC1B,iFAAiF;IACjF,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,uHAAuH,CACxH,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IACxD,wEAAwE;IACxE,wEAAwE;IACxE,4DAA4D;IAC5D,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wDAAwD;YACtD,iEAAiE;YACjE,8DAA8D,CACjE,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface MasterKeySource {
|
|
2
|
+
/** Returns the raw 32-byte AES key. May open a keychain or prompt the user. */
|
|
3
|
+
obtain(salt: Buffer): Promise<Buffer>;
|
|
4
|
+
/**
|
|
5
|
+
* Force-persist a master key obtained out-of-band for future sessions.
|
|
6
|
+
*
|
|
7
|
+
* NB: `VaultStore` does NOT call this — `obtain()` already persists the key
|
|
8
|
+
* as a side effect on the passphrase-prompt path, so the running system
|
|
9
|
+
* never needs it. It exists as a forced-rewrite hook for callers that hold a
|
|
10
|
+
* key themselves (e.g. a hypothetical `moxxy doctor --reseed`). Optional, so
|
|
11
|
+
* sources that can't persist (env/static) simply omit it.
|
|
12
|
+
*/
|
|
13
|
+
persist?(key: Buffer, salt: Buffer): Promise<void>;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CombinedKeySourceOptions {
|
|
17
|
+
readonly passphrasePrompt: () => Promise<string>;
|
|
18
|
+
readonly envVar?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Skip the OS keychain (`@napi-rs/keyring`) entirely, using only the disk
|
|
21
|
+
* cache + passphrase. Named `disableKeytar` for backwards compatibility —
|
|
22
|
+
* the underlying keychain library is now `@napi-rs/keyring`, not keytar.
|
|
23
|
+
*/
|
|
24
|
+
readonly disableKeytar?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Disk fallback for the master key, used when the OS keychain isn't
|
|
27
|
+
* available (no native binary, or it refuses to bind — common on headless
|
|
28
|
+
* Linux). Stored as base64 at this path with mode 0o600 — less secure than
|
|
29
|
+
* the OS keychain, but means the user types their passphrase ONCE instead
|
|
30
|
+
* of every run. Set to false to disable.
|
|
31
|
+
*
|
|
32
|
+
* Default: `~/.moxxy/vault.key`.
|
|
33
|
+
*/
|
|
34
|
+
readonly diskKeyPath?: string | false;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolves the vault master key in priority order:
|
|
38
|
+
* 1. `MOXXY_VAULT_PASSPHRASE` env var (derive on each call — no persistence).
|
|
39
|
+
* 2. OS keychain via `@napi-rs/keyring`.
|
|
40
|
+
* 3. On-disk cached key at `~/.moxxy/vault.key` (mode 0600).
|
|
41
|
+
* 4. Interactive passphrase prompt.
|
|
42
|
+
*
|
|
43
|
+
* The first successful prompt persists the derived key to BOTH the OS keychain
|
|
44
|
+
* (if available) and the disk cache so subsequent runs are silent. The chosen
|
|
45
|
+
* source's name is exposed via `.name` so `moxxy doctor` can surface it
|
|
46
|
+
* ("vault unlocked via keychain" / "via ~/.moxxy/vault.key").
|
|
47
|
+
*/
|
|
48
|
+
export declare function createCombinedKeySource(opts: CombinedKeySourceOptions): MasterKeySource;
|
|
49
|
+
export declare function createStaticKeySource(key: Buffer): MasterKeySource;
|
|
50
|
+
//# sourceMappingURL=keysource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keysource.d.ts","sourceRoot":"","sources":["../src/keysource.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC;;;;;;;;OAQG;IACH,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACvC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,GAAG,eAAe,CAuEvF;AA+DD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAOlE"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { writeFileAtomic, moxxyPath } from '@moxxy/sdk/server';
|
|
3
|
+
import { deriveKeyAsync } from './crypto.js';
|
|
4
|
+
const KEYCHAIN_SERVICE = 'moxxy';
|
|
5
|
+
const KEYCHAIN_ACCOUNT = 'vault-master-key';
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the vault master key in priority order:
|
|
8
|
+
* 1. `MOXXY_VAULT_PASSPHRASE` env var (derive on each call — no persistence).
|
|
9
|
+
* 2. OS keychain via `@napi-rs/keyring`.
|
|
10
|
+
* 3. On-disk cached key at `~/.moxxy/vault.key` (mode 0600).
|
|
11
|
+
* 4. Interactive passphrase prompt.
|
|
12
|
+
*
|
|
13
|
+
* The first successful prompt persists the derived key to BOTH the OS keychain
|
|
14
|
+
* (if available) and the disk cache so subsequent runs are silent. The chosen
|
|
15
|
+
* source's name is exposed via `.name` so `moxxy doctor` can surface it
|
|
16
|
+
* ("vault unlocked via keychain" / "via ~/.moxxy/vault.key").
|
|
17
|
+
*/
|
|
18
|
+
export function createCombinedKeySource(opts) {
|
|
19
|
+
let resolvedName = 'unknown';
|
|
20
|
+
const diskPath = resolveDiskPath(opts.diskKeyPath);
|
|
21
|
+
// Memoize the env-path scrypt derivation. obtain() is called on every
|
|
22
|
+
// VaultStore.open(), and scryptSync (N=16384) is a CPU-bound stall of the
|
|
23
|
+
// single-threaded event loop; without this cache an env-configured process
|
|
24
|
+
// re-pays the full KDF cost every time a fresh VaultStore opens. Keyed by
|
|
25
|
+
// (passphrase, salt) so a salt change still re-derives. Single entry — the
|
|
26
|
+
// env passphrase and the on-disk salt are stable for a process lifetime.
|
|
27
|
+
let envCache = null;
|
|
28
|
+
const deriveEnvKey = async (passphrase, salt) => {
|
|
29
|
+
const saltB64 = salt.toString('base64');
|
|
30
|
+
if (envCache && envCache.passphrase === passphrase && envCache.saltB64 === saltB64) {
|
|
31
|
+
return envCache.key;
|
|
32
|
+
}
|
|
33
|
+
const key = await deriveKeyAsync(passphrase, salt);
|
|
34
|
+
envCache = { passphrase, saltB64, key };
|
|
35
|
+
return key;
|
|
36
|
+
};
|
|
37
|
+
const persistKey = async (keyB64) => {
|
|
38
|
+
if (!opts.disableKeytar)
|
|
39
|
+
await tryKeychainSet(keyB64);
|
|
40
|
+
if (diskPath)
|
|
41
|
+
await tryDiskSet(diskPath, keyB64);
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
get name() {
|
|
45
|
+
return resolvedName;
|
|
46
|
+
},
|
|
47
|
+
async obtain(salt) {
|
|
48
|
+
const envName = opts.envVar ?? 'MOXXY_VAULT_PASSPHRASE';
|
|
49
|
+
const envValue = process.env[envName];
|
|
50
|
+
if (envValue) {
|
|
51
|
+
resolvedName = `env:${envName}`;
|
|
52
|
+
return await deriveEnvKey(envValue, salt);
|
|
53
|
+
}
|
|
54
|
+
if (!opts.disableKeytar) {
|
|
55
|
+
const fromKeychain = await tryKeychainGet();
|
|
56
|
+
if (fromKeychain) {
|
|
57
|
+
resolvedName = 'keychain';
|
|
58
|
+
// Backfill the disk cache so a future keychain outage doesn't
|
|
59
|
+
// suddenly force a passphrase prompt. Awaited (both helpers swallow
|
|
60
|
+
// their own errors) so the "subsequent runs are silent" guarantee
|
|
61
|
+
// actually holds before obtain() resolves, and a process that exits
|
|
62
|
+
// immediately after first open() still persisted the backfill.
|
|
63
|
+
if (diskPath)
|
|
64
|
+
await tryDiskSet(diskPath, fromKeychain);
|
|
65
|
+
return Buffer.from(fromKeychain, 'base64');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (diskPath) {
|
|
69
|
+
const fromDisk = await tryDiskGet(diskPath);
|
|
70
|
+
if (fromDisk) {
|
|
71
|
+
resolvedName = `file:${diskPath}`;
|
|
72
|
+
// Backfill the keychain if it became available since the file was written.
|
|
73
|
+
if (!opts.disableKeytar)
|
|
74
|
+
await tryKeychainSet(fromDisk);
|
|
75
|
+
return Buffer.from(fromDisk, 'base64');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const passphrase = await opts.passphrasePrompt();
|
|
79
|
+
resolvedName = 'passphrase';
|
|
80
|
+
const key = await deriveKeyAsync(passphrase, salt);
|
|
81
|
+
await persistKey(key.toString('base64'));
|
|
82
|
+
return key;
|
|
83
|
+
},
|
|
84
|
+
async persist(key) {
|
|
85
|
+
await persistKey(key.toString('base64'));
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function resolveDiskPath(supplied) {
|
|
90
|
+
if (supplied === false)
|
|
91
|
+
return null;
|
|
92
|
+
if (typeof supplied === 'string')
|
|
93
|
+
return supplied;
|
|
94
|
+
return moxxyPath('vault.key');
|
|
95
|
+
}
|
|
96
|
+
async function tryDiskGet(filePath) {
|
|
97
|
+
try {
|
|
98
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
99
|
+
return raw.trim() || null;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function tryDiskSet(filePath, value) {
|
|
106
|
+
try {
|
|
107
|
+
// Crash-atomic, owner-only (0o600) — this is the cached master key.
|
|
108
|
+
await writeFileAtomic(filePath, value + '\n', { mode: 0o600 });
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Best-effort; if we can't write, the next run will just re-prompt.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function tryKeychainGet() {
|
|
115
|
+
try {
|
|
116
|
+
const mod = (await import('@napi-rs/keyring'));
|
|
117
|
+
if (!mod.Entry)
|
|
118
|
+
return null;
|
|
119
|
+
return new mod.Entry(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT).getPassword() || null;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Not installed, no stored entry, or keychain locked — fall back.
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function tryKeychainSet(value) {
|
|
127
|
+
try {
|
|
128
|
+
const mod = (await import('@napi-rs/keyring'));
|
|
129
|
+
if (!mod.Entry)
|
|
130
|
+
return;
|
|
131
|
+
new mod.Entry(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT).setPassword(value);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Best-effort; keychain failures must not break the vault.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export function createStaticKeySource(key) {
|
|
138
|
+
return {
|
|
139
|
+
name: 'static',
|
|
140
|
+
async obtain() {
|
|
141
|
+
return key;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=keysource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keysource.js","sourceRoot":"","sources":["../src/keysource.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { VaultStore } from './store.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve every `${vault:NAME}` placeholder in a string against the vault. If
|
|
4
|
+
* any referenced key is missing, throws — secret refs are not optional.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveString(input: string, vault: VaultStore): Promise<string>;
|
|
7
|
+
/** Walk an arbitrary value, resolving all vault placeholders in nested strings. */
|
|
8
|
+
export declare function resolveValue(value: unknown, vault: VaultStore): Promise<unknown>;
|
|
9
|
+
export declare function containsPlaceholder(value: unknown): boolean;
|
|
10
|
+
//# sourceMappingURL=placeholder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placeholder.d.ts","sourceRoot":"","sources":["../src/placeholder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAkB7C;;;GAGG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBrF;AAED,mFAAmF;AACnF,wBAAsB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAEtF;AAgCD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE3D"}
|