π Production Audit: Fix 13 vulnerabilities, bugs & stability issues
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 generationgethostname = \"0.5\"β per-machine salt derivation
Files Changed (11)
src-tauri/Cargo.tomlsrc-tauri/tauri.conf.jsonsrc-tauri/capabilities/default.jsonsrc-tauri/src/lib.rssrc-tauri/src/credentials.rssrc-tauri/src/settings.rssrc-tauri/src/library.rssrc-tauri/src/sessions.rssrc-tauri/src/browser/commands.rssrc-tauri/resources/scripts/vault_detector.jssrc/store.tsxsrc/App.tsxsrc/credentialsVault.ts
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:toastmuse: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:
.refsuses Rustrefs_import.jsonuses 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
sourceUrlinstead 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.jsonrenamedrefstudioβ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.tsxsrc/components/Toolbar.tsxsrc/components/ContextMenu.tsxsrc/components/TextNoteNode.tsxsrc/components/SettingsPanel.tsxsrc/store.tsxsrc/types.tspackage.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
.refsarchives, not only.json - Uses Rust
refs_importfor 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/pointerleavehandling 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.