Add scoped human-unlocked vault agent autofill

This commit is contained in:
Karl committed 2026-10-13 19:14:32 +02:00
1 parent 61fcd8336f
commit e8a654fb38
23 files changed
+383 -29

No files matched your search

+11 -3
View File
@@ -49,11 +49,19 @@ function generatePassword(length = 32) {
}
function createVault({ filePath, protect, unprotect, idleSeconds = 300 }) {
if (typeof filePath !== 'string' || !path.isAbsolute(filePath) || typeof protect !== 'function' || typeof unprotect !== 'function' || !Number.isInteger(idleSeconds) || idleSeconds < 60 || idleSeconds > 900) fail();
let key = null, entries = [], envelope = null, timer = null;
let key = null, entries = [], envelope = null, timer = null, accessGeneration = 0;
// Process-local capability epoch. Never persisted or included in public metadata.
// create/unlock begin with lock, so even reopening the same vault invalidates refs.
function invalidateAccess() {
if (accessGeneration >= Number.MAX_SAFE_INTEGER) { lock(); fail(); }
accessGeneration += 1;
}
function lock() {
clearTimeout(timer); timer = null; wipe(key); key = null;
for (const entry of entries) { entry.password = ''; entry.username = ''; entry.label = ''; entry.origin = ''; }
entries = []; envelope = null;
if (accessGeneration >= Number.MAX_SAFE_INTEGER) fail();
accessGeneration += 1;
}
function touch() { clearTimeout(timer); timer = setTimeout(lock, idleSeconds * 1000); timer.unref(); }
function requireUnlocked(refreshIdle = true) { if (!key) fail(); if (refreshIdle) touch(); }
@@ -136,7 +144,7 @@ function createVault({ filePath, protect, unprotect, idleSeconds = 300 }) {
function status() { return { exists: fs.existsSync(filePath), locked: !key, idleSeconds, algorithm: 'AES-256-GCM / ML-KEM-1024 / scrypt / Windows DPAPI' }; }
function list() { requireUnlocked(false); return entries.map(({ id, origin, username, label }) => ({ id, origin, username, label })); }
function mutate(action) {
requireUnlocked(); const previous = entries.map(entry => ({ ...entry }));
requireUnlocked(); invalidateAccess(); const previous = entries.map(entry => ({ ...entry }));
try { const result = action(); persist(); return result; } catch { entries = previous; fail(); }
}
function upsert(entry) {
@@ -156,6 +164,6 @@ function createVault({ filePath, protect, unprotect, idleSeconds = 300 }) {
if (!entry || entry.origin !== expected) fail();
return { username: entry.username, password: entry.password };
}
return { create, unlock, lock, status, list, upsert, delete: remove, credential, generatePassword };
return { create, unlock, lock, status, list, upsert, delete: remove, credential, generatePassword, accessGeneration: () => accessGeneration };
}
module.exports = { createVault, generatePassword };