GitHub Advisoryadvisory · Go

GHSA-7p4m-qxvv-g567 rclone: Local Encoding Path Traversal

MEDIUM Published 2026-08-05 Modified 2026-08-05 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

References