GitHub Advisoryadvisory · Go
GHSA-7p4m-qxvv-g567 rclone: Local Encoding Path Traversal
MEDIUM
Official page ↗
- Aliases
- CVE-2026-71313
- Ecosystem
- Go
- Packages
- github.com/rclone/rclone
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:L
- Weaknesses
- CWE-22
- Reviewed
- 2026-08-05
Affected packages
• Go github.com/rclone/rclone: >= 1.51.0, < 1.75.0 → fixed in 1.75.0
Description
Summary
The local backend relies on its configurable filename encoder to prevent remote filename data from becoming operating-system path syntax. If a local destination uses an encoding that omits `Dot`, such as `Slash`, `None`, or `Raw`, a remote object's standard-encoded `..` component is decoded into an actual `..` component. `backend/local.localPath` then passes the decoded name to `filepath.Join`, which resolves the component and produces a path outside the configured local root.
An attacker who can create object names in a remote source that a victim copies or synchronizes to such a local destination can create or overwrite files outside the selected destination directory, with the permissions of the rclone process.
The default local encoding includes `Dot` and is not affected by that exact path. This finding requires a non-default local encoding that preserves filesystem path syntax. On Windows, a second confirmed form uses a preserved backslash to turn a remote filename into a native `..\file` path even when the destination encoding still includes `Dot`.
This is not merely an odd filename-conversion result. The local remote's configured root is the destination selected by the user, and ordinary backend operations are expected to remain within it. Rclone documents custom and `Raw` encodings as filename-conversion controls; it does not document them as an opt-out from destination confinement. The defect is that confinement depends on an encoding mask instead of an independent post-conversion path check.
Affected Assets & Attack Surface
Confirmed affected versions
• `v1.51.0` through `v1.74.4`
• Local development commit tested: `a0c09f1381ae93e2a9a33c529d170186c61ad058`
• Public `master` inspected through commit `c99b2d11edb0986cd2b1190e9fa25a58a3f12661` (2026-07-23)
`v1.51.0` introduced the configurable encoding option for the local backend. Encodings such as `None` or `Slash` could omit `Dot` from that version onward. The explicit `Raw` encoding was introduced later, in `v1.68.0`.
Required destination configuration
The destination is a local backend whose effective encoding does not safely encode `.` and `..` components. Examples include:
```text
--local-encoding Slash
--local-encoding None
--local-encoding Raw
```
The first PoC below uses `Slash`. Default configurations are not affected because the platform-specific `encoder.OS` masks include `Dot`.
On Windows, custom encodings that omit `BackSlash` can introduce an additional traversal form: an object-key component such as `..\marker.txt` can become a native path separator plus `..`, even if the encoding still contains `Dot`. The fix therefore should enforce containment after conversion to the native path format rather than only require the `Dot` flag.
Attacker-controlled input
The relevant input is an object name returned by a source backend. The confirmed source case is S3:
• `backend/s3/s3.go:2554` converts raw object keys to rclone's standard path representation with `f.opt.Enc.ToStandardPath`.
• A raw `..` component becomes the standard component `..`.
• When the destination local encoder omits `Dot`, `FromStandardPath` decodes `..` back to `..`.
Amazon S3 permits relative path components when their left-to-right cumulative count does not exceed the preceding non-relative components. Consequently, an object named:
```text
tenant/../marker.txt
```
is valid. When the victim's source remote is rooted at `bucket/tenant/`, the relative object name becomes `../marker.txt` before standard encoding. A malicious S3-compatible endpoint can return equivalent keys without relying on Amazon S3.
Reachable operations
The unsafe path resolver is used throughout the local backend, including:
• `backend/local/local.go:798` — `localPath`
• `backend/local/local.go:803` — `Put`
• `backend/local/local.go:979` — `Move`
• `backend/local/local.go:1534` — `Object.Update`
• `backend/local/local.go:1747` — `Object.Remove`
• Local directory creation and object lookup operations that call `localPath`
Normal copy and synchronization propagate the source name to the destination:
• `fs/sync/sync.go:518` passes `src.Remote()` to `operations.Copy`.
• `fs/operations/copy.go:390` uses that remote name for destination `Put` or `Update`.
Commands that copy attacker-controlled source objects to a local destination are therefore in scope, including `copy`, `sync`, and `move`.
Technical Root Cause Analysis
Rclone represents backend filenames using its standard encoding. `lib/encoder/standard.go` defines `encoder.Standard` with `EncodeDot`, causing raw names equal to `.` or `..` to be represented by fullwidth characters:
```text
. -> .
.. -> ..
```
When a standard path is converted for a destination backend, `lib/encoder/encoder.go:1214-1240` performs the following transformation for every path component:
```go
func FromStandardName(e Encoder, s string) string {
if e == Standard {
return s
}
return e.Encode(Standard.Decode(s))
}
```
For a destination encoding that omits `Dot`:
1. `Standard.Decode("..")` returns `".."`.
2. The destination encoder leaves `".."` unchanged.
3. `FromStandardPath` returns a path containing an actual parent-directory component.
The local backend then constructs the native path without validating containment:
```go
func (f *Fs) localPath(name string) string {
return filepath.Join(f.root, filepath.FromSlash(f.opt.Enc.FromStandardPath(name)))
}
```
`filepath.Join` cleans the resulting path. For example:
```text
root: /tmp/destination
name: ../marker.txt
result: /tmp/marker.txt
```
`Put` creates an object from `src.Remote()`, and `Object.Update` eventually opens that resolved path using:
```go
os.O_WRONLY | os.O_CREATE | os.O_TRUNC
```
There is no subsequent `filepath.Rel` check, anchored filesystem operation, or rejection of an absolute, volume-qualified, `.` or `..` result.
The default encoder masks the defect because it re-encodes `..` as a literal fullwidth directory name. That is not a sufficient security boundary: the encoding is explicitly configurable, including an officially documented `Raw` value that disables conversion.
The local backend contains an existing `os.Root` mechanism used while translating symlinks, but ordinary local writes do not use it. In the default non-`--links` mode, `mkdirAll`, `openFile`, rename, and remove operations use ordinary filesystem paths.
Proof of Concept & Evidence
Deterministic regression test
Add the following test to the `backend/local` package. It requires no external storage service. It uses S3's actual default encoding mask to construct the same standard `Remote()` value that an S3 key with a relative `..` component produces.
```go
package local
import (
"bytes"
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/object"
"github.com/rclone/rclone/lib/encoder"
"github.com/stretchr/testify/require"
)
func TestLocalEncodingWithoutDotEscapesRoot(t *testing.T) {
ctx := context.Background()
outer := t.TempDir()
// S3's default encoder converts a raw ".." object-key component
// into rclone's standard fullwidth representation.
s3Encoding := encoder.EncodeInvalidUtf8 | encoder.EncodeSlash | encoder.EncodeDot
remote := s3Encoding.ToStandardPath("../marker.txt")
require.NotEqual(t, "../marker.txt", remote)
// The default local encoding includes Dot and keeps the path confined.
safeRaw, err := NewFs(ctx, "safe", filepath.Join(outer, "safe"),
configmap.Simple{"encoding": encoder.OS.String()})
require.NoError(t, err)
safe := safeRaw.(*Fs)
rel, err := filepath.Rel(safe.root, safe.localPath(remote))
require.NoError(t, err)
require.False(t,
rel == ".." ||
strings.HasPrefix(rel, ".."+string(filepath.Separator)))
// Removing Dot converts the same component to a real "..".
unsafeRaw, err := NewFs(ctx, "unsafe", filepath.Join(outer, "destination"),
configmap.Simple{"encoding": "Slash"})
requir…
Related
Alias · CVE (1)
- CVE-2026-71313 rclone is a command-line program to sync files and directories to and from different cloud storage providers. vulnerability
Weakness · CWE (1)
- CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness