Skip to content

Instantly share code, notes, and snippets.

@jamesob
Created July 8, 2026 14:52
Show Gist options
  • Select an option

  • Save jamesob/ec5fa4c5b76897e66487a04291fd9433 to your computer and use it in GitHub Desktop.

Select an option

Save jamesob/ec5fa4c5b76897e66487a04291fd9433 to your computer and use it in GitHub Desktop.
syncthing audit by GLM-5.2 selfhosted

Syncthing Security Audit — Executive Summary

Repository audited: https://github.com/syncthing/syncthing Date: 2026-07-08 Methodology: Full file-by-file review of the source tree by four parallel read-only security auditor agents, each assigned a balanced batch of files (~599 source files total across cmd/, internal/, lib/, gui/, meta/, script/, test/, and root build files). Each agent read every file in its batch and looked for backdoors, data exfiltration, and suspicious security weaknesses in the context of the project as a whole.


Top-line verdict

No backdoors, data exfiltration, or deliberately weakened security were found.

No CRITICAL or HIGH severity issues were identified in any of the four batches. There is no evidence of:

  • Hardcoded credentials, private keys, or magic auth tokens in production code
  • Hidden authentication bypasses or kill switches
  • Data exfiltration to undocumented / attacker-controlled endpoints
  • Telemetry beyond the documented opt-in usage report (verified: the usage report sends only aggregated counts and feature flags — no file names, paths, contents, device IDs, certs, or private keys)
  • Obfuscated payloads, base64-encoded hidden destinations, or runtime-decoded strings
  • Time-bombed behavior or supply-chain fetch-execute patterns
  • Deliberately weakened crypto, hardcoded IVs/keys, or broken randomness for security-sensitive operations
  • Upgrade signature verification bypass (the embedded key is a public ECDSA P-521 key used only for verification; signed digests bind archive name + binary contents)

The findings below are all defense-in-depth hardening gaps or documented opt-in features — none rise to the level of a backdoor or suspected exfiltration that the user asked us to prioritize.


Scope of review

Batch Files Coverage Result
1 148 cmd/ (syncthing main, cli, stdiscosrv, strelaysrv, infra services), internal/ (db, sqlite, gen, slogutil), meta/, script/, root build files Clean (1 MEDIUM, 1 LOW, 2 INFO)
2 126 First half of lib/: api (auth, CSRF, tokens, support bundle), config, connections (TCP/QUIC/relay), dialer, discover, events, fs (basicfs/watch/xattr) Clean (1 LOW, 2 INFO)
3 131 Middle of lib/: fs (casefs/folding/walkfs/tempname), geoip, httpcache, ignore, locations, model (core sync engine), nat, netutil, osutil, pmp, start of protocol Clean (no findings)
4 194 Rest of lib/: protocol (encryption, wireformat, deviceid), rand, relay, scanner, signature, syncthing, tlsutil, upgrade (signing/verification), upnp, ur (usage/crash reporting), versioner (incl. external), GUI JS app, test/ Clean (1 LOW, 3 INFO)

Findings

MEDIUM

