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

@@ -48,6 +48,20 @@ pub fn build_detail_page(record: &AppImageRecord, db: &Rc<Database>) -> adw::Nav
view_stack.add_titled(&storage_page, Some("storage"), "Storage");
view_stack.page(&storage_page).set_icon_name(Some("drive-harddisk-symbolic"));
// Restore last-used tab from GSettings
let settings = gio::Settings::new(crate::config::APP_ID);
let saved_tab = settings.string("detail-tab");
if view_stack.child_by_name(&saved_tab).is_some() {
view_stack.set_visible_child_name(&saved_tab);
}
// Persist tab choice on switch
view_stack.connect_visible_child_name_notify(move |stack| {
if let Some(name) = stack.visible_child_name() {
settings.set_string("detail-tab", &name).ok();
}
});
// Banner scrolls with content (not sticky) so tall banners don't eat space
let scroll_content = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
@@ -83,6 +97,7 @@ pub fn build_detail_page(record: &AppImageRecord, db: &Rc<Database>) -> adw::Nav
let record_id = record.id;
let path = record.path.clone();
let app_name_launch = record.app_name.clone().unwrap_or_else(|| record.filename.clone());
let launch_args_raw = record.launch_args.clone();
let db_launch = db.clone();
let toast_launch = toast_overlay.clone();
launch_button.connect_clicked(move |btn| {
@@ -92,6 +107,7 @@ pub fn build_detail_page(record: &AppImageRecord, db: &Rc<Database>) -> adw::Nav
let app_name = app_name_launch.clone();
let db_launch = db_launch.clone();
let toast_ref = toast_launch.clone();
let launch_args = launcher::parse_launch_args(launch_args_raw.as_deref());
glib::spawn_future_local(async move {
let path_bg = path.clone();
let result = gio::spawn_blocking(move || {
@@ -101,7 +117,7 @@ pub fn build_detail_page(record: &AppImageRecord, db: &Rc<Database>) -> adw::Nav
record_id,
appimage_path,
"gui_detail",
&[],
&launch_args,
&[],
)
}).await;
@@ -121,13 +137,16 @@ pub fn build_detail_page(record: &AppImageRecord, db: &Rc<Database>) -> adw::Nav
}).await;
if let Ok(Ok(analysis)) = analysis_result {
let status_str = analysis.as_status_str();
log::info!("Runtime Wayland: {} -> {}", path_clone, analysis.status_label());
log::info!(
"Runtime Wayland: {} -> {} (pid={}, env: {:?})",
path_clone, analysis.status_label(), analysis.pid, analysis.env_vars,
);
db_wayland.update_runtime_wayland_status(record_id, status_str).ok();
}
});
}
Ok(launcher::LaunchResult::Crashed { exit_code, stderr, .. }) => {
log::error!("App crashed on launch (exit {}): {}", exit_code.unwrap_or(-1), stderr);
Ok(launcher::LaunchResult::Crashed { exit_code, stderr, method }) => {
log::error!("App crashed on launch (exit {}, method: {}): {}", exit_code.unwrap_or(-1), method.as_str(), stderr);
widgets::show_crash_dialog(&btn_ref, &app_name, exit_code, &stderr);
}
Ok(launcher::LaunchResult::Failed(msg)) => {
@@ -247,9 +266,7 @@ fn build_banner(record: &AppImageRecord) -> gtk::Box {
.margin_top(4)
.build();
if record.integrated {
badge_box.append(&widgets::status_badge("Integrated", "success"));
}
badge_box.append(&widgets::integration_badge(record.integrated));
if let Some(ref ws) = record.wayland_status {
let status = WaylandStatus::from_str(ws);
@@ -1582,6 +1599,10 @@ fn build_backup_group(record_id: i64, toast_overlay: &adw::ToastOverlay) -> adw:
group.add(&empty_row);
} else {
for b in &backups {
log::debug!(
"Listing backup id={} for appimage_id={} at {}",
b.id, b.appimage_id, b.archive_path,
);
let expander = adw::ExpanderRow::builder()
.title(&b.created_at)
.subtitle(&format!(
@@ -1656,12 +1677,28 @@ fn build_backup_group(record_id: i64, toast_overlay: &adw::ToastOverlay) -> adw:
row_clone.set_sensitive(true);
match result {
Ok(Ok(res)) => {
let skip_note = if res.paths_skipped > 0 {
format!(" ({} skipped)", res.paths_skipped)
} else {
String::new()
};
row_clone.set_subtitle(&format!(
"Restored {} path{}",
"Restored {} path{}{}",
res.paths_restored,
if res.paths_restored == 1 { "" } else { "s" },
skip_note,
));
toast.add_toast(adw::Toast::new("Backup restored"));
let toast_msg = format!(
"Restored {} path{}{}",
res.paths_restored,
if res.paths_restored == 1 { "" } else { "s" },
skip_note,
);
toast.add_toast(adw::Toast::new(&toast_msg));
log::info!(
"Backup restored: app={}, paths_restored={}, paths_skipped={}",
res.manifest.app_name, res.paths_restored, res.paths_skipped,
);
}
_ => {
row_clone.set_subtitle("Restore failed");
@@ -1922,7 +1959,9 @@ fn show_screenshot_lightbox(
// --- Click outside image to close ---
// Picture's gesture claims clicks on the image, preventing close.
let pic_gesture = gtk::GestureClick::new();
pic_gesture.connect_released(|_, _, _, _| {});
pic_gesture.connect_released(|gesture, _, _, _| {
gesture.set_state(gtk::EventSequenceState::Claimed);
});
picture.add_controller(pic_gesture);
// Window gesture fires for clicks on the dark margin area.