fix: clip sync timestamps, exclusive temp files, zeroized secret reads
- sync: timestamps and tombstones in the shared file are clipped to the local clock, and an edit wins a tie with a deletion, so a forged future-dated record can no longer override every later local change (or keep deleting a re-created profile) for as long as the forged date. - write_private: the temp file is created O_EXCL under a random name, so a symlink pre-placed in a shared folder can't be followed or overwritten. - Vault/keychain reads return Zeroizing<String>; has_sync_password is cached for a few seconds instead of fetching the real value every frame. - generate_ssh_key creates a missing ~/.ssh as 0700. - Honest wording: encrypted exports and the sync file carry login-script values; the audit entry, the Settings hint and the docs now say so.
This commit is contained in:
@@ -62,7 +62,7 @@ impl Manager {
|
||||
{
|
||||
st.used.insert(key.clone());
|
||||
store.add_audit("secret.access", &pid, "", &format!("Accessed {kind} credential"));
|
||||
return Ok(Zeroizing::new(v));
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
if mode == SecretMode::SessionOnly
|
||||
|
||||
@@ -803,7 +803,7 @@ mod tests {
|
||||
// Typed again and accepted: stored.
|
||||
resolve(&m, "s1");
|
||||
m.finish_connect("s1", e, Ok(Client::None), |_| {});
|
||||
assert_eq!(m.secrets.get(&key).as_deref(), Some("hunter2"));
|
||||
assert_eq!(m.secrets.get(&key).as_deref().map(String::as_str), Some("hunter2"));
|
||||
assert_eq!(asked.load(Ordering::SeqCst), 3);
|
||||
|
||||
// The saved value is reused; once the server rejects it, the next attempt asks again.
|
||||
|
||||
@@ -17,7 +17,7 @@ impl Manager {
|
||||
let passphrase = self.secret_fn(id, &p.id, SecretKind::Passphrase, sc.key_secret_mode, prompt);
|
||||
let c = SshClient::new(&p, self.terminal_callbacks(id, epoch));
|
||||
c.set_authorities(self.cert_authorities());
|
||||
let stored = |k: SecretKind| self.secrets.get(&k.key(&p.id)).unwrap_or_default();
|
||||
let stored = |k: SecretKind| self.secrets.get(&k.key(&p.id)).map(|v| v.to_string()).unwrap_or_default();
|
||||
c.set_stored_secrets(stored(SecretKind::Totp), stored(SecretKind::ProxyPassword));
|
||||
c.connect(self.host_key_check(id, sc.host_key_policy), Some(password), Some(passphrase))?;
|
||||
for pf in sc.port_forwards.iter().filter(|f| f.enabled) {
|
||||
@@ -347,7 +347,7 @@ impl Manager {
|
||||
host: rc.proxy_host.clone(),
|
||||
port: rc.proxy_port as u16,
|
||||
username: rc.proxy_username.clone(),
|
||||
password: self.secrets.get(&SecretKind::ProxyPassword.key(&p.id)).unwrap_or_default(),
|
||||
password: self.secrets.get(&SecretKind::ProxyPassword.key(&p.id)).map(|v| v.to_string()).unwrap_or_default(),
|
||||
};
|
||||
let h = host.clone();
|
||||
Arc::new(move || proxy.connect(&h, port, timeout))
|
||||
|
||||
Reference in New Issue
Block a user