Commit Graph
875 Commits
Author SHA1 Message Date
TapTap dff6609976 feat(daemon): host ACL, configurable max connections, peer audit, auth-failure delay 2026-09-13 02:36:15 +02:00
TapTap 1acb66628d Merge Wave 2: thread-safety fixes (signals, fd ownership, handler epilogue, logging, scanner leak)
CI / lint (push) Successful in 1m30s
CI / sanitizers (undefined) (push) Successful in 59s
CI / sanitizers (address) (push) Successful in 1m6s
CI / fuzz-build (push) Successful in 28s
CI / coverage (push) Successful in 49s
CI / build-and-test (push) Successful in 4m31s
CI / valgrind (push) Successful in 3m10s
2026-09-13 02:13:27 +02:00
TapTap ba1c7a369f fix(server,log): non-socket shutdown fallback, drop redundant delay cleanup, unlock logging I/O 2026-09-13 02:13:22 +02:00
TapTap d28489d83c Merge branch 'fix/w2-scan' into fix/w2-integration 2026-09-13 01:49:44 +02:00
TapTap 312ed05170 Merge branch 'fix/w2-log' into fix/w2-integration 2026-09-13 01:49:44 +02:00
TapTap fecbe2c90c fix(server): child-safe signals, single fd owner, handler cleanup epilogue 2026-09-13 01:49:27 +02:00
TapTap c8f5d80fcb fix(log): serialize message emission; clear log_fp before close; use logger 2026-09-13 01:44:59 +02:00
TapTap 8147ff7b50 fix(scanner): free chunk_data on chunk-create failure 2026-09-13 01:36:51 +02:00
TapTap b7fbb56289 test(file): silence cppcheck constVariablePointer in empty-path test
CI / lint (push) Successful in 1m32s
CI / sanitizers (undefined) (push) Successful in 57s
CI / sanitizers (address) (push) Successful in 1m4s
CI / fuzz-build (push) Successful in 30s
CI / coverage (push) Successful in 49s
CI / build-and-test (push) Successful in 4m28s
CI / valgrind (push) Successful in 3m10s
2026-09-13 01:25:29 +02:00
TapTap 08063b6d73 Merge Wave 1: critical/High fixes (UAF, DoS caps, leaks, hardening)
CI / lint (push) Failing after 1m32s
CI / build-and-test (push) Skipped
CI / sanitizers (address) (push) Skipped
CI / sanitizers (undefined) (push) Skipped
CI / fuzz-build (push) Skipped
CI / coverage (push) Skipped
CI / valgrind (push) Skipped
2026-09-13 01:18:58 +02:00
TapTap ea2f76cd7a fix(receiver): charge per-entry DirTimeList cost; cap client --skip-compress 2026-09-13 01:18:54 +02:00
TapTap 76eeba1773 Merge branch 'fix/w1d-hardening' into fix/w1-integration 2026-09-13 00:59:35 +02:00
TapTap b72ab298ab Merge branch 'fix/w1c-wire' into fix/w1-integration 2026-09-13 00:59:35 +02:00
TapTap 446a714ef8 Merge branch 'fix/w1b-receiver' into fix/w1-integration 2026-09-13 00:59:35 +02:00
TapTap a90e234eb3 harden: overflow guards, auth-user validation, TLS1.3 policy, build hardening 2026-09-13 00:59:21 +02:00
TapTap f8252cf3e7 fix(protocol): bound pre-auth config string memory 2026-09-13 00:57:32 +02:00
TapTap 4557924972 fix(receiver): cap DirTimeList growth and fix placeholder Data leaks 2026-09-13 00:57:32 +02:00
TapTap 59ce174d22 fix(client-send): UAF in basis preflight and missing_args leak 2026-09-13 00:57:09 +02:00
TapTap 2a8941ee5c Merge feat/ref-integration: SuperMode enum, dir-time gate dedup, parse_args/server_module_gate splits
CI / lint (push) Successful in 1m30s
CI / sanitizers (undefined) (push) Successful in 59s
CI / sanitizers (address) (push) Successful in 1m6s
CI / fuzz-build (push) Successful in 29s
CI / coverage (push) Successful in 49s
CI / valgrind (push) Successful in 2m9s
CI / build-and-test (push) Successful in 4m31s
2026-09-12 21:11:03 +02:00
TapTap 37037a6ee7 refactor(client-cli): drop unused CliParseCtx positional fields (cppcheck) 2026-09-12 21:07:14 +02:00
TapTap 061e9ad43f Merge feat/ref-modulegate: split server_module_gate into helpers 2026-09-12 20:56:43 +02:00
TapTap f928879755 Merge feat/ref-parseargs: split parse_args into focused helpers 2026-09-12 20:56:43 +02:00
TapTap 84b7cb0de3 refactor(server): split server_module_gate into ordered helper stages 2026-09-12 20:56:33 +02:00
TapTap 921472b8b3 refactor(client-cli): split parse_args into focused option handlers
Break the ~700-line parse_args god function into cohesive static helpers
grouped by concern: output controls, pre-negation, range/time options, the
OPTION_TABLE dispatcher, flag/meta handlers, IO/network options, filter and
logging options, checksum/socket options, remote/basis/identity options,
positional handling, and a final lowering step.

