Security

sync-servers moves your files with your credentials. This page says what it protects against, how, and what stays your job.

Principles

Protocols

What each protocol protects
ProtocolProtection
SFTPEncrypted and authenticated by SSH. Only modern key exchange, host key, cipher and MAC algorithms are offered. A server that needs older ones must set legacyAlgorithms, and every connection to it warns.
FTPSTLS 1.2 or later, with the certificate chain and host name verified against the system's certificate authorities, plus a private one given with --ca-file. A certificate that fails is shown to you to decide on (see Certificates).
FTPNone: the password and every file travel in clear text. Refused unless the server sets allowInsecure, and every connection warns.
Local folderWhatever the drive or network share provides.

Host keys

An SSH host key proves you reached the right server. The first connection to a server shows its fingerprint, such as ssh-ed25519 SHA256:..., and asks whether to trust it. Compare it with the fingerprint your host publishes before you answer.

Certificates

An FTPS certificate proves you reached the right server. It is checked on every connection, before your user name or password is sent: its chain must lead to a certificate authority this computer trusts, or to one given with --ca-file, and it must name the host you connect to. When both hold, nothing is asked.

When the check fails, a run in a terminal shows the certificate and asks what to do, instead of only refusing it:

The certificate of 203.0.113.10:21 does not name 203.0.113.10.
  Issued to  *.hosting.example.net, hosting.example.net
  Issued by  Example Trust CA
  Valid      2026-03-01 to 2027-03-31
  SHA-256    3A:91:5C:...:C4
203.0.113.10 is server1.hosting.example.net, which this certificate covers.
  1. Connect to server1.hosting.example.net instead (recommended)
  2. Keep 203.0.113.10 and check the certificate as server1.hosting.example.net
  3. Trust this certificate for 203.0.113.10
  4. Cancel

Credentials

Safe deletes and overwrites

A server you do not fully trust

A compromised or malicious server can return any names it likes. Before a name becomes a local path it is checked:

What stays on your computer

What was checked

Each threat below has a defence in the code and a test that proves it, run on every change in CI on Linux, macOS and Windows. The list was last walked row by row for the 1.0.0-alpha.0.6.0 release, together with two independent reviews of the whole source tree, one of the transports and the engine and one of secrets, the command line, settings and workflows. Every finding was fixed in that release.

Threats, defences and the tests that prove them
ThreatDefenceProved by
A password read off the networkSFTP by default; FTPS with verified certificates and TLS 1.2 or later; plain FTP refused without allowInsecure and warned on each connectionA self-signed certificate is refused before the password is sent; plain FTP is refused before any connection
A machine in the middle on SSHHost keys confirmed on first use and pinned; a changed key is fatal; unattended runs refuse unknown keysA changed key is refused with both fingerprints before any password is sent; an unattended run refuses an unknown key unless the expected fingerprint matches
Secrets at restThe operating system's credential store; the environment for CI; a question otherwise; no file fallbackAfter every scenario, every settings, state, log and local file is searched for the server password
Secrets in outputA redacting logger, scrubbed errors, and --json built from chosen fieldsStandard output and standard error of sync, status, diff and server test with --debug are searched for the password
A saved password sent to the wrong serverSaved passwords are tied to the protocol, address, port and user they were saved forA saved password is refused after a change of host, port, user, or a switch to plain FTP
Hostile names from a serverEvery listed name is validated before it becomes a pathA sandbox server lists ../escape, CON.txt, a\b.txt, case twins and a NUL; none is written
Symbolic link escapesLocal links are followed only as the site's sync.symlinks allows: none by default, only inside the site's folder with follow-inside-root, and with follow only where your own links lead, each such row marked; a download never creates a link; links on the server are never followed; writes refuse folders reached by another name; cloaking and the guard see through linksUnder each policy, links out of the site, broken links and links back into a folder above them are followed or become problem rows as the policy says; a Get through a link keeps the link; a link to .env is held back; short names are refused as write targets; files under a linked folder on the server become problem rows
Secrets uploaded by accidentThe sensitive-file guard lists matches uncheckedGuard tests in every direction, over every protocol
Mass deletion from a wrong rootThe delete option, unchecked never-synchronized deletes, the typed count, the empty-root and top-of-server refusals, and the recovery folderAn emptied local root against a full server stops before anything moves
Half-written filesPartial copies renamed into place only when complete; sizes checked; each file checked again just before it movesA download that keeps dropping and an upload that keeps stalling both leave the old copy as it was, with no partial file
Tampered plansEditor mode and saved plans can only choose among the planned rows; a saved plan must match a fresh scanEdited files and saved plans that add a path, renumber a row or choose an impossible action are refused whole
Corrupt settings or racing writersSchema validation, atomic writes with a backup, and a lock fileTwo registries writing at once lose nothing; an invalid change leaves the file as it was
Exhausted memory, a full disk or a hung runStreams only, bounded concurrency, listing, scan and depth limits, downloads capped at their listed size, and timeoutsAn oversized listing and an endless folder tree are refused; a download that sends too much fails; stalled downloads and uploads time out
Escape sequences from a serverControl and bidirectional characters in names, server replies, logs and diffs are escaped or replaced before they reach the terminalPreviews, error messages and debug lines with escape sequences in them are checked; no source file may hold such characters either
A compromised dependency or workflowFew dependencies, a lock file, no install scripts, pinned actions, least-privilege workflows, CodeQL, dependency review and ScorecardA test reads every workflow and fails on an unpinned action, a missing permissions: {}, a kept checkout credential or an expression inside a script
Personal data in public filesIgnore rules, the package allowlist, and a leak scanThe package-contents check and the documentation leak scan

Verifying a release

Every release is built from its tag by a GitHub Actions workflow that runs the full test suite first. The job that builds and tests holds no signing or publishing rights; a separate job that runs no code from the repository attests the files. The release carries the package tarball, a CycloneDX software bill of materials, a Sigstore attestation bundle and a SHA256SUMS file. To check what you downloaded:

sha256sum --check --ignore-missing SHA256SUMS
gh attestation verify isocialpractice-cmd-sync-servers-<version>.tgz --repo isocialPractice/cmd-sync-servers \
  --signer-workflow isocialPractice/cmd-sync-servers/.github/workflows/release.yml

Packages on npm are published from that same workflow with npm trusted publishing, so each carries provenance that links it to the commit and the workflow run that built it. npm audit signatures checks it after installing.

What stays your job

Reporting a vulnerability

Report it privately through GitHub's private vulnerability reporting. Never open a public issue for a vulnerability. Include the version, the operating system and the Node.js version, the steps that reproduce it and the impact as you understand it, with placeholders such as sftp.example.com instead of real hosts or passwords.

You get an acknowledgement within 7 days and an assessment within 14. Fixes ship as a new patch version with a published advisory that credits you, unless you ask not to be named. The full policy is in SECURITY.md.