Audited every copy/move/delete path (local, remote same-host, cross-host)
and fixed:
- Shell command injection in same-host remote cp/mv (unescaped paths
interpolated into exec strings); added remote.ShellQuote.
- Missing guard against copying/moving a directory into its own
subdirectory, locally and on same-host remote transfers (unbounded
recursion / disk fill). New regression tests in ops_test.go.
- Remote-to-remote same-path file copy silently truncating the source
file via Create() before it finished being read.
- Overwrite warning ("N existing target(s)") never shown for remote
destinations, only local ones.
- Remote same-host server-side cp never advancing the file-done counter
(waited on -v output that was never requested).
- Removed a dead "plan-then-confirm" subsystem (5 functions, 2 message
types, 2 unreachable Update() cases) superseded by the current design.
- Remote delete (SFTP) now runs as a cancellable job with the same
progress modal as local delete, instead of a single blocking call.
Also add a CI workflow that runs build/vet/test on every push and PR,
since previously they only ran on release tags.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Replace ssh.InsecureIgnoreHostKey() with TOFU verification against
~/.ssh/known_hosts (accept-new for unknown hosts, hard reject on a
changed key) to close a MITM hole on every SFTP connection.
- Actually connect to a running ssh-agent (SSH_AUTH_SOCK) for auth
instead of only scanning default key files on disk; give a clear
error when an explicit IdentityFile is passphrase-protected.
- Fix SSHClient.walk()/DirectorySize: the internal filepathSkipDir
sentinel leaked out as a real error on stat/ReadDir failures instead
of being swallowed, aborting size calculation on the first
unreadable subdirectory instead of skipping it.
- Skip symlink-to-directory entries in remote directory copies instead
of failing the whole transfer trying to Open() them as regular files.
- Consolidate 5 duplicated sudo-aware home-dir lookups into
internal/homedir, adding a missing os.Geteuid()==0 check before
trusting SUDO_USER.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Delete: skip remoteDeletePlanCmd and trashPlanCmd, show dialog immediately
- Copy: skip copyPlanCmd and remoteCopyPlanCmd, show dialog immediately
- CopyStats: no lstat per file, count files via WalkDir only
- Copy: two-phase (count first, then copy with known total + progress bar)
- Progress: file-based ratio, remove Size/Speed display
- Stage: Counting files... → Coping files... (no empty stage)