Fix data loss: copying a file onto itself via two sessions truncated it to 0
Two panels on the same server hold two SSH sessions by design, and every self-copy guard compared sessionId — so same-server same-path copies slipped through all of them, and createWriteStream truncated the destination before the read began: the file (or every file in a folder) became 0 bytes, with the overwrite dialog making it look like a legitimate replace. Fix, defense in depth, keyed on FILESYSTEM identity instead of session: - fsKey on every provider: 'local', or resolvedIP:port for SFTP (host is DNS-resolved at connect so hostname-vs-IP aliases compare equal; username excluded — same host:port is the same filesystem regardless of login) - engine guard blocks equal/nested paths when fsKeys match; per-file belt-and-suspenders check right before streams open - probe layer for hidden aliases (different addresses, same machine): when fsKeys differ but paths collide, a token file written via dst and read via src decides; visible => same machine => blocked. SFTP exposes no dev/inode (what cp/rsync use), so probing is the only deterministic test; fails open and cleans up after itself - UI: same-folder toast now fires across two sessions to one server, and the overwrite dialog auto-skips (never offers Replace) when the colliding item IS the source file Known residual, documented in README 'Same-file protection': one filesystem mounted at different paths on two connections is undetectable client-side. Regression suite against the real box: file-onto-itself and folder-onto-itself blocked with contents byte-intact, folder-into-descendant blocked, legit same-server different-dir copies still work, local same-path still blocked, simulated alias caught by the probe with cleanup verified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -142,6 +142,31 @@ Saved connections live in a plain JSON file in the OS app-data directory
|
||||
|
||||
(On Macs, function keys may need `fn` — every action also has a toolbar button.)
|
||||
|
||||
## Same-file protection
|
||||
|
||||
Copying a file or folder onto itself would truncate it to zero bytes (the
|
||||
destination is opened for writing before the source is read). BunSCP blocks
|
||||
this at several layers:
|
||||
|
||||
- **Same session / same host**: every connection carries a filesystem identity
|
||||
(`local`, or `resolvedIP:port` — the hostname is DNS-resolved, so
|
||||
`myserver.lan` and its IP compare equal even across two panels/tabs holding
|
||||
separate SSH sessions to the same server). Equal identity + equal or nested
|
||||
paths ⇒ the transfer is refused before anything is opened.
|
||||
- **Hidden aliases** (two *different* addresses that reach the same machine —
|
||||
NAT, split DNS): when identities differ but source and destination paths
|
||||
collide anyway, a probe file is written through one connection and looked up
|
||||
through the other; if it's visible on both, they're the same machine and the
|
||||
copy is refused. (SFTP offers no device/inode identity — the mechanism
|
||||
`cp`/`rsync` use locally — so probing is the only deterministic test.)
|
||||
- The overwrite dialog never offers to "Replace" a file with itself — such
|
||||
items are auto-skipped with a notice.
|
||||
|
||||
**Known residual limitation**: the same filesystem mounted at *different
|
||||
paths* on two connections (bind mounts, NFS exported twice, shared container
|
||||
volumes) cannot be detected — the paths differ, so no guard or probe triggers.
|
||||
Be careful when two panels reach the same data through different mount points.
|
||||
|
||||
## Portability notes
|
||||
|
||||
The codebase is platform-neutral except for two seams, both macOS-only today:
|
||||
|
||||
Reference in New Issue
Block a user