fix: purge keychain credentials on profile delete, atomic owner-only secret writes, zeroize derived keys
- Secrets::delete_profile removes every profile credential kind from the OS keychain as well as the vault (previously only the vault was purged, so keychain passwords/tokens outlived the profile). - write_private writes to a sibling temp file and renames it into place, so a crash mid-write cannot truncate the vault or an export, and a pre-existing world-readable file is replaced rather than reused. - Argon2-derived vault/export keys are Zeroizing and wiped on drop. - Recordings (which capture typed input), JSON exports and --data-dir directories are created owner-only.
This commit is contained in:
@@ -116,7 +116,12 @@ impl Recorder {
|
||||
fn with_limit(dir: &Path, id: &str, title: &str, cols: i64, rows: i64, max_bytes: u64) -> Result<Recorder> {
|
||||
mkdir_private(dir).map_err(|e| format!("recording: failed to create directory: {e}"))?;
|
||||
let path = dir.join(format!("{id}.cast"));
|
||||
let mut f = File::create(&path).map_err(|e| format!("recording: failed to create file: {e}"))?;
|
||||
// Recordings capture typed input (passwords included): owner-only, like the vault.
|
||||
let mut opts = std::fs::OpenOptions::new();
|
||||
opts.write(true).create(true).truncate(true);
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::OpenOptionsExt::mode(&mut opts, 0o600);
|
||||
let mut f = opts.open(&path).map_err(|e| format!("recording: failed to create file: {e}"))?;
|
||||
let start = Instant::now();
|
||||
let hdr = Header {
|
||||
version: 2,
|
||||
@@ -391,6 +396,11 @@ mod tests {
|
||||
assert_eq!(rec.event_count(), 4);
|
||||
rec.close().unwrap();
|
||||
rec.close().unwrap(); // double close is a no-op
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mode = std::os::unix::fs::PermissionsExt::mode(&std::fs::metadata(dir.join("test-session-001.cast")).unwrap().permissions());
|
||||
assert_eq!(mode & 0o777, 0o600);
|
||||
}
|
||||
let text = std::fs::read_to_string(dir.join("test-session-001.cast")).unwrap();
|
||||
let lines: Vec<&str> = text.trim().lines().collect();
|
||||
assert_eq!(lines.len(), 5);
|
||||
|
||||
Reference in New Issue
Block a user