From bf4ef86874a6603e87e69b7e76d6e3e219e0814a Mon Sep 17 00:00:00 2001 From: enzotar Date: Wed, 11 Mar 2026 11:02:01 -0700 Subject: [PATCH] harden: atomic writes, path validation, and save pipeline integrity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c3eff6..649f039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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