fix(security+audit): strip latent RCE grants, opener allowlist, GDI leaks, CI hardening

From the deep-audit wave (reviewer-verified: capability identifiers valid, no
removed-crate references, GDI free ordering correct):

- Removed 8 never-registered plugins (clipboard-manager, fs, shell, http,
  process, os, dialog, global-shortcut) from Cargo.toml AND their capability
  grants (shell:allow-execute, unscoped fs writes, http:default, …) — verified
  the web never invokes any of them. A latent RCE-class surface is gone.
- on_new_window: only http/https/mailto reach the OS opener (file:///custom
  schemes previously bypassed the opener capability scope entirely).
- set_badge_count: freed hdc + hdc_screen on all three GDI error paths
  (leaked per badge update in a long-running tray app).
- 8s reveal failsafe gated by an AtomicBool: no longer re-shows a window the
  user closed to tray; page-load reveal now fires once only (logout reloads
  don't re-surface a tray-hidden window); recovery for a missed page-load
  event preserved.
- toast.rs: store pruned on Activated too + capped at 20 (was unbounded).
- Startup no longer panics when the bundled icon is missing (tray skipped
  gracefully); msSmartScreenProtection no longer disabled (throttling
  disables kept); rust-version corrected to 1.77.2.
- release.yml update-manifest: fails on empty signatures (was: could publish
  a manifest that traps Windows users in a failed-update loop); partial-
  failure window documented. Deleted the stale upstream tauri.yml workflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 00:21:55 -04:00
parent b9cfe3356a
commit f883781c1f
7 changed files with 120 additions and 260 deletions
+17 -2
View File
@@ -158,7 +158,15 @@ fn show_windows_toast(
let room_id_owned = room_id.map(|s| s.to_string());
let path_owned = path.map(|s| s.to_string());
let activated = TypedEventHandler::<ToastNotification, IInspectable>::new(
move |_sender, args| {
move |sender, args| {
// Activation means this toast is done; drop it from the keep-alive
// store now. A Dismissed event doesn't reliably fire for a toast the
// user activated, so pruning only on Dismissed would leak it.
if let Some(sender) = sender.as_ref() {
if let Ok(mut store) = toast_store().lock() {
store.retain(|t| t != sender);
}
}
let Some(args) = args.as_ref() else {
return Ok(());
};
@@ -204,9 +212,16 @@ fn show_windows_toast(
);
let _ = toast.Dismissed(&dismissed)?;
// Keep the toast (and its handlers) alive until dismissed.
// Keep the toast (and its handlers) alive until dismissed/activated.
if let Ok(mut store) = toast_store().lock() {
store.push(toast.clone());
// Hard cap: if some Dismissed/Activated events are missed, retain only
// the most recent 20 toasts (dropping the oldest) so the store can't
// grow unbounded for the app's lifetime.
let len = store.len();
if len > 20 {
store.drain(0..len - 20);
}
}
// No AUMID argument: relies on the process AppUserModelID (see module note).