Conversation
ConnectToFTP built a goftp.Config with only user, password and timeout: no TLSConfig, so FTPS was unreachable and both credentials and backup data always travelled in the clear, with nothing in the config to change that. Add a tls option (explicit, implicit, none) defaulting to explicit AUTH TLS, plus tls_insecure_no_verify for certificate verification. Plain FTP now has to be asked for twice -- tls=none is refused unless tls_insecure_no_verify=true acknowledges what it means -- which mirrors how the webdav connector gates dav:// behind insecure=true. The exporter also joined record pathnames onto the root with no containment check, so a record pathname of "/../../etc/x" resolved above the restore root and was written there; contained() rejects that now. It uses path rather than filepath because FTP paths are slash-separated whatever the client platform, which filepath.Join got wrong on Windows anyway. writeAtomic() named its temporary file with math/rand/v2, neither seeded nor a CSPRNG, so on a shared server the name could be precomputed and pre-created. Draw the suffix from crypto/rand instead.
QuentinVigand
approved these changes
Sep 1, 2026
|
|
||
| // TLS selects the transport: "explicit" upgrades the control connection | ||
| // with AUTH TLS, "implicit" wraps it from the start, "none" is plain FTP. | ||
| TLS string |
Contributor
There was a problem hiding this comment.
It be nice to add const for possible values and/or a type for this.
type TLS string
const (
TLSExplicit TLS = "explicit"
TLSImplicit TLS = "implicit"
TLSNone TLS = "none"
)| @@ -0,0 +1,7 @@ | |||
| package common | |||
Contributor
There was a problem hiding this comment.
NIT: we should rename this common package to something else. (config, ftp, conn, ...)
|
|
||
| if opts.TLS != "none" { | ||
| hostname := host | ||
| if h, _, err := splitHostPort(host); err == nil { |
Contributor
There was a problem hiding this comment.
Suggested change
| if h, _, err := splitHostPort(host); err == nil { | |
| if h, _, err := net.SplitHostPort(host); err == nil { |
So we can drop the hostport file
|
|
||
| import "net" | ||
|
|
||
| func splitHostPort(host string) (string, string, error) { |
Contributor
There was a problem hiding this comment.
As suggested before, we can just use net.SplitHostPort(host) and delete this file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three issues in the FTP connector.
1. No transport security at all
No
TLSConfig, so FTPS was simply unreachable — credentials and every byte of backup data always went in the clear, with no config key to change it.Adds a
tlsoption (explicit/implicit/none) defaulting to explicit AUTH TLS, andtls_insecure_no_verifyfor certificate verification. Plain FTP now has to be asked for twice:tls=noneis refused unlesstls_insecure_no_verify=trueacknowledges it. That's the same shape as thedav://gate inwebdav/connector.go— reusing the pattern this repo already settled on.This changes the default, so existing plaintext configs will fail with an actionable error pointing at
tls=none.2. The exporter had no containment check
filepath.Join(p.Root(), record.Pathname)with nothing after it: a record pathname of/../../etc/xresolves above the restore root and is written there.contained()rejects it now.It also switches
filepath→path, since FTP paths are slash-separated regardless of client platform —filepath.Joinwas producing backslashes on Windows.3. Predictable temp names
fmt.Sprintf("%s.tmp.%d", pathname, rand.Int())withmath/rand/v2— neither seeded nor a CSPRNG. On a shared server the name can be precomputed and pre-created. Nowcrypto/rand.Tests
common/options_test.gocovers the TLS default, the double opt-in for cleartext, and rejected values.exporter/containment_test.gocovers the traversal cases.go vetclean.🤖 Generated with Claude Code