fix: address audit findings — polling, symlinks, validation, wallpaper removal (v0.7.0)

Three parallel audits (quality, performance, security) identified issues
across the codebase. This commit addresses all remaining findings:

- Replace busy-loop polling in run_command with child.wait() + timeout thread
- Canonicalize ~/.face and AccountsService avatar paths to prevent symlink abuse
- Add detect_locale_with() DI function for testable locale detection
- Move config I/O from activate() to main() to avoid blocking GTK main loop
- Validate background_blur range (0–200), reject invalid values with warning
- Remove embedded wallpaper from GResource — moonarch provides it via filesystem
  (binary size ~3.2MB → ~1.3MB)
This commit is contained in:
2026-03-28 23:09:29 +01:00
parent 71670eb263
commit 5a6900e85a
13 changed files with 242 additions and 95 deletions
+19 -9
View File
@@ -52,20 +52,27 @@ pub fn get_avatar_path_with(
username: Option<&str>,
accountsservice_dir: &Path,
) -> Option<PathBuf> {
// ~/.face takes priority
// ~/.face takes priority — canonicalize to resolve symlinks
let face = home.join(".face");
if face.exists() {
log::debug!("Avatar: using ~/.face");
return Some(face);
if let Ok(canonical) = std::fs::canonicalize(&face) {
log::debug!("Avatar: using ~/.face ({})", canonical.display());
return Some(canonical);
}
// canonicalize failed (e.g. permissions) — skip rather than return unresolved symlink
log::warn!("Avatar: ~/.face exists but canonicalize failed, skipping");
}
// AccountsService icon
// AccountsService icon — also canonicalize for consistency
if let Some(name) = username {
if accountsservice_dir.exists() {
let icon = accountsservice_dir.join(name);
if icon.exists() {
log::debug!("Avatar: using AccountsService icon");
return Some(icon);
if let Ok(canonical) = std::fs::canonicalize(&icon) {
log::debug!("Avatar: using AccountsService icon ({})", canonical.display());
return Some(canonical);
}
log::warn!("Avatar: AccountsService icon exists but canonicalize failed, skipping");
}
}
}
@@ -100,7 +107,8 @@ mod tests {
let face = dir.path().join(".face");
fs::write(&face, "fake image").unwrap();
let path = get_avatar_path_with(dir.path(), None, Path::new("/nonexistent"));
assert_eq!(path, Some(face));
let expected = fs::canonicalize(&face).unwrap();
assert_eq!(path, Some(expected));
}
#[test]
@@ -111,7 +119,8 @@ mod tests {
let icon = icons_dir.join("testuser");
fs::write(&icon, "fake image").unwrap();
let path = get_avatar_path_with(dir.path(), Some("testuser"), &icons_dir);
assert_eq!(path, Some(icon));
let expected = fs::canonicalize(&icon).unwrap();
assert_eq!(path, Some(expected));
}
#[test]
@@ -124,7 +133,8 @@ mod tests {
let icon = icons_dir.join("testuser");
fs::write(&icon, "fake image").unwrap();
let path = get_avatar_path_with(dir.path(), Some("testuser"), &icons_dir);
assert_eq!(path, Some(face));
let expected = fs::canonicalize(&face).unwrap();
assert_eq!(path, Some(expected));
}
#[test]