A file-local CliParseCtx carries the config, cursor, positional buffers and
the mutable parse flags, so each handler stays focused. The dispatcher calls
the handlers in the original recognition order and preserves the exact
return contract (0/1/negative), error messages, log levels and control flow.

Behavior preserved; no functional changes.
2026-09-12 20:55:04 +02:00
TapTap 082ac2645d Merge feat/ref-dirtime: dir-time capture gate dedup 2026-09-12 20:43:42 +02:00
TapTap eefbd1e849 Merge feat/ref-supermode: SuperMode enum 2026-09-12 20:43:42 +02:00
TapTap 1fd462cca8 refactor(config): replace SUPER_MODE_* macros with SuperMode enum
Type Config.super_mode as SuperMode (a proper C enum) instead of a bare
int.  The wire boundary still carries the mode as an int: send casts the
enum explicitly and receive reads a temporary int, validates the
AUTO..OFF range, then casts.  Emitted bytes and accepted values are
unchanged.  ModuleGateContext.super_mode_override keeps its -1 sentinel
as int with an explicit cast at the apply site.

Behavior preserved.
2026-09-12 20:43:24 +02:00
TapTap 4ac37c4d8a refactor(dir-times): extract dir_times_should_capture predicate
Deduplicate the repeated directory-time capture gate
(`config->use_metadata && !config->omit_dir_times`) used by the
sender-side (multiprocessing.c) and receiver-side (receiver.c) sinks
into a single predicate declared next to the DirTimeList machinery in
file_receive.h and defined in file_receive.c.

Behavior preserved: identical short-circuit condition and semantics,
no signature or protocol changes.
2026-09-12 20:42:47 +02:00
TapTap 08af945bd6 Merge main back into dev after v2.19.0 release
CI / lint (push) Successful in 1m32s
CI / sanitizers (address) (push) Successful in 55s
CI / fuzz-build (push) Successful in 30s
CI / sanitizers (undefined) (push) Successful in 53s
CI / coverage (push) Successful in 47s
CI / build-and-test (push) Successful in 4m28s
CI / valgrind (push) Successful in 2m11s
2026-09-12 20:22:54 +02:00
TapTap 378d881ca7 Merge release/v2.19.0 into main: FastSync v2.19.0
CI / lint (push) Successful in 1m33s
CI / sanitizers (undefined) (push) Successful in 59s
CI / sanitizers (address) (push) Successful in 1m4s
CI / fuzz-build (push) Successful in 28s
CI / coverage (push) Successful in 49s
CI / valgrind (push) Successful in 2m10s
CI / build-and-test (push) Successful in 4m29s
v2.19.0
2026-09-12 20:22:50 +02:00
TapTap cc27ee1b83 Release v2.19.0
- Protocol version 2.19.0 (SCRAM-SHA-256 daemon auth replacing the replayable digest)
- Salted PBKDF2 verifier store + --hash-credentials; legacy store hard-rejected
- Persistent anti-enumeration dummy key (<store>.dummykey)
- Verified TLS / opted-in loopback transport required for auth modules
- Secret wiping; carried-over hardening from the security phases
- Add CHANGELOG.md and set the CMake project version
2026-09-12 20:22:46 +02:00
TapTap d653c3e151 Merge feat/tls-dummy-integration: verified/Local-only transport for daemon auth + persistent dummy key
CI / lint (push) Successful in 1m38s
CI / sanitizers (undefined) (push) Successful in 1m1s
CI / sanitizers (address) (push) Successful in 1m7s
CI / fuzz-build (push) Successful in 30s
CI / coverage (push) Successful in 49s
CI / valgrind (push) Successful in 2m14s
CI / build-and-test (push) Successful in 4m32s
2026-09-12 19:55:28 +02:00
TapTap 1b90ee2449 fix(review): close loopback TLS auth bypass; align docs and wrong-CN test
- server gate: the --allow-unauthenticated loopback allowance now requires
  an actual plaintext connection (!gate_ctx->ssl), so a loopback TLS client
  whose cert fails the --client-cn check is refused before any SCRAM
  challenge instead of falling through the plaintext opt-in.  Keep the
  invalid-fd guard as belt-and-braces (unreachable after the policy check).
