πŸ”’ Production Audit: Fix 13 vulnerabilities, bugs & stability issues

#1
by asdf98 - opened

MuseAlpha Production Audit β€” Complete Fix Set

Summary

Full security audit and stability fix for production release. Fixes 13 issues across 11 files β€” 5 critical security vulnerabilities, 4 stability bugs, and 4 quality/correctness issues.


πŸ”΄ CRITICAL Security Vulnerabilities Fixed

1. Insecure Password Generation (credentials.rs)

Before: Used timestamp_subsec_nanos() as seed β€” completely predictable. An attacker knowing the approximate time can reproduce any generated password.
After: Uses rand::rngs::OsRng (CSPRNG) with Fisher-Yates shuffle and guaranteed character class coverage.

2. Hardcoded KDF Salt (lib.rs)

Before: All installations shared the same static salt b\"muse-vault-kdf-salt-2026-v1-prod\". If one vault is compromised, rainbow tables work against ALL users.
After: Salt is derived per-machine using SHA-256(static_salt || hostname). Each installation has a unique salt.

3. Content Security Policy Disabled (tauri.conf.json)

Before: \"csp\": null β€” no CSP at all. XSS in any loaded page could execute arbitrary code with full Tauri IPC access.
After: Restrictive CSP allowing only necessary sources.

4. Arbitrary File Path Write (settings.rs)

Before: board_export_file accepted any filepath string with no validation β€” could write to /etc/passwd, C:\\Windows\\System32, etc.
After: Path traversal detection, system directory blocklist, parent existence check, and 100MB import size limit.

5. Wildcard CORS on Internal Protocol (lib.rs)

