Summary
The import path promises never to overwrite an existing file, but destination selection and copying are separate operations. Another process can create the selected destination between exists() and std::fs::copy, allowing the copy to overwrite it.
Evidence
src-tauri/src/workspace/import.rs:24-38 selects a name by polling Path::exists.
src-tauri/src/workspace/import.rs:18 subsequently calls std::fs::copy.
- The workspace is intentionally shared with Finder and other applications, so concurrent creation is expected.
Proposed change
- Reserve the destination atomically with
OpenOptions::create_new(true).
- Copy through the reserved handle rather than reopening the path.
- On collision, generate the next suffix and retry.
- Remove a partially copied destination on failure.
- Preserve source metadata only if the product specification requires it.
Acceptance criteria
- Imports never truncate or overwrite an existing destination.
- Concurrent imports of the same filename both complete with distinct names.
- A failed copy leaves no partial destination.
- Tests cover existing collisions, a simulated concurrent collision, and copy failure cleanup.
Summary
The import path promises never to overwrite an existing file, but destination selection and copying are separate operations. Another process can create the selected destination between
exists()andstd::fs::copy, allowing the copy to overwrite it.Evidence
src-tauri/src/workspace/import.rs:24-38selects a name by pollingPath::exists.src-tauri/src/workspace/import.rs:18subsequently callsstd::fs::copy.Proposed change
OpenOptions::create_new(true).Acceptance criteria