- test: rewrote test_wrong_client_cn_refused_before_auth_challenge to run
  deterministically over 127.0.0.1 with --tls + --allow-unauthenticated and
  a CA-valid wrong-CN client cert, asserting the gate refusal log and an
  unchanged module tree (no skip).
- docs: --client-cn is mandatory with --tls; dummykey sidecar is secret
  material; document all transient-fallback reasons; qualify
  --allow-unauthenticated in README and --help so it cannot read as
  permitting remote plaintext auth.
- credentials.h: drop stale restrictive-umask claim (fchmod forces exact
  0600; only create/write/fsync/link/fchmod failure degrades to ephemeral).
2026-09-12 19:50:52 +02:00
TapTap 89b967f29a Merge feat/dummy-key: persist anti-enumeration dummy key across restarts
# Conflicts:
#	RSYNC_COMPAT.md
2026-09-12 19:30:35 +02:00
TapTap 8f06ae5262 Merge feat/tls-auth: require verified/local transport for daemon auth modules 2026-09-12 19:29:35 +02:00
TapTap ac3c4c7c72 fix(a7-auth): harden dummy-key temp creation
- Make the atomic-publish temp name unpredictable by appending 16 random
  hex chars to the pid, so a leftover/planted temp cannot be targeted.
- On EEXIST, unlink the stale temp and retry the O_EXCL create once
  (bounded), so a crash leftover or reused pid cannot silently defeat
  sidecar persistence.
- fchmod the temp fd to 0600 after creation (umask can clear owner bits)
  and treat failure as a create failure, so the published sidecar is
  always exactly 0600.
- Clarify comments: the sidecar requires exact 0600 while the store and
  password files only reject group/other bits.
- Add a unit test that a restrictive umask still yields an exact 0600
  sidecar; clean random-suffixed temps in tests.
2026-09-12 19:29:23 +02:00
TapTap d53614d06b fix(a7-3/s1): fail closed on non-loopback peers; require plaintext opt-in before challenge
utils_fd_peer_is_local now returns true only when getpeername SUCCEEDS and the
peer address classifies as loopback. A non-socket descriptor (pipe/socketpair)
or any getpeername error is NOT local, so the daemon auth gate fails closed
instead of treating an untestable --stdio pipe as trusted (daemon auth modules
are --daemon-only and the stdio path never loads a daemon config).

server_module_gate now requires --allow-unauthenticated for the loopback
plaintext auth path: a plaintext loopback connection without the operator
opt-in is refused at the config gate BEFORE server_auth_handshake, so no SCRAM
challenge is sent. Remote peers still require verified TLS regardless of the
flag; the handler keeps its defense-in-depth checks.

Docs state the exact policy (verified TLS with matching --client-cn, or
operator-opted-in loopback plaintext), drop the SSH/stdio auth-transport claim
(they are daemon-only), and add the loopback trust-boundary relay caveat and
the CN-only (no SAN) residual. Adds a unit-test negative for pipe/socketpair
and an integration test where a relay observes no challenge when the flag is
absent.
2026-09-12 19:18:29 +02:00
TapTap f0381a6b8e fix(a7-auth): publish dummy-key sidecar atomically and harden reads
Address review findings on the persistent dummy-key sidecar:

