GitHub Advisoryadvisory · Go

GHSA-38xv-hf3p-h7mq rclone: source object names can escape the configured root on upload

MEDIUM Published 2026-09-10 Modified 2026-09-10 Official page ↗
Aliases
CVE-2026-88046
Ecosystem
Go
Packages
github.com/rclone/rclone
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N
Weaknesses
CWE-22
Reviewed
2026-09-10

Affected packages

• Go github.com/rclone/rclone: < 1.75.1 → fixed in 1.75.1

Description

Summary Multiple backends, when given a specially crafted object to copy, can escape the backend confinement. | Backend | Keep/Close | Per-backend severity | |---|---|---| | sftp | Medium | Real filesystem escape, fires under default encoding. | | smb | Low-Medium | Escapes to a different SMB share the credential can reach. | | ftp | Low | Real, leading-`..` overshoot PoC is partly neutralized by encoding; escape bounded to at/below the login base. | | webdav | Low | Server-side ACLs are the real boundary. | | b2 | Low | Same-account sibling bucket crossing on a flat keyspace. | | swift | Low | Same, container. | | qingstor | Low | Same. | | oracleobjectstorage | Low | Same. | | internetarchive | Low | IA items are owner-writable only; confined to user's own items. | | storj | Low | Can retarget a different bucket in the same access grant. | | filelu | Low | Confined to the user's own account. | | shade | Low | Confined to the user's own drive. | | sia | Low | siad API password already grants full-daemon access. | Root cause rclone core does not sanitize `..` in a source object's `Remote()` - verified: nothing in `fs/march`, `fs/sync`, `fs/list`, or `fs/operations` rejects `..` segments before the name reaches the destination backend's `Put`/`Update`/`Mkdir`. Confinement is therefore each backend's responsibility, and these backends join `root + remote` without a check. This divides into two classes: • Bucket based backends - `bucket.Split(path.Join(f.root, rootRelativePath))`: • `backend/b2/b2.go:404`, `backend/swift/swift.go:464`, `backend/qingstor/qingstor.go:198`, `backend/oracleobjectstorage/oracleobjectstorage.go:245`, `backend/internetarchive/internetarchive.go:1016`, `backend/smb/smb.go:885`, `backend/storj/fs.go:289`. • `path.Join` collapses `..` on the standard (ASCII) form before encoding is applied (e.g. `FromStandardPath(path.Join(...))` at `backend/b2/b2.go:1641`), so `EncodeDot` never gets the chance to neutralize the `..`. • `lib/bucket.Join` does not clean paths (keeps `..` as a literal key segment); `path.Join` does. `backend/s3`, `backend/azureblob`, `backend/googlecloudstorage` already use `bucket.Join` and are therefore not affected. • Path based backends - `path.Join(root, remote)` onto a real path: • sftp: `remotePath = path.Join(f.absRoot, f.opt.Enc.FromStandardPath(remote))` (`backend/sftp/sftp.go:2497`). Default encoding is `encoder.Display` (== `Standard`), and `FromStandardPath` short-circuits to a pass-through in that mode, so `..` survives; `f.absRoot` is absolute, so `path.Join("/home/user/root", "../../../../etc/passwd")` -> `/etc/passwd`. • webdav: `filePath` at `backend/webdav/webdav.go:426-432`. • ftp: `path.Join(f.root, remote)` at ~14 sites (e.g. `backend/ftp/ftp.go:1247`). • filelu, shade, sia: analogous joins. Precondition that limits reachability For any of these to fire, a source must hand rclone a `Remote()` containing raw `..`. That is only possible when: 1. the source is a flat-keyspace object store (not a filesystem - a local/sftp/smb source cannot represent `../../x` as one directory entry), and 2. the offending key was written with native, non-rclone tooling - rclone's own writer applies `EncodeDot` and rewrites a `..` segment to fullwidth `..`, so you cannot create such a key *through rclone*. rclone's source-side listing does pass a natively-planted raw `..` key through unchanged (verified for b2: `remote := file.Name[len(prefix):]` after `ToStandardPath`, `backend/b2/b2.go:858,867`). The reports never establish this precondition; it is the same omission across every member of the class. Example attack ```bash Step 1 - attacker, using NATIVE S3 tooling (NOT rclone) on a source the victim ingests from: aws s3api put-object --bucket shared-drop --key '../../victim-backups/pwned.txt' --body evil.txt Step 2 - victim's ordinary ingest: rclone copy s3-drop:shared-drop b2:victim-uploads/incoming path.Join("victim-uploads/incoming", "../../victim-backups/pwned.txt") = "victim-backups/pwned.txt" -> lands in the victim's victim-backups bucket instead of under incoming/ ``` The blast radius is the victim's own account (a bucket/share/path the configured credential already reaches) - integrity misdirection, not a cross-tenant or confidentiality breach. sftp/smb are the exception in *reach* (server filesystem / other share), still bounded by the login's own permissions. Precedent This is the same class as the already-fixed local backend advisory https://github.com/rclone/rclone/security/advisories/GHSA-7p4m-qxvv-g567, which added `(*Fs).localPath` returning `errPathEscapes` for names resolving outside the root (`backend/local/local.go:819-826`). That fix was justified because the destination was the operator's own OS filesystem; the same reasoning extends (at lower severity) to sftp/smb.

Related

Alias · CVE (1)

  • CVE-2026-88046 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