The security advisory clearly identifies the WithRemote function in cmd/serve/restic/restic.go as the primary source of the vulnerability. The provided commit patch confirms this by showing the change in the path validation logic within this specific function. The vulnerable code used path.Clean, which was insufficient to prevent path traversal attacks starting with ../. The fix involves replacing this logic with iofs.ValidPath, which provides a more robust validation. While the advisory mentions that other backend functions are affected, the root cause of the vulnerability is the improper input validation in WithRemote. Therefore, this is the key function that would appear in a runtime profile during exploitation, as it's the entry point for processing the malicious path.
Vulnerable functions
WithRemote
cmd/serve/restic/restic.go
The `WithRemote` function in `cmd/serve/restic/restic.go` is a middleware responsible for handling URL paths. The vulnerability lies in its path validation logic. The original code used `path.Clean(urlpath) != urlpath` to detect non-canonical paths. However, for a path starting with `../`, `path.Clean` returns the path unchanged, causing the check to be bypassed. This allows a path traversal attack where an attacker can provide a path like `../secret.txt` to access files and directories outside of the intended root directory for the `rclone serve restic` command. The vulnerability is fixed by using `iofs.ValidPath`, which correctly identifies and rejects such traversal attempts.
These are backend-specific manifestations of the same WithRemote validation flaw, not separate vulnerabilities.
Technical Details
WithRemote obtains the decoded URL path, removes external slashes, and tries to reject traversal by comparing the path with path.Clean:
urlpath = strings.Trim(urlpath, "/")
// Reject any non-canonical path, in particular one containing ".."
// traversal elements.
if urlpath != "" && path.Clean(urlpath) != urlpath {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
The comment describes the intended behavior, but the condition does not reject every parent component. path.Clean preserves leading parent components in a relative path:
GET, POST, and DELETE handlers retrieve this same value. GET passes it to s.f.NewObject, POST passes it to operations.RcatSize, and DELETE resolves the object and calls Remove. There is no second containment check.
WebDAV is used below as the concrete end-to-end example because it was the backend used for the main proof of concept. WebDAV is not the source of the validation flaw. The example demonstrates one way in which an unsafe remote accepted by WithRemote is propagated by a backend.
The WebDAV backend joins its configured root with the attacker-controlled remote:
The configured root is removed before encoding. WsgiDAV receives a normal operation for /outside-secret.txt, which is outside the root published by rclone serve restic.
The same accepted leading parent path propagates through the other affected backends tested. FTP joins its root and remote with path.Join before object operations; HTTP preserves served-root/../outside-secret.txt when constructing the endpoint request; Memory joins the root and relative path before splitting the bucket and key; and SFTP joins f.absRoot and the remote in remotePath. In each case, the backend receives the leading parent component already accepted by WithRemote. The exact escape mechanism and available operations vary by backend. Conversely, S3-compatible and local backends did not escape in the tested configuration because they encoded .. as filename characters.
Expected behavior is HTTP 400 before any backend operation. Actual behavior is HTTP 200 followed by an operation outside served-root.
Preconditions and impact
The operator must publish a backend subdirectory, the endpoint must be reachable, and the backend credential must have access to a parent or sibling object. Exploitability also depends on backend path semantics.
An attacker may:
read files and objects outside the published backup root;
create or overwrite sibling objects;
delete objects when deletion is permitted;
cross isolation boundaries between users, repositories, or automation jobs;
indirectly compromise another system if it later trusts an overwritten configuration, script, or artifact.
--append-only reduces overwrite and delete impact but does not prevent traversal reads or creation of new objects.
Proof of concept
The following procedure was executed on Linux Mint 22.3 with the official rclone v1.74.4 Linux AMD64 binary, WsgiDAV 4.3.5, and Cheroot 10.0.1. The rclone binary reports that it was built with Go 1.26.5.
Only the backends listed in this table were tested or classified. Every row was dynamically repeated with the same official v1.74.4 Linux AMD64 binary identified in the proof of concept.
Suggested remediation
Reject . and .. components in WithRemote before storing the remote in the context. Validating the decoded relative path with io/fs.ValidPath, with explicit handling for the empty API root, is one possible approach. Authorization and backend lookup should use the same validated representation.
Regression tests should cover GET, HEAD, POST, and DELETE with .., ../x, ../../x, %2e%2e/x, a/../x, and a/../../x, both with and without --private-repos.
Additional impact scenarios identified by the maintainer
GET /../ could reach the list handler and enumerate the parent directory, allowing an attacker to discover object names before accessing them.
With --append-only, a request such as DELETE /../locks/<name> could satisfy the existing delete guard and delete an object outside the served root.
A bare . path was also accepted. On bucket-based backends, POST /. could write an object outside the intended served path.