- Publish atomically: write a private same-directory temp file
  (<store>.dummykey.tmp.<pid>, 0600), fsync, then link(2) into place;
  fsync the containing directory and drop the temp name. A concurrent
  starter can no longer observe a zero/partial sidecar and fail closed.
  On EEXIST adopt the winner's sidecar; otherwise warn and use a
  transient ephemeral key.
- Harden the read path (initial and EEXIST-adopt) with
  O_RDONLY|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC: reject planted symlinks
  (ELOOP fails closed) and never block on a planted FIFO.
- Require the exact owner-only mode (st_mode & 07777) == 0600 and make
  the rejection message truthful.
- Report a clear "short write" instead of a stale strerror(errno) when
  write() returns 0.
- Document the artifact and its creation-failure caveat (FIFO store
  path, read-only filesystem, missing directory) in README.md and
  RSYNC_COMPAT.md.
- Tests: known-key sidecar adoption (dummy salt KAT + reload), symlink
  rejection, and the exact-0600 rule (0400 now rejected).
2026-09-12 19:16:11 +02:00
TapTap a7a1930e88 fix(a7-3/s1): require TLS or local transport for daemon auth
Daemon modules that declare 'auth users' no longer accept credentials over a
remote plaintext connection: server_module_gate refuses at the config gate,
before any SCRAM challenge is sent, unless the connection is verified TLS with
a client certificate matching --client-cn, or a local/SSH transport (loopback
TCP peer or the --stdio pipe). --allow-unauthenticated does not relax this.

The TLS client-CN comparison now uses credentials_secure_equal (S2). Clients
sending --password-file to a non-loopback daemon must use --tls; validate_config
rejects the plaintext case before any network I/O.

Adds utils_sockaddr_is_loopback / utils_fd_peer_is_local / utils_host_is_loopback
helpers with unit tests, a client validation unit test, and integration tests
for the client-side plaintext rejection and the wrong-CN gate refusal.
2026-09-12 19:02:05 +02:00
TapTap 42f01c0968 fix(a7-auth): persist dummy key in owner-only sidecar
The store-wide dummy key was regenerated on every credentials_load, so an
unknown user's dummy salt changed across daemon restarts while a real user's
stored salt stayed stable -- a restart-gated username-enumeration oracle.

Persist the 32-byte key in a 0600 <store>.dummykey sidecar next to the
credential store.  An absent sidecar is created with O_EXCL and fsynced; a
present sidecar is read only when it is an owner-only regular file of exactly
32 bytes (otherwise the load fails closed).  If the sidecar cannot be created
(read-only mount, missing directory) fall back to a transient per-run key with
a warning.  A NULL store path keeps the key ephemeral.
2026-09-12 18:40:21 +02:00
TapTap 2489d422e5 Merge feat/a7-integration: SCRAM-SHA-256 daemon auth + lazy protocol debug escaping
CI / lint (push) Successful in 1m33s
CI / sanitizers (undefined) (push) Successful in 59s
CI / sanitizers (address) (push) Successful in 1m4s
CI / fuzz-build (push) Successful in 29s
CI / coverage (push) Successful in 50s
CI / valgrind (push) Successful in 1m54s
CI / build-and-test (push) Successful in 4m45s
2026-09-12 18:21:13 +02:00
TapTap e2ddc0c07f Merge feat/hardening-misc: lazy protocol debug escaping, log_debug_enabled 2026-09-12 18:08:56 +02:00
TapTap 1480716304 Merge feat/a7-auth: SCRAM-SHA-256 daemon auth replacing replayable static digest 2026-09-12 18:08:56 +02:00
TapTap 1de1376e54 fix(a7-auth): final hardening pass on SCRAM auth
- burn the store-wide dummy_key in credentials_free()
- burn the local mac on hmac_sha256 failure in credentials_get_verifier()
- always run the O(store) constant-time scan, even for off-list users, to
  close the pre-existing off-list timing channel; select the real verifier
  only when on_list && match
- clarify the server_auth_handshake STATUS_AUTH_FAILED comment (failure
  before success vs. a dropped broken connection while writing the signature)
- document accepted anti-enumeration residuals (restart-gated dummy salt;
  pre-auth-observable iteration count)
2026-09-12 18:08:46 +02:00
TapTap eaf67f6257 fix(a7-auth): address SCRAM auth review findings A-G
- tests: pass CREDENTIAL_KEY_LEN to unhex for the 32-byte KAT proof/sig
  (sizeof(expect) is 348, over-reading the 65-byte hex literal under ASan)
