fix(prefs): stop pulling secret prefs into the renderer just to render the UI
A renderer dump taken after a quick-unlock, with nothing opened, contained the auto-backup password in cleartext - inside an ExecuteJavaScript string and in the DevTools protocol buffers - and it survived the auto-lock that wipes the vault key. New prefs/has answers set-or-not without moving the value. The settings panel uses that; only the paths that encrypt a backup, test the WebDAV connection or run a sync fetch the secrets themselves.
This commit is contained in:
@@ -1280,6 +1280,25 @@ begin
|
|||||||
'Bridge.onPrefResult("' + LEscapedKey + '","' + LEscapedVal + '")');
|
'Bridge.onPrefResult("' + LEscapedKey + '","' + LEscapedVal + '")');
|
||||||
end
|
end
|
||||||
|
|
||||||
|
// Existence probe. Some prefs ARE secrets (the auto-backup password, the sync
|
||||||
|
// passwords) and the UI mostly only needs to know whether one is set. Asking
|
||||||
|
// through prefs/get would interpolate the secret into an ExecuteJavaScript
|
||||||
|
// string — measured in a renderer dump, where it also showed up in the
|
||||||
|
// DevTools protocol buffers, and it stayed there after the vault auto-locked.
|
||||||
|
// The answer rides the same callback under "<key>#has".
|
||||||
|
else if ACmd = 'prefs/has' then
|
||||||
|
begin
|
||||||
|
var LHasKey := GetParam('key');
|
||||||
|
if LHasKey = '' then Exit;
|
||||||
|
var LEsc := StringReplace(LHasKey, '\', '\\', [rfReplaceAll]);
|
||||||
|
LEsc := StringReplace(LEsc, '"', '\"', [rfReplaceAll]);
|
||||||
|
var LSet := '';
|
||||||
|
if PM.UserPrefs.GetPref(LHasKey) <> '' then LSet := '1';
|
||||||
|
WebBrowser.ExecuteJavaScript(
|
||||||
|
'if(window.Bridge&&Bridge.onPrefResult)' +
|
||||||
|
'Bridge.onPrefResult("' + LEsc + '#has","' + LSet + '")');
|
||||||
|
end
|
||||||
|
|
||||||
else if ACmd = 'prefs/set' then
|
else if ACmd = 'prefs/set' then
|
||||||
begin
|
begin
|
||||||
var LKey := GetParam('key');
|
var LKey := GetParam('key');
|
||||||
|
|||||||
+14
-5
@@ -24,15 +24,21 @@ const ABK = {
|
|||||||
};
|
};
|
||||||
const AUTO_BACKUP_PREFIX = 'vault-autobackup-';
|
const AUTO_BACKUP_PREFIX = 'vault-autobackup-';
|
||||||
|
|
||||||
async function loadAutoBackupConfig() {
|
// withPwd defaults to FALSE on purpose: the UI only ever needs to know whether
|
||||||
|
// a backup password exists. Pulling the value across the bridge leaves it in
|
||||||
|
// the renderer for the life of the process — verified in a memory dump, where
|
||||||
|
// it was still there after the vault had auto-locked and the vault key was
|
||||||
|
// gone. Only the two paths that actually encrypt a backup ask for it.
|
||||||
|
async function loadAutoBackupConfig(withPwd) {
|
||||||
if (!Bridge.active) return null;
|
if (!Bridge.active) return null;
|
||||||
const [enabled, dir, interval, keep, last, pwd] = await Promise.all([
|
const [enabled, dir, interval, keep, last, pwd, hasPwd] = await Promise.all([
|
||||||
Bridge.getPref(ABK.enabled),
|
Bridge.getPref(ABK.enabled),
|
||||||
Bridge.getPref(ABK.dir),
|
Bridge.getPref(ABK.dir),
|
||||||
Bridge.getPref(ABK.interval),
|
Bridge.getPref(ABK.interval),
|
||||||
Bridge.getPref(ABK.keep),
|
Bridge.getPref(ABK.keep),
|
||||||
Bridge.getPref(ABK.last),
|
Bridge.getPref(ABK.last),
|
||||||
Bridge.getPref(ABK.pwd),
|
withPwd ? Bridge.getPref(ABK.pwd) : Promise.resolve(''),
|
||||||
|
Bridge.hasPref(ABK.pwd),
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
enabled: enabled === '1',
|
enabled: enabled === '1',
|
||||||
@@ -40,7 +46,7 @@ async function loadAutoBackupConfig() {
|
|||||||
interval: Math.max(1, parseInt(interval, 10) || 7),
|
interval: Math.max(1, parseInt(interval, 10) || 7),
|
||||||
keep: Math.max(1, parseInt(keep, 10) || 10),
|
keep: Math.max(1, parseInt(keep, 10) || 10),
|
||||||
last: last || '',
|
last: last || '',
|
||||||
hasPwd: !!pwd,
|
hasPwd: !!hasPwd,
|
||||||
pwd,
|
pwd,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -139,7 +145,8 @@ async function changeBackupPwd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runAutoBackupNow(silent) {
|
async function runAutoBackupNow(silent) {
|
||||||
const cfg = await loadAutoBackupConfig();
|
// withPwd: this is the one place that encrypts a backup.
|
||||||
|
const cfg = await loadAutoBackupConfig(true);
|
||||||
if (!cfg) return silent || toast('Bridge not available', 'error');
|
if (!cfg) return silent || toast('Bridge not available', 'error');
|
||||||
if (!cfg.dir) return silent || toast('Choose a backup folder first', 'warning');
|
if (!cfg.dir) return silent || toast('Choose a backup folder first', 'warning');
|
||||||
if (!cfg.hasPwd) return silent || toast('Backup password not set', 'warning');
|
if (!cfg.hasPwd) return silent || toast('Backup password not set', 'warning');
|
||||||
@@ -263,6 +270,8 @@ async function applyAutoBackupRetention(dir, keep) {
|
|||||||
|
|
||||||
async function runAutoBackupIfDue() {
|
async function runAutoBackupIfDue() {
|
||||||
if (!Bridge.active) return;
|
if (!Bridge.active) return;
|
||||||
|
// No password yet: the due-check reads only metadata. runAutoBackupNow
|
||||||
|
// fetches the secret itself, and only if a run is actually starting.
|
||||||
const cfg = await loadAutoBackupConfig();
|
const cfg = await loadAutoBackupConfig();
|
||||||
if (!cfg || !cfg.enabled || !cfg.dir || !cfg.hasPwd) return;
|
if (!cfg || !cfg.enabled || !cfg.dir || !cfg.hasPwd) return;
|
||||||
if (!state.cryptoKey) return;
|
if (!state.cryptoKey) return;
|
||||||
|
|||||||
@@ -269,6 +269,25 @@ const Bridge = (() => {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// "Is this pref set?" without pulling the value across. For the ones
|
||||||
|
// that are secrets (backup / sync passwords) the UI usually only needs
|
||||||
|
// the boolean, and fetching the value would leave it sitting in the
|
||||||
|
// renderer — and in the DevTools protocol buffers — until the process
|
||||||
|
// dies, surviving even an auto-lock.
|
||||||
|
hasPref(key) {
|
||||||
|
if (!active) return Promise.resolve(false);
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const slot = key + '#has';
|
||||||
|
prefResolvers[slot] = v => resolve(v === '1');
|
||||||
|
cmd('cmd://prefs/has?key=' + encodeURIComponent(key));
|
||||||
|
setTimeout(() => {
|
||||||
|
if (prefResolvers[slot]) {
|
||||||
|
delete prefResolvers[slot];
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
},
|
||||||
setPref(key, value) {
|
setPref(key, value) {
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
cmd('cmd://prefs/set?key=' + encodeURIComponent(key) +
|
cmd('cmd://prefs/set?key=' + encodeURIComponent(key) +
|
||||||
|
|||||||
+23
-8
@@ -106,10 +106,16 @@ function refreshSyncUI(cfg) {
|
|||||||
const pre = document.getElementById('syncPreBackup');
|
const pre = document.getElementById('syncPreBackup');
|
||||||
if (url) url.value = cfg.url || '';
|
if (url) url.value = cfg.url || '';
|
||||||
if (user) user.value = cfg.user || '';
|
if (user) user.value = cfg.user || '';
|
||||||
if (pwd) pwd.value = cfg.pwd || '';
|
if (pwd) {
|
||||||
|
// Left EMPTY on purpose — the settings panel no longer pulls the stored
|
||||||
|
// password across. The field only saves on `change`, so an untouched
|
||||||
|
// empty field never overwrites anything; the placeholder says so.
|
||||||
|
pwd.value = cfg.pwd || '';
|
||||||
|
pwd.placeholder = cfg.hasPwd ? 'Saved — type to replace' : '';
|
||||||
|
}
|
||||||
if (pre) pre.checked = !!cfg.preBackup;
|
if (pre) pre.checked = !!cfg.preBackup;
|
||||||
const pwdStatus = document.getElementById('syncPwdStatus');
|
const pwdStatus = document.getElementById('syncPwdStatus');
|
||||||
if (pwdStatus) pwdStatus.textContent = cfg.encPwd ? 'Set.' : 'Not set.';
|
if (pwdStatus) pwdStatus.textContent = cfg.hasEncPwd ? 'Set.' : 'Not set.';
|
||||||
const last = document.getElementById('syncLast');
|
const last = document.getElementById('syncLast');
|
||||||
if (last) last.textContent = cfg.last
|
if (last) last.textContent = cfg.last
|
||||||
? ' · Last: ' + cfg.last.replace('T', ' ').slice(0, 16)
|
? ' · Last: ' + cfg.last.replace('T', ' ').slice(0, 16)
|
||||||
@@ -146,16 +152,23 @@ async function syncSetEncPwdFlow() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSyncConfig() {
|
// withSecrets defaults to FALSE: the settings panel only needs to know whether
|
||||||
|
// the two passwords are set. Fetching them plants the values in the renderer
|
||||||
|
// for the life of the process — seen in a memory dump, still present after the
|
||||||
|
// vault had auto-locked. Only the paths that actually talk to the server or
|
||||||
|
// encrypt a snapshot ask for them.
|
||||||
|
async function loadSyncConfig(withSecrets) {
|
||||||
if (!Bridge.active) return null;
|
if (!Bridge.active) return null;
|
||||||
const [enabled, url, user, pwd, encPwd, pre, last] = await Promise.all([
|
const [enabled, url, user, pwd, encPwd, pre, last, hasPwd, hasEnc] = await Promise.all([
|
||||||
Bridge.getPref(SYNC_PREFS.enabled),
|
Bridge.getPref(SYNC_PREFS.enabled),
|
||||||
Bridge.getPref(SYNC_PREFS.url),
|
Bridge.getPref(SYNC_PREFS.url),
|
||||||
Bridge.getPref(SYNC_PREFS.user),
|
Bridge.getPref(SYNC_PREFS.user),
|
||||||
Bridge.getPref(SYNC_PREFS.pwd),
|
withSecrets ? Bridge.getPref(SYNC_PREFS.pwd) : Promise.resolve(''),
|
||||||
Bridge.getPref(SYNC_PREFS.encPwd),
|
withSecrets ? Bridge.getPref(SYNC_PREFS.encPwd) : Promise.resolve(''),
|
||||||
Bridge.getPref(SYNC_PREFS.preBackup),
|
Bridge.getPref(SYNC_PREFS.preBackup),
|
||||||
Bridge.getPref(SYNC_PREFS.last),
|
Bridge.getPref(SYNC_PREFS.last),
|
||||||
|
Bridge.hasPref(SYNC_PREFS.pwd),
|
||||||
|
Bridge.hasPref(SYNC_PREFS.encPwd),
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
enabled: enabled === '1',
|
enabled: enabled === '1',
|
||||||
@@ -163,13 +176,15 @@ async function loadSyncConfig() {
|
|||||||
user: user || '',
|
user: user || '',
|
||||||
pwd: pwd || '',
|
pwd: pwd || '',
|
||||||
encPwd: encPwd || '',
|
encPwd: encPwd || '',
|
||||||
|
hasPwd: !!hasPwd,
|
||||||
|
hasEncPwd: !!hasEnc,
|
||||||
preBackup: pre === '1',
|
preBackup: pre === '1',
|
||||||
last: last || '',
|
last: last || '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncTestConnection() {
|
async function syncTestConnection() {
|
||||||
const cfg = await loadSyncConfig();
|
const cfg = await loadSyncConfig(true); // needs the WebDAV credentials
|
||||||
if (!cfg || !cfg.url) return toast('Set the WebDAV URL first', 'warning');
|
if (!cfg || !cfg.url) return toast('Set the WebDAV URL first', 'warning');
|
||||||
toast('Testing connection…');
|
toast('Testing connection…');
|
||||||
const r = await _webdavCall('test', cfg.url, cfg.user, cfg.pwd);
|
const r = await _webdavCall('test', cfg.url, cfg.user, cfg.pwd);
|
||||||
@@ -464,7 +479,7 @@ function syncStatus(text) {
|
|||||||
|
|
||||||
async function runSyncNow(_attempt) {
|
async function runSyncNow(_attempt) {
|
||||||
_attempt = _attempt || 0;
|
_attempt = _attempt || 0;
|
||||||
const cfg = await loadSyncConfig();
|
const cfg = await loadSyncConfig(true); // talks to the server + encrypts
|
||||||
if (!cfg) return toast('Bridge not available', 'error');
|
if (!cfg) return toast('Bridge not available', 'error');
|
||||||
if (!cfg.url) return toast('Sync not configured', 'warning');
|
if (!cfg.url) return toast('Sync not configured', 'warning');
|
||||||
if (!cfg.encPwd) return toast('Set the sync password first', 'warning');
|
if (!cfg.encPwd) return toast('Set the sync password first', 'warning');
|
||||||
|
|||||||
Reference in New Issue
Block a user