@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.
@@ -0,0 +1,92 @@
1
+ import { MoxxyError } from '@moxxy/sdk';
2
+ const PLACEHOLDER_RE = /\$\{vault:([A-Za-z0-9_.-]+)\}/g;
3
+ // Bound recursion over caller-supplied objects: a pathologically deep config
4
+ // (or an in-memory reference cycle, which is legal for JS objects even though
5
+ // JSON can't express one) would otherwise overflow the stack and take down the
6
+ // process. 64 levels is far deeper than any real config tree.
7
+ const MAX_DEPTH = 64;
8
+ function tooDeepError() {
9
+ return new MoxxyError({
10
+ code: 'CONFIG_INVALID',
11
+ message: `vault: value nested too deeply (> ${MAX_DEPTH} levels) — possible reference cycle`,
12
+ hint: 'Flatten the config or remove the cyclic reference before resolving vault placeholders.',
13
+ });
14
+ }
15
+ /**
16
+ * Resolve every `${vault:NAME}` placeholder in a string against the vault. If
17
+ * any referenced key is missing, throws — secret refs are not optional.
18
+ */
19
+ export async function resolveString(input, vault) {
20
+ PLACEHOLDER_RE.lastIndex = 0;
21
+ if (!PLACEHOLDER_RE.test(input))
22
+ return input;
23
+ PLACEHOLDER_RE.lastIndex = 0;
24
+ const names = new Set();
25
+ let m;
26
+ while ((m = PLACEHOLDER_RE.exec(input)))
27
+ names.add(m[1]);
28
+ const values = new Map();
29
+ for (const name of names) {
30
+ const value = await vault.get(name);
31
+ if (value === null) {
32
+ throw new MoxxyError({
33
+ code: 'CONFIG_INVALID',
34
+ message: `vault: missing required entry '${name}' referenced in config`,
35
+ hint: `Add it with \`/vault set ${name} <value>\` (or the \`vault_set\` tool), then retry.`,
36
+ context: { name },
37
+ });
38
+ }
39
+ values.set(name, value);
40
+ }
41
+ return input.replace(PLACEHOLDER_RE, (_match, name) => values.get(name) ?? '');
42
+ }
43
+ /** Walk an arbitrary value, resolving all vault placeholders in nested strings. */
44
+ export async function resolveValue(value, vault) {
45
+ return resolveValueInner(value, vault, new Set());
46
+ }
47
+ // `ancestors` is the chain of objects on the path from the root to (but not
48
+ // including) the current node, so a node that appears as its own ancestor is a
49
+ // true cycle — while a shared object reachable via two *sibling* paths (a legal
50
+ // DAG) is resolved on each path rather than falsely flagged. The chain length
51
+ // is the nesting depth, so its size doubles as the depth bound.
52
+ async function resolveValueInner(value, vault, ancestors) {
53
+ if (typeof value === 'string')
54
+ return await resolveString(value, vault);
55
+ if (value && typeof value === 'object') {
56
+ if (ancestors.has(value))
57
+ throw tooDeepError(); // reference cycle
58
+ if (ancestors.size >= MAX_DEPTH)
59
+ throw tooDeepError();
60
+ const nextAncestors = new Set(ancestors).add(value);
61
+ if (Array.isArray(value)) {
62
+ return Promise.all(value.map((v) => resolveValueInner(v, vault, nextAncestors)));
63
+ }
64
+ // Resolve object properties concurrently (mirrors the array branch); each
65
+ // leaf may await vault.get(), so serializing them needlessly serializes I/O.
66
+ const pairs = await Promise.all(Object.entries(value).map(async ([k, v]) => [k, await resolveValueInner(v, vault, nextAncestors)]));
67
+ return Object.fromEntries(pairs);
68
+ }
69
+ return value;
70
+ }
71
+ export function containsPlaceholder(value) {
72
+ return containsPlaceholderInner(value, new Set());
73
+ }
74
+ function containsPlaceholderInner(value, ancestors) {
75
+ if (typeof value === 'string') {
76
+ PLACEHOLDER_RE.lastIndex = 0;
77
+ return PLACEHOLDER_RE.test(value);
78
+ }
79
+ if (value && typeof value === 'object') {
80
+ if (ancestors.has(value))
81
+ return false; // cycle — this subtree already inspected on the path
82
+ if (ancestors.size >= MAX_DEPTH)
83
+ throw tooDeepError();
84
+ const nextAncestors = new Set(ancestors).add(value);
85
+ if (Array.isArray(value)) {
86
+ return value.some((v) => containsPlaceholderInner(v, nextAncestors));
87
+ }
88
+ return Object.values(value).some((v) => containsPlaceholderInner(v, nextAncestors));
89
+ }
90
+ return false;
91
+ }
92
+ //# sourceMappingURL=placeholder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholder.js","sourceRoot":"","sources":["../src/placeholder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import { type EncryptedBlob } from './crypto.js';
2
+ import type { MasterKeySource } from './keysource.js';
3
+ /**
4
+ * Thrown when the supplied passphrase doesn't match the stored vault.
5
+ * Surfaced to the user with a recovery hint instead of the raw AES-GCM
6
+ * "Unsupported state or unable to authenticate data" error.
7
+ */
8
+ export declare class VaultPassphraseError extends Error {
9
+ readonly filePath: string;
10
+ constructor(filePath: string);
11
+ }
12
+ export interface VaultEntry extends EncryptedBlob {
13
+ readonly createdAt: string;
14
+ readonly updatedAt: string;
15
+ readonly tags?: ReadonlyArray<string>;
16
+ }
17
+ export interface VaultEntryInfo {
18
+ readonly name: string;
19
+ readonly createdAt: string;
20
+ readonly updatedAt: string;
21
+ readonly tags?: ReadonlyArray<string>;
22
+ }
23
+ export interface VaultStoreOptions {
24
+ readonly filePath: string;
25
+ readonly keySource: MasterKeySource;
26
+ }
27
+ export declare class VaultStore {
28
+ private readonly filePath;
29
+ private readonly keySource;
30
+ private file;
31
+ private masterKey;
32
+ private readonly mutex;
33
+ private lastSynced;
34
+ constructor(opts: VaultStoreOptions);
35
+ open(): Promise<void>;
36
+ get sourceName(): string;
37
+ private load;
38
+ /**
39
+ * Obtain the master key and return a copy the store solely owns. Key sources
40
+ * may hand back a buffer they (or the caller) keep a reference to — copying
41
+ * lets `close()` zero our buffer without corrupting that shared one.
42
+ */
43
+ private obtainOwnedKey;
44
+ private corruptError;
45
+ /**
46
+ * Confirm the master key decrypts the file's canary (or, for legacy
47
+ * vaults without a canary, the first stored entry). On mismatch throw
48
+ * a friendly `VaultPassphraseError` rather than letting the cryptic
49
+ * "Unsupported state or unable to authenticate data" error from
50
+ * Node's AES-GCM bubble up later.
51
+ */
52
+ private verifyPassphrase;
53
+ /**
54
+ * Fold other writers' entries in from the on-disk file. Historically the
55
+ * vault persisted a whole-file snapshot of THIS instance's memory, so a
56
+ * write from any other `VaultStore` (another moxxy process, or a second
57
+ * instance in this one) was silently clobbered by our next persist —
58
+ * last-writer-wins, fatal for single-use rotated OAuth refresh tokens.
59
+ * Now every read and every mutation first re-reads the file (mtime/size
60
+ * gated, so the common no-other-writer case costs one `stat`) and merges:
61
+ * - key on both sides → newer `updatedAt` wins (ISO timestamps);
62
+ * - key only on disk → adopt it (another writer added it);
63
+ * - key only in memory → drop it (every mutation persists before
64
+ * returning, so a key missing on disk was deleted by another writer).
65
+ * Skipped when the on-disk salt differs (vault wiped/recreated — those
66
+ * entries are undecryptable under our master key) or the file is
67
+ * unreadable/corrupt (keep memory; the next persist restores a good file).
68
+ *
69
+ * Must be called while holding `this.mutex`.
70
+ */
71
+ private syncFromDisk;
72
+ /**
73
+ * Crash-atomic write: serialize to a sibling tmp file, then rename. POSIX
74
+ * rename is atomic, so a crash mid-write leaves the previous vault intact
75
+ * rather than truncated. mode 0o600 keeps the secret store owner-only.
76
+ */
77
+ private persist;
78
+ set(name: string, value: string, tags?: ReadonlyArray<string>): Promise<void>;
79
+ get(name: string): Promise<string | null>;
80
+ /**
81
+ * Decrypt a single stored blob, converting any low-level crypto failure into
82
+ * a friendly `VAULT_CORRUPT` error. `validateVaultFile` only guarantees the
83
+ * blob's iv/tag/data are *strings*, not that they base64-decode to the right
84
+ * lengths — a partially-corrupted entry (e.g. a truncated `iv` or `tag`, or a
85
+ * value re-keyed out-of-band) passes structural validation yet makes Node's
86
+ * AES-GCM throw a raw `ERR_CRYPTO_INVALID_IV` / `ERR_CRYPTO_INVALID_AUTH_TAG`
87
+ * / auth-tag-mismatch error. The vault's passphrase is already verified
88
+ * against the canary on open(), so a per-entry decrypt failure means THAT
89
+ * entry is corrupt, not that the whole vault is locked — surface a targeted
90
+ * recovery hint instead of crashing the caller with a cryptic crypto error.
91
+ */
92
+ private decryptEntry;
93
+ has(name: string): Promise<boolean>;
94
+ delete(name: string): Promise<boolean>;
95
+ list(): Promise<ReadonlyArray<VaultEntryInfo>>;
96
+ /**
97
+ * Wipe the in-memory master key and cached file so the AES key is no longer
98
+ * recoverable from a heap/core dump or swap after the store is done. The
99
+ * store can be reopened — `open()` re-derives the key. Returned plaintext
100
+ * strings from `get()` cannot be wiped (JS strings are immutable); only the
101
+ * long-lived key Buffer is zeroed here.
102
+ */
103
+ close(): void;
104
+ [Symbol.dispose](): void;
105
+ }
106
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AA2BtD;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM;CAU7C;AAED,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;CACrC;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;IAC5C,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,SAAS,CAAuB;IAKxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAG9C,OAAO,CAAC,UAAU,CAAkD;gBAExD,IAAI,EAAE,iBAAiB;IAK7B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,IAAI,UAAU,IAAI,MAAM,CAEvB;YAEa,IAAI;IAwDlB;;;;OAIG;YACW,cAAc;IAI5B,OAAO,CAAC,YAAY;IASpB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAexB;;;;;;;;;;;;;;;;;OAiBG;YACW,YAAY;IA6C1B;;;;OAIG;YACW,OAAO;IAYf,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB7E,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAc/C;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,YAAY;IAcd,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQnC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IActC,IAAI,IAAI,OAAO,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;IAcpD;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;IAOb,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAGzB"}
package/dist/store.js ADDED
@@ -0,0 +1,415 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { createMutex, MoxxyError } from '@moxxy/sdk';
3
+ import { writeFileAtomic } from '@moxxy/sdk/server';
4
+ import { decrypt, encrypt, generateSalt } from './crypto.js';
5
+ const CANARY_PLAINTEXT = 'moxxy:vault:v1';
6
+ // Upper bound on filesystem mtime granularity (HFS+/old ext are ~1-2s). Within
7
+ // this window of "now" an unchanged (mtime,size) fingerprint can't be trusted
8
+ // to mean "no other writer", so syncFromDisk re-reads instead of fast-pathing.
9
+ const MTIME_GRANULARITY_MS = 2000;
10
+ /**
11
+ * Thrown when the supplied passphrase doesn't match the stored vault.
12
+ * Surfaced to the user with a recovery hint instead of the raw AES-GCM
13
+ * "Unsupported state or unable to authenticate data" error.
14
+ */
15
+ export class VaultPassphraseError extends Error {
16
+ filePath;
17
+ constructor(filePath) {
18
+ super(`Wrong vault passphrase for ${filePath}.\n` +
19
+ ` If you've forgotten it, wipe the vault and key cache, then re-run \`moxxy init\`:\n` +
20
+ ` rm ${filePath} ~/.moxxy/vault.key\n` +
21
+ ` (If the OS keychain holds a cached key, also clear it: \`security delete-generic-password -s moxxy\` on macOS.)\n` +
22
+ ` Or set MOXXY_VAULT_PASSPHRASE to a known value to skip the prompt entirely.`);
23
+ this.filePath = filePath;
24
+ this.name = 'VaultPassphraseError';
25
+ }
26
+ }
27
+ export class VaultStore {
28
+ filePath;
29
+ keySource;
30
+ file = null;
31
+ masterKey = null;
32
+ // Serializes every mutator (set/delete) and persist() so concurrent
33
+ // writers don't clobber each other through the read-modify-write +
34
+ // whole-file rewrite. open() is also chained through this so two
35
+ // parallel `open()` calls never both derive a fresh salt.
36
+ mutex = createMutex();
37
+ // stat() fingerprint of the file as of our last load/sync/persist; lets
38
+ // syncFromDisk() skip the read+parse when nothing else has written.
39
+ lastSynced = null;
40
+ constructor(opts) {
41
+ this.filePath = opts.filePath;
42
+ this.keySource = opts.keySource;
43
+ }
44
+ async open() {
45
+ if (this.file && this.masterKey)
46
+ return;
47
+ return this.mutex.run(async () => {
48
+ if (this.file && this.masterKey)
49
+ return;
50
+ await this.load();
51
+ });
52
+ }
53
+ get sourceName() {
54
+ return this.keySource.name;
55
+ }
56
+ async load() {
57
+ let raw = null;
58
+ try {
59
+ raw = await fs.readFile(this.filePath, 'utf8');
60
+ }
61
+ catch (err) {
62
+ if (!isEnoent(err))
63
+ throw err;
64
+ }
65
+ if (raw === null) {
66
+ const salt = generateSalt();
67
+ this.masterKey = await this.obtainOwnedKey(salt);
68
+ this.file = {
69
+ version: 1,
70
+ kdf: 'scrypt',
71
+ salt: salt.toString('base64'),
72
+ entries: {},
73
+ canary: encrypt(CANARY_PLAINTEXT, this.masterKey),
74
+ };
75
+ await this.persist();
76
+ return;
77
+ }
78
+ let parsedRaw;
79
+ try {
80
+ parsedRaw = JSON.parse(raw);
81
+ }
82
+ catch {
83
+ throw this.corruptError('Vault file is not valid JSON');
84
+ }
85
+ if (!isPlainObject(parsedRaw)) {
86
+ throw this.corruptError('Vault file is not a JSON object');
87
+ }
88
+ const parsed = parsedRaw;
89
+ if (parsed.version !== 1 || parsed.kdf !== 'scrypt') {
90
+ throw new MoxxyError({
91
+ code: 'VAULT_CORRUPT',
92
+ message: `Unsupported vault file: version=${String(parsed.version)} kdf=${String(parsed.kdf)}`,
93
+ hint: `This vault was written by an incompatible version. Back up and remove ${this.filePath} to re-initialize.`,
94
+ context: { filePath: this.filePath },
95
+ });
96
+ }
97
+ if (!validateVaultFile(parsed)) {
98
+ throw this.corruptError('Vault file is malformed (bad salt/entries/canary shape)');
99
+ }
100
+ this.file = parsed;
101
+ const salt = Buffer.from(parsed.salt, 'base64');
102
+ this.masterKey = await this.obtainOwnedKey(salt);
103
+ this.verifyPassphrase();
104
+ // Backfill the canary on the first successful open of a legacy vault.
105
+ if (!this.file.canary) {
106
+ this.file = { ...this.file, canary: encrypt(CANARY_PLAINTEXT, this.masterKey) };
107
+ try {
108
+ await this.persist();
109
+ }
110
+ catch {
111
+ // Best-effort — failing to backfill doesn't break the open.
112
+ }
113
+ }
114
+ }
115
+ /**
116
+ * Obtain the master key and return a copy the store solely owns. Key sources
117
+ * may hand back a buffer they (or the caller) keep a reference to — copying
118
+ * lets `close()` zero our buffer without corrupting that shared one.
119
+ */
120
+ async obtainOwnedKey(salt) {
121
+ return Buffer.from(await this.keySource.obtain(salt));
122
+ }
123
+ corruptError(message) {
124
+ return new MoxxyError({
125
+ code: 'VAULT_CORRUPT',
126
+ message,
127
+ hint: `Back up and remove ${this.filePath} to re-initialize the vault.`,
128
+ context: { filePath: this.filePath },
129
+ });
130
+ }
131
+ /**
132
+ * Confirm the master key decrypts the file's canary (or, for legacy
133
+ * vaults without a canary, the first stored entry). On mismatch throw
134
+ * a friendly `VaultPassphraseError` rather than letting the cryptic
135
+ * "Unsupported state or unable to authenticate data" error from
136
+ * Node's AES-GCM bubble up later.
137
+ */
138
+ verifyPassphrase() {
139
+ if (!this.file || !this.masterKey)
140
+ return;
141
+ const probe = this.file.canary ?? firstEntry(this.file.entries);
142
+ if (!probe)
143
+ return; // empty legacy vault — nothing to verify yet.
144
+ try {
145
+ const plaintext = decrypt(probe, this.masterKey);
146
+ if (this.file.canary && plaintext !== CANARY_PLAINTEXT) {
147
+ throw new VaultPassphraseError(this.filePath);
148
+ }
149
+ }
150
+ catch (err) {
151
+ if (err instanceof VaultPassphraseError)
152
+ throw err;
153
+ throw new VaultPassphraseError(this.filePath);
154
+ }
155
+ }
156
+ /**
157
+ * Fold other writers' entries in from the on-disk file. Historically the
158
+ * vault persisted a whole-file snapshot of THIS instance's memory, so a
159
+ * write from any other `VaultStore` (another moxxy process, or a second
160
+ * instance in this one) was silently clobbered by our next persist —
161
+ * last-writer-wins, fatal for single-use rotated OAuth refresh tokens.
162
+ * Now every read and every mutation first re-reads the file (mtime/size
163
+ * gated, so the common no-other-writer case costs one `stat`) and merges:
164
+ * - key on both sides → newer `updatedAt` wins (ISO timestamps);
165
+ * - key only on disk → adopt it (another writer added it);
166
+ * - key only in memory → drop it (every mutation persists before
167
+ * returning, so a key missing on disk was deleted by another writer).
168
+ * Skipped when the on-disk salt differs (vault wiped/recreated — those
169
+ * entries are undecryptable under our master key) or the file is
170
+ * unreadable/corrupt (keep memory; the next persist restores a good file).
171
+ *
172
+ * Must be called while holding `this.mutex`.
173
+ */
174
+ async syncFromDisk() {
175
+ if (!this.file)
176
+ return;
177
+ let st;
178
+ try {
179
+ st = await fs.stat(this.filePath);
180
+ }
181
+ catch (err) {
182
+ if (isEnoent(err))
183
+ return; // deleted out from under us — keep memory
184
+ throw err;
185
+ }
186
+ // Fast path: skip the read+merge only when the stat fingerprint is
187
+ // unchanged AND it has aged past the filesystem's mtime granularity. On
188
+ // coarse-mtime filesystems (HFS+, older ext) two writes inside the same
189
+ // ~1-2s tick that also produce an identical byte length share a
190
+ // (mtime,size) fingerprint, so a recent match might hide a sibling write.
191
+ // The merge is idempotent and cheap, so when in doubt we re-read.
192
+ if (this.lastSynced &&
193
+ st.mtimeMs === this.lastSynced.mtimeMs &&
194
+ st.size === this.lastSynced.size &&
195
+ Date.now() - st.mtimeMs > MTIME_GRANULARITY_MS) {
196
+ return;
197
+ }
198
+ let parsedRaw;
199
+ try {
200
+ parsedRaw = JSON.parse(await fs.readFile(this.filePath, 'utf8'));
201
+ }
202
+ catch {
203
+ return;
204
+ }
205
+ if (!isPlainObject(parsedRaw))
206
+ return;
207
+ const parsed = parsedRaw;
208
+ if (parsed.version !== 1 ||
209
+ parsed.kdf !== 'scrypt' ||
210
+ parsed.salt !== this.file.salt ||
211
+ !validateVaultFile(parsed)) {
212
+ return;
213
+ }
214
+ this.file = { ...this.file, entries: mergeEntries(this.file.entries, parsed.entries) };
215
+ // Fingerprint from BEFORE the read: if a write landed in between, the
216
+ // next sync simply re-reads — never the other way around.
217
+ this.lastSynced = { mtimeMs: st.mtimeMs, size: st.size };
218
+ }
219
+ /**
220
+ * Crash-atomic write: serialize to a sibling tmp file, then rename. POSIX
221
+ * rename is atomic, so a crash mid-write leaves the previous vault intact
222
+ * rather than truncated. mode 0o600 keeps the secret store owner-only.
223
+ */
224
+ async persist() {
225
+ if (!this.file)
226
+ return;
227
+ await writeFileAtomic(this.filePath, JSON.stringify(this.file, null, 2), { mode: 0o600 });
228
+ // Force the next syncFromDisk() to re-read rather than recording a stat
229
+ // here: between our rename and a post-write fs.stat another process may
230
+ // have renamed ITS version in, so the fingerprint we'd capture could be
231
+ // a foreign file's — which would then make us skip reading the very
232
+ // update we're out of sync with. Dropping the fingerprint costs one
233
+ // extra (idempotent, merge-only) read on the next access.
234
+ this.lastSynced = null;
235
+ }
236
+ async set(name, value, tags) {
237
+ await this.open();
238
+ return this.mutex.run(async () => {
239
+ if (!this.file || !this.masterKey)
240
+ throw new Error('vault not open');
241
+ await this.syncFromDisk();
242
+ const now = new Date().toISOString();
243
+ const existing = this.file.entries[name];
244
+ const blob = encrypt(value, this.masterKey);
245
+ this.file = {
246
+ ...this.file,
247
+ entries: {
248
+ ...this.file.entries,
249
+ [name]: {
250
+ ...blob,
251
+ createdAt: existing?.createdAt ?? now,
252
+ updatedAt: now,
253
+ tags: tags ?? existing?.tags,
254
+ },
255
+ },
256
+ };
257
+ await this.persist();
258
+ });
259
+ }
260
+ async get(name) {
261
+ await this.open();
262
+ // Read the entry inside the same mutex turn as syncFromDisk so a
263
+ // concurrent set()/delete() (which replaces this.file wholesale) can't
264
+ // swap the snapshot out between the sync and the decrypt.
265
+ return this.mutex.run(async () => {
266
+ await this.syncFromDisk();
267
+ if (!this.file || !this.masterKey)
268
+ throw new Error('vault not open');
269
+ const entry = this.file.entries[name];
270
+ if (!entry)
271
+ return null;
272
+ return this.decryptEntry(name, entry);
273
+ });
274
+ }
275
+ /**
276
+ * Decrypt a single stored blob, converting any low-level crypto failure into
277
+ * a friendly `VAULT_CORRUPT` error. `validateVaultFile` only guarantees the
278
+ * blob's iv/tag/data are *strings*, not that they base64-decode to the right
279
+ * lengths — a partially-corrupted entry (e.g. a truncated `iv` or `tag`, or a
280
+ * value re-keyed out-of-band) passes structural validation yet makes Node's
281
+ * AES-GCM throw a raw `ERR_CRYPTO_INVALID_IV` / `ERR_CRYPTO_INVALID_AUTH_TAG`
282
+ * / auth-tag-mismatch error. The vault's passphrase is already verified
283
+ * against the canary on open(), so a per-entry decrypt failure means THAT
284
+ * entry is corrupt, not that the whole vault is locked — surface a targeted
285
+ * recovery hint instead of crashing the caller with a cryptic crypto error.
286
+ */
287
+ decryptEntry(name, entry) {
288
+ try {
289
+ return decrypt(entry, this.masterKey);
290
+ }
291
+ catch (err) {
292
+ throw new MoxxyError({
293
+ code: 'VAULT_CORRUPT',
294
+ message: `vault: entry '${name}' could not be decrypted (corrupt or re-keyed)`,
295
+ hint: `Re-store it with \`/vault set ${name} <value>\`, or back up and remove ${this.filePath} to re-initialize.`,
296
+ context: { filePath: this.filePath, name },
297
+ cause: err,
298
+ });
299
+ }
300
+ }
301
+ async has(name) {
302
+ await this.open();
303
+ return this.mutex.run(async () => {
304
+ await this.syncFromDisk();
305
+ return Boolean(this.file?.entries[name]);
306
+ });
307
+ }
308
+ async delete(name) {
309
+ await this.open();
310
+ return this.mutex.run(async () => {
311
+ if (!this.file)
312
+ return false;
313
+ await this.syncFromDisk();
314
+ if (!(name in this.file.entries))
315
+ return false;
316
+ const { [name]: _removed, ...rest } = this.file.entries;
317
+ void _removed;
318
+ this.file = { ...this.file, entries: rest };
319
+ await this.persist();
320
+ return true;
321
+ });
322
+ }
323
+ async list() {
324
+ await this.open();
325
+ return this.mutex.run(async () => {
326
+ await this.syncFromDisk();
327
+ if (!this.file)
328
+ return [];
329
+ return Object.entries(this.file.entries).map(([name, e]) => ({
330
+ name,
331
+ createdAt: e.createdAt,
332
+ updatedAt: e.updatedAt,
333
+ tags: e.tags,
334
+ }));
335
+ });
336
+ }
337
+ /**
338
+ * Wipe the in-memory master key and cached file so the AES key is no longer
339
+ * recoverable from a heap/core dump or swap after the store is done. The
340
+ * store can be reopened — `open()` re-derives the key. Returned plaintext
341
+ * strings from `get()` cannot be wiped (JS strings are immutable); only the
342
+ * long-lived key Buffer is zeroed here.
343
+ */
344
+ close() {
345
+ this.masterKey?.fill(0);
346
+ this.masterKey = null;
347
+ this.file = null;
348
+ this.lastSynced = null;
349
+ }
350
+ [Symbol.dispose]() {
351
+ this.close();
352
+ }
353
+ }
354
+ /**
355
+ * Merge our in-memory entries with another writer's on-disk entries. Disk is
356
+ * the base (all current writers merge-before-persist, so disk is the union of
357
+ * everyone's persisted state); when both sides hold a key, the newer
358
+ * `updatedAt` wins so a stale whole-file write from an older moxxy can't roll
359
+ * back a fresher value (e.g. a just-rotated OAuth refresh token).
360
+ */
361
+ function mergeEntries(memory, disk) {
362
+ const merged = { ...disk };
363
+ for (const [name, mine] of Object.entries(memory)) {
364
+ const theirs = merged[name];
365
+ if (theirs && theirs.updatedAt >= mine.updatedAt)
366
+ continue;
367
+ if (!theirs)
368
+ continue; // absent on disk → deleted by another writer
369
+ merged[name] = mine;
370
+ }
371
+ return merged;
372
+ }
373
+ function isEnoent(err) {
374
+ return err instanceof Error && 'code' in err && err.code === 'ENOENT';
375
+ }
376
+ function isPlainObject(v) {
377
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
378
+ }
379
+ function isEncryptedBlob(v) {
380
+ return (isPlainObject(v) &&
381
+ typeof v.iv === 'string' &&
382
+ typeof v.tag === 'string' &&
383
+ typeof v.data === 'string');
384
+ }
385
+ /**
386
+ * Structural validation of a parsed vault file beyond the version/kdf gate:
387
+ * `salt` is a string, `entries` is a plain object whose every value is a
388
+ * well-formed encrypted blob (with createdAt/updatedAt strings), and `canary`
389
+ * (when present) is a blob. Guards against partial writes / manual edits that
390
+ * are valid JSON but would otherwise throw a raw TypeError in
391
+ * verifyPassphrase()/list() instead of a friendly VAULT_CORRUPT error.
392
+ */
393
+ function validateVaultFile(parsed) {
394
+ if (typeof parsed.salt !== 'string')
395
+ return false;
396
+ if (!isPlainObject(parsed.entries))
397
+ return false;
398
+ for (const entry of Object.values(parsed.entries)) {
399
+ if (!isEncryptedBlob(entry))
400
+ return false;
401
+ const e = entry;
402
+ if (typeof e.createdAt !== 'string' || typeof e.updatedAt !== 'string')
403
+ return false;
404
+ }
405
+ if (parsed.canary !== undefined && !isEncryptedBlob(parsed.canary))
406
+ return false;
407
+ return true;
408
+ }
409
+ function firstEntry(entries) {
410
+ for (const key of Object.keys(entries)) {
411
+ return entries[key];
412
+ }
413
+ return undefined;
414
+ }
415
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}