- credentials: close the username-enumeration oracle with a store-wide
  dummy_key and a deterministic per-username dummy salt; make the store's
  iteration count uniform (reject intra-file and layered disagreements) and
  answer a miss with the store-wide count; run the constant-time key compare
  even when found=false and fold the decision with bitwise AND
- credentials_compute_keys: enforce [CREDENTIAL_MIN_ITERS, CREDENTIAL_MAX_ITERS]
- tests: recompute the whole KAT independently at CREDENTIAL_DEFAULT_ITERS
  (600000) and pin the golden store line; add non-uniform-store rejection,
  bound and deterministic-dummy-salt assertions
- server: send exactly one generic STATUS_AUTH_FAILED on every failure path
  (including credentials_get_verifier failure); route all handshake exits
  through one burn path
- credentials/server: burn the base64 decoders' scratch on error, the
  hash_store_line base64/line buffers on failure, and all handshake key/proof
  material
- fuzz: guard the auth-offset scan against size_t underflow and use a found flag
- docs: drop stale digest wording, use CREDENTIAL_MIN_ITERS as the --iterations
  bound, document 0600 output for --hash-credentials (plus a stderr warning on
  a group/other-accessible stdout file), and describe the deterministic dummy
  salt in the no-oracle claims
2026-09-12 17:56:52 +02:00
TapTap 8c94ec9886 feat(a7): SCRAM-SHA-256 daemon auth to replace replayable digest
Replace the challenge-less static-SHA-256 daemon bearer credential with a
SCRAM-SHA-256-style challenge/response and a salted PBKDF2 verifier store.
PROTOCOL_VERSION 2.18.0 -> 2.19.0; legacy user:SHA256HEX stores hard-reject.

- credentials: b64/rand/PBKDF2/HMAC primitives, verifier store parser,
  constant-time proof verify + ServerSignature, --hash-credentials helper
- config: auth block is now [present][username]; client runs the challenge
  exchange; config_burn_auth wipes plaintext/derived secrets (A7-4)
- server: gate drives the challenge, dummy verifier for unknown/off-list users
- tests: independent Python KAT, replay + legacy integration tests, fuzz paths
- docs: new store format, --hash-credentials, 2.19.0 bump

TLS verification behavior (A7-3/S1) is intentionally unchanged.
2026-09-12 17:19:33 +02:00
TapTap 87585e9881 perf(protocol): skip string debug escaping when proto debug is off
output_escape() was called on every send_str/receive_str even when
LOG_DEBUG_PROTO logging was disabled, allocating and scanning the whole
payload for a line that log_debug_message() then discarded.  Add a
log_debug_enabled(flag) gate mirroring log_debug_message()'s own filter and
check it before escaping.  Redacted (secret) strings still log the same
<redacted> marker; no observable log output changes.
2026-09-12 16:54:43 +02:00
TapTap 1ba6372017 Merge feat/p8-integration: P8 hardening batch (fs/transport, identity/server, CLI quality, fuzz)
CI / lint (push) Successful in 1m28s
CI / sanitizers (undefined) (push) Successful in 57s
CI / sanitizers (address) (push) Successful in 1m0s
CI / fuzz-build (push) Successful in 27s
CI / coverage (push) Successful in 47s
CI / valgrind (push) Successful in 40s
CI / build-and-test (push) Successful in 5m24s
2026-09-12 15:55:49 +02:00
TapTap 9f74b21c64 test(p8h): assert a --no-super daemon still refuses client --super 2026-09-12 15:55:44 +02:00
TapTap 108fee1e41 fix(p8h): restore daemon --super refusal, race-free secret-file check, remaining log escapes
- server_module_gate: refuse client-chosen ownership against the ORIGINAL config
  so an explicit --super is still refused under an operator --no-super veto
  (the veto must not turn a refusal into an accept).
- credentials: open-then-fstat the exact secret inode, require current-user
  ownership and no group/other bits, but continue to allow process-substitution
  FIFOs; removes the stat->fopen TOCTOU.
- file.c preallocate + protocol.c send-string debug logs escape attacker paths.
- usage/RSYNC_COMPAT updated for --old-args no-op and secret-file rules.
2026-09-12 15:50:14 +02:00