1. SSRF in relay pool stats fetcher

  • Location: cmd/infra/strelaypoolsrv/stats.go:146-178 (with statusAddr sourced from the registered relay's query string in main.go:147)
  • Description: fetchStats issues an HTTP GET to "http://" + net.JoinHostPort(statusHost, statusPort) + "/status", where statusAddr is taken directly from the relay operator's registration query string. The host is not validated against loopback, private, or link-local ranges.
  • Impact: A malicious or compromised relay operator can set statusAddr to an internal target (e.g. 169.254.169.254:80 cloud metadata endpoint, 127.0.0.1:port, or any internal IP:port), causing the pool server to perform an outbound GET from its vantage point. Exfiltration is limited (the response is parsed as a typed stats struct), but internal network probing / metadata-endpoint reachability is possible.
  • Note: This affects an infrastructure server (strelaypoolsrv), not the end-user Syncthing client. It is the only finding in the audit that lets an external party influence an outbound request target.
  • Recommendation: Validate statusHost against the relay's announced IP and reject IsLoopback(), IsPrivate(), IsUnspecified(), and link-local addresses.

LOW

2. Weak TLS configuration on the relay pool server

  • Location: cmd/infra/strelaypoolsrv/main.go:200-217
  • Description: The public-facing strelaypoolsrv TLS listener sets MinVersion: tls.VersionTLS10 and enables 3DES cipher suites (TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA) and non-PFS RSA key-exchange suites. TLS 1.0 and 3DES are deprecated; 3DES is vulnerable to Sweet32.
  • Impact: This is materially weaker than the other infra servers (stdiscosrv and strelaysrv both enforce TLS 1.2 minimum with no 3DES). Appears to be legacy compatibility, not a backdoor.
  • Recommendation: Raise MinVersion to tls.VersionTLS12 and drop 3DES / plain-RSA suites to match the other infra servers.

3. Support bundle backup written with world-writable permissions

  • Location: lib/api/api.go:1251
  • Description: getSupportBundle writes a backup copy of the generated ZIP to the config directory with mode 0o666 (world-readable + world-writable). The bundle contains logs, panic files, pprof profiles, connection stats, usage-report preview, and a partially-redacted config.
  • Impact: Not a backdoor — a weak default. Any other local user can read the bundle (local info disclosure of file paths, device IDs, addresses, profile data) and can replace/truncate it.
  • Recommendation: Use 0o600 (owner-only), consistent with the sensitivity of the bundle contents.

4. External versioner executes a user-configured command (by design, mitigated)

  • Location: lib/versioner/external.go:153
  • Description: The "external" versioner runs a user-configured command from cfg.Versioning.Params["command"] via exec.Command whenever a file is archived, substituting %FILE_PATH% and folder info into the command words.
  • Why it's acceptable: No shell is involved (go-shellquote.Split + exec.Command(words[0], words[1:]...)); words containing both unsafe metacharacters and template placeholders are rejected; STGUIAUTH/STGUIAPIKEY are stripped from the child environment. The command and file path both originate from trusted sources (user config and the local sync engine). An attacker would need write access to the config — which already implies GUI/admin compromise.
  • Recommendation: Keep the documentation link in the error message current so users understand the quoting requirement.

INFO (documented for completeness / to prevent re-flagging)

  • lib/api/api_auth.go:250-264 / lib/config/ldapconfiguration.go:13 — LDAP InsecureSkipVerify is a documented opt-in config (insecureSkipVerify, default false) for self-signed/internal-CA LDAP servers. Not a backdoor.
  • lib/discover/global.go:120,140 — Global discovery InsecureSkipVerify is keyed off the URL ?id=<deviceID> (in which case identity is enforced by the stronger device-ID-pinning idCheckingHTTPClient) or an explicit ?insecure opt-in. Not a backdoor.
  • lib/syncthing/syncthing.go:268 — BEP sync-connection TLS sets InsecureSkipVerify = true by design: Syncthing authenticates peers by the SHA-256 of their device certificate (Device ID) at the application layer, not by TLS chain validation. This is the documented TOFU/pinned-identity model. Relay-client TLS is compensated the same way (lib/relay/client/static.go:206-235).
  • lib/ur/usage_report.go:366 — Usage-report HTTP client allows URPostInsecurely (opt-in, default false) for posting to self-hosted collection endpoints with self-signed certs. Failure/crash reporting uses tlsutil.SecureDefaultWithTLS12() with no skip-verify.
  • cmd/syncthing/main.go:393-401, cmd/syncthing/cli/client.go:57-66, cmd/strelaysrv/main.go:178InsecureSkipVerify: true against the local GUI's self-signed device certificate (identity, not PKI). Documented and correct.
  • cmd/infra/stcrashreceiver/util.go:24-36, cmd/infra/ursrv/serve/serve.go:291-300X-Forwarded-For is trusted to derive a client IP for Sentry dedup ID (salted, rotated, truncated SHA-256) and usage-report address storage. No auth/access decision depends on these values. stdiscosrv --http mode trusting the proxy header is the explicit proxied-deployment contract.
  • lib/rc/rc.go:37 — Hardcoded APIKey constant is test-harness-only (passed to a locally-launched child syncthing via STGUIAPIKEY); not embedded in production binaries.

Confirmed-clean areas of particular interest

These high-sensitivity areas were specifically scrutinized and confirmed free of backdoors/exfiltration:

  • Usage reporting (lib/ur/) — Sends only aggregated counts and feature flags (version, platform, folder/device counts, total/max file counts and MiB, memory usage, SHA256/cpu benchmark, NAT type, boolean feature-configured flags). No file names, file contents, folder paths, device IDs, certificates, private keys, or config secrets are sent. UniqueID is a random per-installation token, not the device ID. Gated on explicit user acceptance (URAccepted >= 2); posts to configurable URURL (default https://data.syncthing.net/newdata). Candidate builds force-enable reporting — documented beta-channel behavior.
  • Crash reporting (cmd/syncthing/crash_reporting.go, lib/ur/failurereporting.go) — Opt-in via CREnabled; posts to user-configured CRURL (default https://crash.syncthing.net/newcrash). filterLogLines actively strips log lines before upload. Uses TLS 1.2 with no skip-verify.
  • Upgrade verification (lib/upgrade/, lib/signature/) — Embedded public ECDSA P-521 key used only for verification; ecdsa.Verify with SHA-256; signed digest covers archive name + binary contents; rejects when signature is missing; sizes capped. No hardcoded private keys, no alternate keys, no "accept if unsigned" path.
  • Device/folder encryption (lib/protocol/encryption.go) — AEAD throughout: ChaCha20-Poly1305 (extended-nonce) for randomized encryption, AES-SIV (miscreant) for deterministic encryption. Nonces from crypto/rand. Key derivation: scrypt (N=32768) for password→folder-key, HKDF-SHA256 for folder-key→file-key. Padding hides small-file size leakage. No hardcoded keys/IVs, no ECB, no weakened mode.
  • Secure RNG (lib/rand/securesource.go) — Reads exclusively from crypto/rand.Reader; Seed() panics by design. All session, CSRF, and API-key tokens flow through this.
  • TLS config (lib/tlsutil/tlsutil.go) — TLS 1.3-only and TLS 1.2-min with curated ciphers; Ed25519/ECDSA-P256 cert generation; private keys written 0o600; serials from secure RNG.
  • REST API auth (lib/api/) — bcrypt password hashing (constant-time), crypto/rand-backed session/CSRF/API-key tokens with expiry and max-active caps, login body size limit, anti-brute-force sleep, CSRF required for /rest/ (API-key bearer bypass is intentional for programmatic clients). Support bundle redacts APIKey, Password, User, EncryptionPassword.
  • Wire protocol validation (lib/protocol/protocol.go)checkFilename rejects non-canonical paths, "", ".", "..", absolute paths, and ../-prefixed names (prevents path traversal over the wire). Message/header/request sizes bounded. LZ4 decompression bounded.
  • Sync engine path/symlink handling (lib/model/, lib/fs/)basicfs.rooted() canonicalizes and joins against root; watch events outside the root are rejected; O_NOFOLLOW on Unix opens. Symlink traversal explicitly tested (TestSymlinkTraversalRead/Write, TestDeleteBehindSymlink, TestTraversesSymlink).
  • Build tooling / scriptsexec.Command uses arg vectors throughout (no sh -c); codesign/keychain passwords passed as separate args. docker-entrypoint.sh uses exec "$@". No build-time backdoor flags.
  • GUI JavaScript (gui/default/syncthing/) — All HTTP calls target the local backend (rest) or local asset files. CSRF cookie correctly wired to the device ID. $translateProvider.useSanitizeValueStrategy('escape') enabled. No eval, new Function, document.write, or remote-script injection. External URLs in templates are static docs/license links only.

Conclusion

Syncthing's codebase shows no signs of malicious intervention. The architecture's security-sensitive primitives (authentication, CSRF, token generation, TLS, device-identity pinning, peer-wire validation, encryption, secure randomness, upgrade signature verification, and telemetry) are implemented correctly and without hidden bypasses. The four findings above MEDIUM/LOW severity are conventional hardening gaps in infrastructure/auxiliary code (an SSRF surface in the relay pool server, a legacy TLS configuration, an over-permissive file mode on a diagnostic artifact) and a by-design external-command execution that is properly mitigated. None constitute a backdoor or data exfiltration path.

The most actionable recommendation is to remediate the SSRF in strelaypoolsrv (Finding 1) and the world-writable support-bundle permissions (Finding 3), and to modernize the strelaypoolsrv TLS configuration (Finding 2) — but these are hardening improvements, not evidence of compromise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment