Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
29acea4
test(project): require no-clobber staged publication
seonghobae Aug 20, 2026
e673356
test(project): wire no-clobber publication regression
seonghobae Aug 20, 2026
f476181
fix(project): stage and publish new saves without clobber
seonghobae Aug 20, 2026
18d5812
test(project): require safe save publication wiring
seonghobae Aug 20, 2026
200eac3
fix(project): publish saves without clobbering existing files
seonghobae Aug 20, 2026
1ecb7c4
test(project): require bounded project reads
seonghobae Aug 20, 2026
f852054
fix(project): bound project reads at the file boundary
seonghobae Aug 20, 2026
eae423c
fix(project): route load through bounded reader
seonghobae Aug 20, 2026
f99d41f
docs(changelog): record bounded project persistence
seonghobae Aug 20, 2026
935bfa8
test(project): reject symlink project reads
seonghobae Aug 21, 2026
2076715
fix(project): reject directly selected symlink loads
seonghobae Aug 21, 2026
d38862c
docs(changelog): record symlink-safe project loading
seonghobae Aug 21, 2026
ad1d791
test(project): prove load TOCTOU path swap
seonghobae Aug 21, 2026
6cc163c
fix(project): bind load preflight to opened file
seonghobae Aug 21, 2026
aec9c4e
docs(changelog): record project load identity guard
seonghobae Aug 21, 2026
33da8db
test(project): reject symlinked save parent
seonghobae Aug 21, 2026
3bc0114
fix(project): reject symlinked save parent
seonghobae Aug 21, 2026
f08dd97
docs(project): record save-parent trust boundary
seonghobae Aug 21, 2026
39272d2
test(project): reject symlinked save ancestors
seonghobae Aug 21, 2026
2f6afc9
fix(project): validate save parent chain
seonghobae Aug 21, 2026
49c002c
test(project): keep portable parent-link boundary
seonghobae Aug 21, 2026
2e48e59
fix(project): preserve portable parent validation
seonghobae Aug 21, 2026
0c991df
docs(changelog): preserve protected release history
seonghobae Aug 26, 2026
5caf248
merge: integrate protected develop into project persistence lane
seonghobae Aug 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
### Fixed

- Upgraded the local score PDF parser to `pdfjs-dist` 6.2.108, pinned Undici 7.29.0 across the workspace, and constrained PDF loading to copied in-memory bytes with a same-origin bundled worker and npm-generated lock provenance.
- Stage and sync new project saves before non-clobbering publication, and enforce the existing 5 MiB project limit during the file read itself so a selected project cannot grow past a metadata preflight into an unbounded load allocation.
- Reject directly selected project symlinks before reading so a chosen `.bscope` path cannot silently redirect the loader to different file content.
- Reject a symlinked/reparse-point save parent before staging so a selected project path cannot redirect new project publication into a different directory.
- Fail closed when a selected `.bscope` path changes file identity between preflight and handle acquisition; Windows also opens reparse points without following them before validation.

## [0.1.3] - 2026-04-29

Expand Down
11 changes: 4 additions & 7 deletions apps/desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod project_persistence;

use bandscope_desktop_core::*;
use rfd::FileDialog;
use serde_json::{json, Value};
Expand Down Expand Up @@ -750,7 +752,7 @@ fn save_project(payload: Value) -> Result<(), String> {

let content = serde_json::to_string_pretty(&parsed)
.map_err(|_| "Failed to serialize project".to_string())?;
std::fs::write(path, content).map_err(|_| "Failed to write file".to_string())?;
project_persistence::publish_new_project_file(&path, content.as_bytes())?;

Ok(())
}
Expand All @@ -762,12 +764,7 @@ fn load_project() -> Result<RehearsalSongPayload, String> {
.pick_file()
.ok_or_else(|| "User cancelled".to_string())?;

let metadata = std::fs::metadata(&path).map_err(|_| "Failed to read file".to_string())?;
if metadata.len() > 5 * 1024 * 1024 {
return Err("Project file is too large (exceeds 5MB limit)".to_string());
}

let content = std::fs::read_to_string(path).map_err(|_| "Failed to read file".to_string())?;
let content = project_persistence::read_project_file(&path)?;
project_payload_from_content(&content)
}

Expand Down
Loading
Loading