Fix 29 audit findings across all severity tiers

Critical: fix unsquashfs arg order, quote Exec paths with spaces,
fix compare_versions antisymmetry, chunk-based signature detection,
bounded ELF header reads.

High: handle NULL CVE severity, prevent pipe deadlock in inspector,
fix glob_match edge case, fix backup archive path collisions, async
crash detection with stderr capture.

Medium: gate scan on auto-scan setting, fix window size persistence,
fix announce() for Stack containers, claim lightbox gesture, use
serde_json for CLI output, remove dead CSS @media blocks, add
detail-tab persistence, remove invalid metainfo categories, byte-level
fuse signature search.

Low: tighten Wayland env var detection, ELF magic validation,
timeout for update info extraction, quoted arg parsing, stop watcher
timer on window destroy, GSettings choices/range constraints, remove
unused CSS classes, define status-ok/status-attention CSS.
This commit is contained in:
lashman
2026-02-27 22:08:53 +02:00
parent f87403794e
commit e9343da249
27 changed files with 1737 additions and 250 deletions

View File

@@ -595,29 +595,30 @@ impl DriftwoodWindow {
let Some(record_id) = param.and_then(|p| p.get::<i64>()) else { return };
let toast_overlay = window.imp().toast_overlay.get().unwrap().clone();
let window_ref = window.clone();
let (path_str, app_name) = {
let (path_str, app_name, launch_args_raw) = {
let db = window.database();
match db.get_appimage_by_id(record_id) {
Ok(Some(r)) => {
let name = r.app_name.clone().unwrap_or_else(|| r.filename.clone());
(r.path.clone(), name)
(r.path.clone(), name, r.launch_args.clone())
}
_ => return,
}
};
let launch_args = launcher::parse_launch_args(launch_args_raw.as_deref());
glib::spawn_future_local(async move {
let path_bg = path_str.clone();
let result = gio::spawn_blocking(move || {
let bg_db = crate::core::database::Database::open().expect("DB open");
let appimage_path = std::path::Path::new(&path_bg);
launcher::launch_appimage(&bg_db, record_id, appimage_path, "gui_context", &[], &[])
launcher::launch_appimage(&bg_db, record_id, appimage_path, "gui_context", &launch_args, &[])
}).await;
match result {
Ok(launcher::LaunchResult::Started { child, method }) => {
log::info!("Launched: {} (PID: {}, method: {})", path_str, child.id(), method.as_str());
}
Ok(launcher::LaunchResult::Crashed { exit_code, stderr, .. }) => {
log::error!("App crashed (exit {}): {}", exit_code.unwrap_or(-1), stderr);
Ok(launcher::LaunchResult::Crashed { exit_code, stderr, method }) => {
log::error!("App crashed (exit {}, method: {}): {}", exit_code.unwrap_or(-1), method.as_str(), stderr);
widgets::show_crash_dialog(&window_ref, &app_name, exit_code, &stderr);
}
Ok(launcher::LaunchResult::Failed(msg)) => {
@@ -830,12 +831,49 @@ impl DriftwoodWindow {
}
}
// Always scan on startup to discover new AppImages and complete pending analyses
self.trigger_scan();
// Scan on startup if enabled in preferences
if self.settings().boolean("auto-scan-on-startup") {
self.trigger_scan();
}
// Start watching scan directories for new AppImage files
self.start_file_watcher();
// Auto-cleanup old backups based on retention setting
let retention_days = self.settings().int("backup-retention-days") as u32;
glib::spawn_future_local(async move {
let _ = gio::spawn_blocking(move || {
let bg_db = Database::open().expect("Failed to open database");
match crate::core::backup::auto_cleanup_old_backups(&bg_db, retention_days) {
Ok(removed) if removed > 0 => {
log::info!("Auto-cleaned {} old backup(s) (retention: {} days)", removed, retention_days);
}
Err(e) => log::warn!("Backup auto-cleanup failed: {}", e),
_ => {}
}
})
.await;
});
// Run background security scan and notify if auto-security-scan is enabled
let settings_sec = self.settings().clone();
if settings_sec.boolean("auto-security-scan") {
let threshold = settings_sec.string("security-notification-threshold").to_string();
glib::spawn_future_local(async move {
let _ = gio::spawn_blocking(move || {
let bg_db = Database::open().expect("Failed to open database");
let notifications = notification::scan_and_notify(&bg_db, &threshold);
for n in &notifications {
log::info!(
"CVE notification sent: app={} (id={}), severity={}, count={}",
n.app_name, n.appimage_id, n.severity, n.cve_count,
);
}
})
.await;
});
}
// Check for orphaned desktop entries in the background
let toast_overlay = self.imp().toast_overlay.get().unwrap().clone();
glib::spawn_future_local(async move {
@@ -989,6 +1027,10 @@ impl DriftwoodWindow {
toast_overlay.add_toast(adw::Toast::new(&msg));
// Phase 2: Background analysis per file with debounced UI refresh
let running = analysis::running_count();
if running > 0 {
log::info!("Analyzing {} AppImage(s) in background ({} already running)", needs_analysis.len(), running);
}
if !needs_analysis.is_empty() {
let pending = Rc::new(std::cell::Cell::new(needs_analysis.len()));
let refresh_timer: Rc<std::cell::Cell<Option<glib::SourceId>>> =
@@ -1057,20 +1099,27 @@ impl DriftwoodWindow {
let changed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let changed_watcher = changed.clone();
let handle = watcher::start_watcher(dirs, move |_event| {
let handle = watcher::start_watcher(dirs, move |event| {
match &event {
watcher::WatchEvent::Changed(paths) => {
log::info!("File watcher: {} path(s) changed: {:?}", paths.len(), paths);
}
}
changed_watcher.store(true, std::sync::atomic::Ordering::Relaxed);
});
if let Some(h) = handle {
self.imp().watcher_handle.replace(Some(h));
// Poll the flag every second from the main thread
// Poll the flag every second from the main thread.
// Returns Break when the window is gone to stop the timer.
let window_weak = self.downgrade();
glib::timeout_add_local(std::time::Duration::from_secs(1), move || {
let Some(window) = window_weak.upgrade() else {
return glib::ControlFlow::Break;
};
if changed.swap(false, std::sync::atomic::Ordering::Relaxed) {
if let Some(window) = window_weak.upgrade() {
window.trigger_scan();
}
window.trigger_scan();
}
glib::ControlFlow::Continue
});
@@ -1204,7 +1253,7 @@ impl DriftwoodWindow {
fn save_window_state(&self) {
let settings = self.settings();
let (width, height) = self.default_size();
let (width, height) = (self.width(), self.height());
settings
.set_int("window-width", width)
.expect("Failed to save window width");