Before: Access-Control-Allow-Origin: * on muse-action:// handler responses. Combined with the remote.urls: [\"https://*\"] capability, any website could potentially probe internal state.
After: No CORS headers (internal protocol doesn't need them). Remote URL wildcard removed from capabilities.


🟑 Stability Bugs Fixed

6. Mutex Panics Crash the App (browser/commands.rs)

Before: Multiple .expect(\"tabs lock\") calls β€” if the mutex is ever poisoned (e.g., by a panic in another thread), the entire app crashes.
After: All mutex access uses .map_err() for graceful error propagation.

7. Unbounded Memory Growth (browser/commands.rs)

Before: SEEN_CAPTURE_IDS HashSet grew without limit. On a long session with hundreds of captures, this leaked memory indefinitely. The "cleanup" code removed a random element which doesn't bound growth.
After: Replaced with a bounded VecDeque<String> (FIFO, capped at 1024).

8. Duplicate Event Listener Race Condition (store.tsx + App.tsx)

Before: board://image_added was listened to in BOTH store.tsx AND App.tsx. This caused images to be inserted twice into the canvas array, with different IDs, creating ghost duplicates.
After: Single listener in store.tsx only. App.tsx comment documents the decision.

9. Undo History Memory Leak (store.tsx)

Before: History used JSON.stringify() comparison on every state change (expensive GC pressure on large boards) and the cap at 200 entries had a logic error that allowed unbounded growth.
After: Uses refs for O(1) access, proper bounded array with shift(), and a dirty flag instead of JSON comparison.


🟒 Quality & Correctness Fixes

10. Session Data Loss (sessions.rs)

Before: sessions_save called .retain(|s| !s.is_auto) which deleted ALL auto-saves whenever a manual save happened. Users lost their crash-recovery snapshots.
After: Separate limits for manual (50) and auto (5) sessions.

11. Vault Auto-Lock Missing (credentialsVault.ts)

Before: Once unlocked, the vault stayed open forever until the app was closed. If the user walks away, credentials are exposed.
After: 15-minute auto-lock timeout with user notification event.

12. Vault Detector Breaks Web Pages (vault_detector.js)

Before: Monkey-patched window.fetch and XMLHttpRequest.prototype.send globally. This broke pages that check function identity, interfered with service workers, and leaked credential-entry timing.
After: Removed fetch/XHR interception. Uses only form-submit and button-click detection.

13. Product Naming Inconsistency

Before: Mixed "Refstudio" and "MuseAlpha" throughout β€” confusing for users and breaks branding. Window title said "Refstudio", panic message said "Refstudio", etc.
After: Consistently "MuseAlpha" everywhere.


New Dependencies Added

  • rand = \"0.8\" β€” cryptographic random number generation
  • gethostname = \"0.5\" β€” per-machine salt derivation

Files Changed (11)

  • src-tauri/Cargo.toml
  • src-tauri/tauri.conf.json
  • src-tauri/capabilities/default.json
  • src-tauri/src/lib.rs
  • src-tauri/src/credentials.rs
  • src-tauri/src/settings.rs
  • src-tauri/src/library.rs
  • src-tauri/src/sessions.rs
  • src-tauri/src/browser/commands.rs
  • src-tauri/resources/scripts/vault_detector.js
  • src/store.tsx
  • src/App.tsx
  • src/credentialsVault.ts
asdf98 changed pull request status to open

Feature/UX Completion Pass Added

I continued the production audit beyond security/stability and implemented the missing/polish features that made MuseAlpha feel partially complete.

βœ… Annotation UI is now production-grade

Added src/components/AnnotationWidget.tsx and wired it into App.tsx.

Features:

  • Visible annotation HUD at bottom of canvas
  • Pen / highlighter / eraser modes
  • Color presets
  • Brush-size slider + quick sizes
  • Undo last stroke
  • Clear all strokes
  • Stroke count
  • Keyboard hint copy
  • Esc exits annotation mode before clearing panels

This fixes the previous issue where annotation mode existed but felt invisible/broken.

βœ… Global toast system now works

Many parts of the app dispatched muse:toast, but nothing listened to it globally. Now App.tsx listens to:

  • muse:toast
  • muse:error

So save/export/import/library/context actions now actually show feedback.

βœ… Toolbar .refs import fixed

Toolbar accepted .refs, but parsed every file as JSON. Now:

  • .refs uses Rust refs_import
  • .json uses JSON parsing
  • Empty files are rejected
  • Invalid files show clear toast errors
  • Loaded state updates board consistently

βœ… Context menu polished

Implemented:

  • Clamp menu to viewport so it never opens off-screen
  • Duplicate image action
  • Reset crop action
  • Copy source URL
  • Open real sourceUrl instead of accidentally opening data URLs
  • Browser initializes before source navigation
  • Toast feedback for key actions

βœ… Text notes hardened + polished

Text notes render imported rich HTML, so I added lightweight sanitization:

  • Blocks scripts/event handlers/styles from imported board files
  • Keeps safe rich text: bold/italic/underline/lists/links/spans/divs/br
  • Sanitizes links to http/https/mailto only

βœ… Branding/product consistency

  • package.json renamed refstudio β†’ musealpha
  • Settings About now says MuseAlpha
  • Settings auto-save copy now says 1.5s debounce, matching the hardened store

βœ… Capture dedupe edge case fixed

Improved board://image_added dedupe so multiple different images from the same source page are allowed, while exact duplicates are blocked by:

  • captureId
  • embedded URL
  • original URL

This avoids both ghost duplicates and over-aggressive source-page blocking.

Files added/changed in this pass:

  • src/components/AnnotationWidget.tsx (new)
  • src/App.tsx
  • src/components/Toolbar.tsx
  • src/components/ContextMenu.tsx
  • src/components/TextNoteNode.tsx
  • src/components/SettingsPanel.tsx
  • src/store.tsx
  • src/types.ts
  • package.json

This pass specifically targets the β€œfeatures feel half-implemented” problem and makes the artist workflow more discoverable, responsive, and sellable.

Final Edge-Case UX Pass Added

Continued the feature-completion pass and fixed remaining high-impact rough edges:

StarterHub

  • Open File now supports .refs archives, not only .json
  • Uses Rust refs_import for portable board imports
  • Adds import error toasts instead of silent failure
  • Completed MuseAlpha branding in top bar
  • Delete project now asks for confirmation
  • New/open/delete project actions now show user feedback

Canvas

  • Annotation strokes are now point-throttled by distance to prevent huge arrays during long drawing sessions
  • Added pointercancel / pointerleave handling so drawing or panning cannot get stuck if the pointer leaves the window
  • Drag/drop imports now show success/error toast feedback
  • Drag overlay flicker reduced by checking relatedTarget
  • Spacebar pan blur listener is now correctly cleaned up on unmount
  • Palette color copy now gives feedback

Product quality impact

This pass targets small-but-important β€œthis feels unfinished” issues:

  • advertised import modes now actually work everywhere
  • drawing feels smoother and safer for long sessions
  • drag/drop failure states are visible to the user
  • app lifecycle cleanup is safer for a long-running desktop app

Note: I attempted to run a build/check job, but the job execution was cancelled externally, so final validation is static-code-review based for this pass.

Ready to merge
This branch is ready to get merged automatically.

Sign up or log in to comment