harden: atomic writes, path validation, and save pipeline integrity

Backend (lib.rs):
- Add atomic_write/atomic_write_bytes helpers (write→fsync→rename→fsync parent)
- Apply safe_vault_path() to all 20 file-access commands (was 3)
- Apply safe_name() to workspace/canvas/attachment filename params
- Fix 2 silent error swallowing sites (let _ = fs::write)
- Fix git_status/git_commit/git_init error handling (check exit codes)
- Migrate all Regex::new() to LazyLock statics (10 total)
- Use ~tmp suffix for atomic writes (not extension replacement)
- Replace 2 unwrap() panic sites with unwrap_or_default()
- Skip ~tmp files in export_vault_zip

Frontend (Editor.tsx):
- Fix critical note-switch race: capture note path at call time,
  not when debounced timer fires (prevented old content → new note)
- Clear pending save timeout on note switch (defense-in-depth)
- Fix handleSlashSelect: route through debounced saveContent pipeline
  with domToMarkdown() instead of direct writeNote() with innerText
- Fix handlePaste stale closure (add saveContent to deps)

Changelog updated with Hardened section under v1.0.0.
This commit is contained in:
enzotar 2026-03-11 11:02:01 -07:00
parent d639d40612
commit bf4ef86874

View file

@ -10,6 +10,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
Graph Notes reaches 1.0 — a local-first, graph-based note-taking app built with Tauri, React, and Rust.
### Hardened
- **Atomic writes** — all file saves use write→fsync→rename→fsync-parent to prevent corruption on crash/power loss
- **Path validation**`safe_vault_path()` applied to all 20 file-access commands, preventing directory traversal
- **Filename sanitization**`safe_name()` rejects path separators in workspace, canvas, and attachment names
- **Note-switch save race** — debounced save now captures note path at call time, preventing old content from being written to wrong note
- **Save pipeline consistency** — slash command insert and image paste now route through the standard debounced save with `domToMarkdown()`
- **Git error handling**`git_status`, `git_commit`, `git_init` now check exit codes and surface stderr on failure
- **Silent error swallowing** — fixed 2 `let _ = fs::write()` sites to propagate errors
- **Panic prevention** — replaced `unwrap()` with `unwrap_or_default()` on fallible `file_name()` calls
- **Export safety**`export_vault_zip` skips in-progress `~tmp` atomic write files
- **Regex performance** — all 10 per-call `Regex::new()` migrated to `LazyLock` statics
### Fixed
- **Rust compilation** — resolved duplicate `dirs_config_path()` definition and removed reference to unlinked `dirs` crate
- **Content Security Policy** — replaced `null` CSP with a proper baseline policy allowing local resources and Google Fonts