--dry-run now handshakes with a remote/daemon receiver and reports what WOULD transfer/skip based on receiver state, mutating nothing on either side. - Serialize Config.dry_run into the wire config frame and append STATUS_DRY_RUN_TRANSFER to the status enum (no renumbering); bump PROTOCOL_VERSION/CMake VERSION/CHANGELOG/golden wire to 2.21.0. - Receiver: receive_incremental_check_ex runs the normal read-only decision and answers STATUS_OK (skip) or STATUS_DRY_RUN_TRANSFER (would transfer) with no basis materialization/append/delta/full transfer. All mutation sites are guarded by !dry_run: file store, manifest deletes, --mkpath root creation, --delay-updates staging, publication, directory-time application, and outcome acks. - Client: send_dry_run_remote connects, sends the config, checks each regular file and prints the would-transfer set + trailer; no file data or delete manifest is sent. Plain local destinations keep the client-side manifest.
6.5 KiB
6.5 KiB
Changelog
All notable changes to FastSync are documented here. Versions match
PROTOCOL_VERSION (printed by fastsync --version); the client and server must
run the same version because the handshake is strict.
[Unreleased]
Added
- Server-contacting
--dry-run(protocol 2.21.0).--dry-runnow performs a real handshake with a remote/daemon receiver and reports exactly what WOULD change based on receiver state (existing destination files, mtimes, checksums, basis dirs). The wire config carries the dry-run intent (Config.dry_run) and the receiver answers each per-file check withSTATUS_DRY_RUN_TRANSFER(would transfer) orSTATUS_OK(already up to date); the sender prints the would-transfer set and its trailer without sending any file data. The receiver performs the normal read-only incremental decision but mutates nothing: no temp files, writes, renames, deletes, metadata/xattr/chown, or directory creation. A plain local destination (no explicit--server-port/remote) keeps the original client-side dry-run. Would-delete reporting for--delete*is deferred to a follow-up; dry-run never deletes.
Security
- Enforce the daemon's per-module
max connectionscap and add a globalmax connections per hostcap plus a cross-processauth lockout(auth lockout threshold/auth lockout duration). Because the listener forks one child per connection, the counters live in an anonymous shared mapping created before the accept loop and reclaimed by the parent'sSIGCHLDhandler, so the per-module, per-source and auth-failure state is shared across every child (including afterSIGKILL). The per-source table now has a bounded lifetime (expired-lockout/idle entries are reclaimed, with a rate-limited warning when it is genuinely full), and the occupancy counters are re-derived from the shared slot table on every child exit so a child killed mid-registration cannot leak a count. Trusted loopback peers are exempt from the per-host cap and the auth lockout (they share one address); clients behind a shared NAT/proxy still share a single per-host budget and lockout, which is documented.
[2.20.0] - 2026-09-13
Security
- Cap cumulative
DirTimeListgrowth and bound pre-auth config-string memory (remote memory-exhaustion DoS). - Daemon host access control (
hosts allow/hosts deny, IPv4/IPv6/CIDR), configurable globalmax connections, connection audit logging, and a boundedauth failure delaythrottle. IPv4-mapped peers are normalized and invalid patterns are rejected at parse time (no silent fail-open). - Honor
--timeoutfor protocol I/O and bound idle/session time to defeat keepalive slowloris; child-safe signal handling in the forked daemon. - Compiler/linker hardening (
_FORTIFY_SOURCE, stack protector, PIE, RELRO) and pinned build dependencies.
Fixed
- Use-after-free in the basis-dir oversize preflight.
- Placeholder
Dataleaks,missing_argsleak, scanner chunk leak. - Thread-safe logging; single fd owner and cleanup epilogue in the server handler.
Performance
- Metadata now crosses the wire as one packed frame (protocol 2.20.0).
- Delete keep-set and
--files-fromlookups indexed (O(n*m) → O(n)). - Reused per-thread zstd contexts;
TCP_NODELAYby default. - Byte-bounded sender queues; removed a redundant scanner
stat().
[2.19.0] - 2026-09-12
Security
- Daemon authentication rewritten as SCRAM-SHA-256 challenge/response
(
STATUS_AUTH_CHALLENGE→STATUS_AUTH_RESPONSE→STATUS_AUTH_OK/STATUS_AUTH_FAILED), replacing the old replayable staticSHA-256(password)bearer credential. Each proof is bound to a fresh per-connection server nonce plus a client nonce, so a captured response can never be reused. - Salted verifier store.
--password-file/--early-inputnow holduser:$fastsync$1$pbkdf2-sha256$<iters>$<salt>$<stored_key>$<server_key>(PBKDF2-HMAC-SHA256, default 600000 iterations, range 100000–10000000). The legacyuser:SHA256HEXform is hard-rejected; there is no auto-upgrade. Generate stores offline withfastsync-server --hash-credentials FILE [--iterations N]. - Username-enumeration hardening. Unknown/off-list users are answered with a
dummy verifier whose salt is a deterministic per-username value
(
HMAC-SHA256(dummy_key, username)), using the store-wide uniform iteration count and a constant-time full-length membership scan. The dummy key is persisted in an owner-only<store>.dummykeysidecar (atomic publish, exact mode 0600) so challenges are stable across restarts. - Verified transport for auth-required modules. A module with
auth usersaccepts credentials only over verified TLS whose client certificate matches--client-cn, or — when--allow-unauthenticatedis explicitly set — plaintext from a loopback peer. Remote plaintext is refused before any challenge. Clients must use--tlsto send--password-filecredentials to a non-loopback daemon;--client-cnis mandatory with--tls. - Secret hygiene. The plaintext password, derived keys, nonces/proofs and the dummy key are wiped from memory on every path and never logged.
- Carried-over hardening:
-KTOCTOU-safe directory walk (openat(O_NOFOLLOW)per component), always shell-quoted SSH remote path, TLS compression/renegotiation disabled, race-free (open-then-fstat)--password-file/--early-inputchecks, log-injection escaping, and lazy protocol debug escaping.
Added
fastsync-server --hash-credentials FILE [--iterations N]offline tool.<store>.dummykeysidecar (auto-created, owner-only, 0600).- Integration tests for auth replay rejection, malformed frames, legacy-store refusal, and the loopback/TLS transport policy; fuzz targets for config receive and daemon-auth parsing.
Changed
- Protocol version 2.18.0 → 2.19.0 (breaking). The config-frame auth block
is now
[present][username](digest removed) and the auth challenge/response frames are interleaved between the config frame and itsSTATUS_OK. A 2.19.0 client and a 2.18.0 server (or vice versa) fail cleanly at the handshake. - Daemon modules declaring
auth usersrequire a configured credential store at startup (fail closed); operators regenerate stores from plaintext with--hash-credentials.
Notes
- First tagged release. FastSync implements rsync-compatible file synchronization over TCP and SSH with TLS (OpenSSL), streaming zstd compression, multithreaded transfers, and incremental sync. See RSYNC_COMPAT.md for the flag-parity matrix.