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
+15
View File
@@ -24,6 +24,14 @@ jobs:
id: release
env:
TOKEN: ${{ secrets.RELEASE_TOKEN }}
# NOTE (partial-failure window): this renames the `latest` release's
# name/body to the new version up front, before the platform builds run.
# If a build later fails, the release keeps the OLD binary assets but the
# NEW name. That's cosmetic: the auto-updater reads release.json, which is
# only regenerated by the update-manifest job — and that job `needs:` BOTH
# build-windows and build-linux, so a failed/skipped build prevents any
# manifest (and therefore updater) change. Clients keep the last good
# release.json until a fully successful run replaces it.
run: |
VERSION="4.12.${{ github.run_number }}"
EXISTING=$(curl -sf "$GITEA_URL/api/v1/repos/$REPO/releases/tags/latest" \
@@ -277,6 +285,13 @@ jobs:
WIN_SIG=$(curl -sf "$BASE/LotusChat-x86_64-setup.nsis.zip.sig")
LIN_SIG=$(curl -sf "$BASE/LotusChat-x86_64.AppImage.tar.gz.sig")
# Never publish a manifest with a missing/empty signature: the updater
# would reject (or worse, accept an unsigned) artifact. Fail the job so
# the previous good release.json stays in place.
[ -n "$WIN_SIG" ] || { echo "ERROR: empty Windows signature" >&2; exit 1; }
[ -n "$LIN_SIG" ] || { echo "ERROR: empty Linux signature" >&2; exit 1; }
DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
python3 -c "import json,sys; v,d,wu,ws,lu,ls=sys.argv[1:]; print(json.dumps({'version':v,'notes':'Latest Lotus Chat release','pub_date':d,'platforms':{'windows-x86_64':{'url':wu,'signature':ws},'linux-x86_64':{'url':lu,'signature':ls}}},indent=2))" \